mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-17 14:27:02 +08:00
chore(editor): fix imports in legacy tests (#10300)
This commit is contained in:
455
blocksuite/tests-legacy/e2e/attachment.spec.ts
Normal file
455
blocksuite/tests-legacy/e2e/attachment.spec.ts
Normal file
@@ -0,0 +1,455 @@
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { sleep } from '@blocksuite/global/utils';
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import { dragBlockToPoint, popImageMoreMenu } from './utils/actions/drag.js';
|
||||
import { switchEditorMode } from './utils/actions/edgeless.js';
|
||||
import {
|
||||
pressArrowDown,
|
||||
pressArrowUp,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
pressShiftTab,
|
||||
pressTab,
|
||||
redoByKeyboard,
|
||||
SHORT_KEY,
|
||||
type,
|
||||
undoByKeyboard,
|
||||
} from './utils/actions/keyboard.js';
|
||||
import {
|
||||
captureHistory,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getPageSnapshot,
|
||||
initEmptyEdgelessState,
|
||||
initEmptyParagraphState,
|
||||
resetHistory,
|
||||
waitNextFrame,
|
||||
} from './utils/actions/misc.js';
|
||||
import {
|
||||
assertBlockChildrenIds,
|
||||
assertBlockCount,
|
||||
assertBlockFlavour,
|
||||
assertBlockSelections,
|
||||
assertKeyboardWorkInInput,
|
||||
assertParentBlockFlavour,
|
||||
assertRichImage,
|
||||
assertRichTextInlineRange,
|
||||
} from './utils/asserts.js';
|
||||
import { test } from './utils/playwright.js';
|
||||
|
||||
const FILE_NAME = 'test-card-1.png';
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const FILE_PATH = path.resolve(
|
||||
__dirname,
|
||||
`../../playground/public/${FILE_NAME}`
|
||||
);
|
||||
|
||||
function getAttachment(page: Page) {
|
||||
const attachment = page.locator('affine-attachment');
|
||||
const loading = attachment.locator('.affine-attachment-card.loading');
|
||||
const toolbar = page.locator('.affine-attachment-toolbar');
|
||||
const switchViewButton = toolbar.getByRole('button', { name: 'Switch view' });
|
||||
const renameBtn = toolbar.getByRole('button', { name: 'Rename' });
|
||||
const renameInput = page.locator('.affine-attachment-rename-container input');
|
||||
|
||||
const insertAttachment = async () => {
|
||||
await page.evaluate(() => {
|
||||
// Force fallback to input[type=file] in tests
|
||||
// See https://github.com/microsoft/playwright/issues/8850
|
||||
window.showOpenFilePicker = undefined;
|
||||
});
|
||||
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await waitNextFrame(page);
|
||||
await type(page, '/');
|
||||
await resetHistory(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await type(page, 'file', 100);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const fileChooser = page.waitForEvent('filechooser');
|
||||
await pressEnter(page);
|
||||
await sleep(100);
|
||||
await (await fileChooser).setFiles(FILE_PATH);
|
||||
|
||||
// Try to break the undo redo test
|
||||
await captureHistory(page);
|
||||
|
||||
await expect(attachment).toBeVisible();
|
||||
};
|
||||
|
||||
const getName = () =>
|
||||
attachment.locator('.affine-attachment-content-title-text').innerText();
|
||||
|
||||
return {
|
||||
// locators
|
||||
attachment,
|
||||
toolbar,
|
||||
switchViewButton,
|
||||
renameBtn,
|
||||
renameInput,
|
||||
|
||||
// actions
|
||||
insertAttachment,
|
||||
/**
|
||||
* Wait for the attachment upload to finish
|
||||
*/
|
||||
waitLoading: () => loading.waitFor({ state: 'hidden' }),
|
||||
getName,
|
||||
getSize: () =>
|
||||
attachment.locator('.affine-attachment-content-info').innerText(),
|
||||
|
||||
turnToEmbed: async () => {
|
||||
await expect(switchViewButton).toBeVisible();
|
||||
await switchViewButton.click();
|
||||
await page.getByRole('button', { name: 'Embed view' }).click();
|
||||
await assertRichImage(page, 1);
|
||||
},
|
||||
rename: async (newName: string) => {
|
||||
await attachment.hover();
|
||||
await expect(toolbar).toBeVisible();
|
||||
await renameBtn.click();
|
||||
await page.keyboard.press(`${SHORT_KEY}+a`, { delay: 50 });
|
||||
await pressBackspace(page);
|
||||
await type(page, newName);
|
||||
await pressEnter(page);
|
||||
expect(await getName()).toContain(newName);
|
||||
},
|
||||
|
||||
// external
|
||||
turnImageToCard: async () => {
|
||||
const { turnIntoCardButton } = await popImageMoreMenu(page);
|
||||
await turnIntoCardButton.click();
|
||||
await expect(attachment).toBeVisible();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('can insert attachment from slash menu', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
const { insertAttachment, waitLoading, getName, getSize } =
|
||||
getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await insertAttachment();
|
||||
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
expect(await getName()).toBe(FILE_NAME);
|
||||
expect(await getSize()).toBe('45.8 kB');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('should undo/redo works for attachment', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
const { insertAttachment, waitLoading } = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await insertAttachment();
|
||||
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_1.json`
|
||||
);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
// The loading/error state should not be restored after undo
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_2.json`
|
||||
);
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
// The loading/error state should not be restored after undo
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_3.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('should rename attachment works', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/4534',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const {
|
||||
attachment,
|
||||
renameBtn,
|
||||
renameInput,
|
||||
insertAttachment,
|
||||
waitLoading,
|
||||
getName,
|
||||
rename,
|
||||
} = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await insertAttachment();
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
expect(await getName()).toBe(FILE_NAME);
|
||||
|
||||
await attachment.hover();
|
||||
await expect(renameBtn).toBeVisible();
|
||||
await renameBtn.click();
|
||||
await assertKeyboardWorkInInput(page, renameInput);
|
||||
await pressEscape(page);
|
||||
await expect(renameInput).not.toBeVisible();
|
||||
|
||||
await rename('new-name');
|
||||
expect(await getName()).toBe('new-name.png');
|
||||
await rename('');
|
||||
expect(await getName()).toBe('.png');
|
||||
await rename('abc');
|
||||
expect(await getName()).toBe('abc');
|
||||
});
|
||||
|
||||
test('should turn attachment to image works', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const { insertAttachment, waitLoading, turnToEmbed, turnImageToCard } =
|
||||
getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await insertAttachment();
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
await turnToEmbed();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_1.json`
|
||||
);
|
||||
await turnImageToCard();
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_2.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('should attachment can be deleted', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const { attachment, insertAttachment, waitLoading } = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await insertAttachment();
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
await attachment.click();
|
||||
await pressBackspace(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
});
|
||||
|
||||
test(`support dragging attachment block directly`, async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
const { insertAttachment, waitLoading, getName, getSize } =
|
||||
getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await insertAttachment();
|
||||
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
expect(await getName()).toBe(FILE_NAME);
|
||||
expect(await getSize()).toBe('45.8 kB');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_1.json`
|
||||
);
|
||||
|
||||
const attachmentBlock = page.locator('affine-attachment');
|
||||
const rect = await attachmentBlock.boundingBox();
|
||||
if (!rect) {
|
||||
throw new Error('image not found');
|
||||
}
|
||||
|
||||
// add new paragraph blocks
|
||||
await page.mouse.click(rect.x + 20, rect.y + rect.height + 20);
|
||||
await focusRichText(page);
|
||||
await type(page, '111');
|
||||
await page.waitForTimeout(200);
|
||||
await pressEnter(page);
|
||||
|
||||
await type(page, '222');
|
||||
await page.waitForTimeout(200);
|
||||
await pressEnter(page);
|
||||
|
||||
await type(page, '333');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.waitForTimeout(200);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_2.json`
|
||||
);
|
||||
|
||||
// drag bookmark block
|
||||
await page.mouse.move(rect.x + 20, rect.y + 20);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(rect.x + 40, rect.y + rect.height + 80, { steps: 20 });
|
||||
await page.mouse.up();
|
||||
|
||||
const rects = page.locator('affine-block-selection').locator('visible=true');
|
||||
await expect(rects).toHaveCount(1);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_3.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('press backspace after bookmark block can select bookmark block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const { insertAttachment, waitLoading } = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await pressEnter(page);
|
||||
await pressArrowUp(page);
|
||||
await insertAttachment();
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
await focusRichText(page);
|
||||
await assertBlockCount(page, 'paragraph', 1);
|
||||
await assertRichTextInlineRange(page, 0, 0);
|
||||
await pressBackspace(page);
|
||||
await assertBlockSelections(page, ['4']);
|
||||
await assertBlockCount(page, 'paragraph', 0);
|
||||
});
|
||||
|
||||
test('cancel file picker with input element resolves', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
const { attachment } = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await pressEnter(page);
|
||||
await pressArrowUp(page);
|
||||
|
||||
await page.evaluate(() => {
|
||||
// Force fallback to input[type=file]
|
||||
window.showOpenFilePicker = undefined;
|
||||
});
|
||||
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await waitNextFrame(page);
|
||||
await type(page, '/file', 100);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const fileChooser = page.waitForEvent('filechooser');
|
||||
await pressEnter(page);
|
||||
const inputFile = page.locator("input[type='file']");
|
||||
await expect(inputFile).toHaveCount(1);
|
||||
|
||||
// This does not trigger `cancel` event and,
|
||||
// therefore, the test isn't representative.
|
||||
// Waiting for https://github.com/microsoft/playwright/issues/27524
|
||||
await (await fileChooser).setFiles([]);
|
||||
|
||||
await expect(attachment).toHaveCount(0);
|
||||
await expect(inputFile).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('indent attachment block to paragraph', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const { insertAttachment, waitLoading } = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await pressEnter(page);
|
||||
await insertAttachment();
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
await assertBlockChildrenIds(page, '1', ['2', '4']);
|
||||
await assertBlockFlavour(page, '1', 'affine:note');
|
||||
await assertBlockFlavour(page, '2', 'affine:paragraph');
|
||||
await assertBlockFlavour(page, '4', 'affine:attachment');
|
||||
|
||||
await focusRichText(page);
|
||||
await pressArrowDown(page);
|
||||
await assertBlockSelections(page, ['4']);
|
||||
await pressTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['2']);
|
||||
await assertBlockChildrenIds(page, '2', ['4']);
|
||||
|
||||
await pressShiftTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['2', '4']);
|
||||
});
|
||||
|
||||
test('indent attachment block to list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const { insertAttachment, waitLoading } = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '- a');
|
||||
await pressEnter(page);
|
||||
await insertAttachment();
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
await assertBlockChildrenIds(page, '1', ['3', '5']);
|
||||
await assertBlockFlavour(page, '1', 'affine:note');
|
||||
await assertBlockFlavour(page, '3', 'affine:list');
|
||||
await assertBlockFlavour(page, '5', 'affine:attachment');
|
||||
|
||||
await focusRichText(page);
|
||||
await pressArrowDown(page);
|
||||
await assertBlockSelections(page, ['5']);
|
||||
await pressTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['3']);
|
||||
await assertBlockChildrenIds(page, '3', ['5']);
|
||||
|
||||
await pressShiftTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['3', '5']);
|
||||
});
|
||||
|
||||
test('attachment can be dragged from note to surface top level block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
const { insertAttachment, waitLoading } = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await insertAttachment();
|
||||
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
await switchEditorMode(page);
|
||||
await page.mouse.dblclick(450, 450);
|
||||
|
||||
await dragBlockToPoint(page, '4', { x: 200, y: 200 });
|
||||
|
||||
await waitNextFrame(page);
|
||||
await assertParentBlockFlavour(page, '4', 'affine:surface');
|
||||
});
|
||||
589
blocksuite/tests-legacy/e2e/basic.spec.ts
Normal file
589
blocksuite/tests-legacy/e2e/basic.spec.ts
Normal file
@@ -0,0 +1,589 @@
|
||||
import type { DeltaInsert } from '@blocksuite/inline';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addNoteByClick,
|
||||
captureHistory,
|
||||
click,
|
||||
disconnectByClick,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
focusTitle,
|
||||
getCurrentEditorTheme,
|
||||
getCurrentHTMLTheme,
|
||||
getPageSnapshot,
|
||||
initEmptyEdgelessState,
|
||||
initEmptyParagraphState,
|
||||
pressArrowLeft,
|
||||
pressArrowRight,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressForwardDelete,
|
||||
pressForwardDeleteWord,
|
||||
pressShiftEnter,
|
||||
redoByClick,
|
||||
redoByKeyboard,
|
||||
setSelection,
|
||||
switchEditorMode,
|
||||
toggleDarkMode,
|
||||
type,
|
||||
undoByClick,
|
||||
undoByKeyboard,
|
||||
waitDefaultPageLoaded,
|
||||
waitNextFrame,
|
||||
} from './utils/actions/index.js';
|
||||
import {
|
||||
assertBlockChildrenIds,
|
||||
assertEmpty,
|
||||
assertRichTextInlineDeltas,
|
||||
assertRichTexts,
|
||||
assertText,
|
||||
assertTitle,
|
||||
} from './utils/asserts.js';
|
||||
import { scoped, test } from './utils/playwright.js';
|
||||
import { getFormatBar } from './utils/query.js';
|
||||
|
||||
const BASIC_DEFAULT_SNAPSHOT = 'basic test default';
|
||||
|
||||
test(scoped`basic input`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
|
||||
await test.expect(page).toHaveTitle(/BlockSuite/);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${BASIC_DEFAULT_SNAPSHOT}.json`
|
||||
);
|
||||
await assertText(page, 'hello');
|
||||
});
|
||||
|
||||
test(scoped`basic init with external text`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
|
||||
await page.evaluate(() => {
|
||||
const { doc } = window;
|
||||
const rootId = doc.addBlock('affine:page', {
|
||||
title: new window.$blocksuite.store.Text('hello'),
|
||||
});
|
||||
const note = doc.addBlock('affine:note', {}, rootId);
|
||||
|
||||
const text = new window.$blocksuite.store.Text('world');
|
||||
doc.addBlock('affine:paragraph', { text }, note);
|
||||
|
||||
const delta = [
|
||||
{ insert: 'foo ' },
|
||||
{ insert: 'bar', attributes: { bold: true } },
|
||||
];
|
||||
doc.addBlock(
|
||||
'affine:paragraph',
|
||||
{
|
||||
text: new window.$blocksuite.store.Text(delta as DeltaInsert[]),
|
||||
},
|
||||
note
|
||||
);
|
||||
});
|
||||
|
||||
await assertTitle(page, 'hello');
|
||||
await assertRichTexts(page, ['world', 'foo bar']);
|
||||
await focusRichText(page);
|
||||
});
|
||||
|
||||
test(scoped`basic multi user state`, async ({ context, page: pageA }) => {
|
||||
const room = await enterPlaygroundRoom(pageA);
|
||||
await initEmptyParagraphState(pageA);
|
||||
await waitNextFrame(pageA);
|
||||
await waitDefaultPageLoaded(pageA);
|
||||
await focusTitle(pageA);
|
||||
await type(pageA, 'hello');
|
||||
|
||||
const pageB = await context.newPage();
|
||||
await enterPlaygroundRoom(pageB, {
|
||||
room,
|
||||
noInit: true,
|
||||
});
|
||||
await waitDefaultPageLoaded(pageB);
|
||||
await focusTitle(pageB);
|
||||
await assertTitle(pageB, 'hello');
|
||||
|
||||
await type(pageB, ' world');
|
||||
await assertTitle(pageA, 'hello world');
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`A open and edit, then joins B`,
|
||||
async ({ context, page: pageA }) => {
|
||||
const room = await enterPlaygroundRoom(pageA);
|
||||
await initEmptyParagraphState(pageA);
|
||||
await waitNextFrame(pageA);
|
||||
await focusRichText(pageA);
|
||||
await type(pageA, 'hello');
|
||||
|
||||
const pageB = await context.newPage();
|
||||
await enterPlaygroundRoom(pageB, {
|
||||
flags: {},
|
||||
room,
|
||||
noInit: true,
|
||||
});
|
||||
|
||||
// wait until pageB content updated
|
||||
await assertText(pageB, 'hello');
|
||||
await Promise.all([
|
||||
assertText(pageA, 'hello'),
|
||||
expect(await getPageSnapshot(pageA, true)).toMatchSnapshot(
|
||||
`${BASIC_DEFAULT_SNAPSHOT}.json`
|
||||
),
|
||||
expect(await getPageSnapshot(pageB, true)).toMatchSnapshot(
|
||||
`${BASIC_DEFAULT_SNAPSHOT}.json`
|
||||
),
|
||||
assertBlockChildrenIds(pageA, '0', ['1']),
|
||||
assertBlockChildrenIds(pageB, '0', ['1']),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
test(scoped`A first open, B first edit`, async ({ context, page: pageA }) => {
|
||||
const room = await enterPlaygroundRoom(pageA);
|
||||
await initEmptyParagraphState(pageA);
|
||||
await waitNextFrame(pageA);
|
||||
await focusRichText(pageA);
|
||||
|
||||
const pageB = await context.newPage();
|
||||
await enterPlaygroundRoom(pageB, {
|
||||
room,
|
||||
noInit: true,
|
||||
});
|
||||
await pageB.waitForTimeout(500);
|
||||
await focusRichText(pageB);
|
||||
|
||||
await waitNextFrame(pageA);
|
||||
await waitNextFrame(pageB);
|
||||
await type(pageB, 'hello');
|
||||
await pageA.waitForTimeout(500);
|
||||
|
||||
// wait until pageA content updated
|
||||
await assertText(pageA, 'hello');
|
||||
await assertText(pageB, 'hello');
|
||||
await Promise.all([
|
||||
expect(await getPageSnapshot(pageA, true)).toMatchSnapshot(
|
||||
`${BASIC_DEFAULT_SNAPSHOT}.json`
|
||||
),
|
||||
expect(await getPageSnapshot(pageB, true)).toMatchSnapshot(
|
||||
`${BASIC_DEFAULT_SNAPSHOT}.json`
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`does not sync when disconnected`,
|
||||
async ({ browser, page: pageA }) => {
|
||||
test.fail();
|
||||
|
||||
const room = await enterPlaygroundRoom(pageA);
|
||||
const pageB = await browser.newPage();
|
||||
await enterPlaygroundRoom(pageB, { flags: {}, room });
|
||||
|
||||
await disconnectByClick(pageA);
|
||||
await disconnectByClick(pageB);
|
||||
|
||||
// click together, both init with default id should lead to conflicts
|
||||
await initEmptyParagraphState(pageA);
|
||||
await initEmptyParagraphState(pageB);
|
||||
|
||||
await waitNextFrame(pageA);
|
||||
await focusRichText(pageA);
|
||||
await waitNextFrame(pageB);
|
||||
await focusRichText(pageB);
|
||||
await waitNextFrame(pageA);
|
||||
|
||||
await type(pageA, '');
|
||||
await waitNextFrame(pageB);
|
||||
await type(pageB, '');
|
||||
await waitNextFrame(pageA);
|
||||
await type(pageA, 'hello');
|
||||
await waitNextFrame(pageB);
|
||||
|
||||
await assertText(pageB, 'hello');
|
||||
await assertText(pageA, 'hello'); // actually '\n'
|
||||
}
|
||||
);
|
||||
|
||||
test(scoped`basic paired undo/redo`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
|
||||
await assertText(page, 'hello');
|
||||
await undoByClick(page);
|
||||
await assertEmpty(page);
|
||||
await redoByClick(page);
|
||||
await assertText(page, 'hello');
|
||||
|
||||
await undoByClick(page);
|
||||
await assertEmpty(page);
|
||||
await redoByClick(page);
|
||||
await assertText(page, 'hello');
|
||||
});
|
||||
|
||||
test(scoped`undo/redo with keyboard`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
|
||||
await assertText(page, 'hello');
|
||||
await undoByKeyboard(page);
|
||||
await assertEmpty(page);
|
||||
await redoByClick(page);
|
||||
await assertText(page, 'hello');
|
||||
});
|
||||
|
||||
test(scoped`undo after adding block twice`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['hello']);
|
||||
await redoByKeyboard(page);
|
||||
await assertRichTexts(page, ['hello', 'world']);
|
||||
});
|
||||
|
||||
test(scoped`undo/redo twice after adding block twice`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
await assertRichTexts(page, ['hello', 'world']);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await redoByClick(page);
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await assertRichTexts(page, ['hello', 'world']);
|
||||
});
|
||||
|
||||
test(scoped`should undo/redo works on title`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await waitNextFrame(page);
|
||||
await focusTitle(page);
|
||||
await type(page, 'title');
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello world');
|
||||
|
||||
await assertTitle(page, 'title');
|
||||
await assertRichTexts(page, ['hello world']);
|
||||
|
||||
await captureHistory(page);
|
||||
await pressBackspace(page, 5);
|
||||
await captureHistory(page);
|
||||
await focusTitle(page);
|
||||
await type(page, ' something');
|
||||
|
||||
await assertTitle(page, 'title something');
|
||||
await assertRichTexts(page, ['hello ']);
|
||||
|
||||
await focusRichText(page);
|
||||
await undoByKeyboard(page);
|
||||
await assertTitle(page, 'title');
|
||||
await assertRichTexts(page, ['hello ']);
|
||||
await undoByKeyboard(page);
|
||||
await assertTitle(page, 'title');
|
||||
await assertRichTexts(page, ['hello world']);
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await assertTitle(page, 'title');
|
||||
await assertRichTexts(page, ['hello ']);
|
||||
await redoByKeyboard(page);
|
||||
await assertTitle(page, 'title something');
|
||||
await assertRichTexts(page, ['hello ']);
|
||||
});
|
||||
|
||||
test(scoped`undo multi notes`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await addNoteByClick(page);
|
||||
await assertRichTexts(page, ['', '']);
|
||||
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await redoByClick(page);
|
||||
await assertRichTexts(page, ['', '']);
|
||||
});
|
||||
|
||||
test(scoped`change theme`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const currentTheme = await getCurrentHTMLTheme(page);
|
||||
await toggleDarkMode(page);
|
||||
const expectNextTheme = currentTheme === 'light' ? 'dark' : 'light';
|
||||
const nextHTMLTheme = await getCurrentHTMLTheme(page);
|
||||
expect(nextHTMLTheme).toBe(expectNextTheme);
|
||||
|
||||
const nextEditorTheme = await getCurrentEditorTheme(page);
|
||||
expect(nextEditorTheme).toBe(expectNextTheme);
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`should be able to delete an emoji completely by pressing backspace once`,
|
||||
async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2138',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '🌷🙅♂️🏳️🌈');
|
||||
await pressBackspace(page);
|
||||
await pressBackspace(page);
|
||||
await pressBackspace(page);
|
||||
await assertText(page, '');
|
||||
}
|
||||
);
|
||||
|
||||
test(scoped`delete emoji in the middle of the text`, async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2138',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '1🌷1🙅♂️1🏳️🌈1👨👩👧👦1');
|
||||
await pressArrowLeft(page, 1);
|
||||
await pressBackspace(page);
|
||||
await pressArrowLeft(page, 1);
|
||||
await pressBackspace(page);
|
||||
await pressArrowLeft(page, 1);
|
||||
await pressBackspace(page);
|
||||
await pressArrowLeft(page, 1);
|
||||
await pressBackspace(page);
|
||||
await assertText(page, '11111');
|
||||
});
|
||||
|
||||
test(scoped`delete emoji forward`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '1🌷1🙅♂️1🏳️🌈1👨👩👧👦1');
|
||||
await pressArrowLeft(page, 8);
|
||||
await pressForwardDelete(page);
|
||||
await pressArrowRight(page, 1);
|
||||
await pressForwardDelete(page);
|
||||
await pressArrowRight(page, 1);
|
||||
await pressForwardDelete(page);
|
||||
await pressArrowRight(page, 1);
|
||||
await pressForwardDelete(page);
|
||||
await assertText(page, '11111');
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`ZERO_WIDTH_SPACE should be counted by one cursor position`,
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await pressShiftEnter(page);
|
||||
await type(page, 'asdfg');
|
||||
await pressEnter(page);
|
||||
await undoByKeyboard(page);
|
||||
await page.waitForTimeout(300);
|
||||
await pressBackspace(page);
|
||||
await assertRichTexts(page, ['\nasdf']);
|
||||
}
|
||||
);
|
||||
|
||||
test('when no note block, click editing area auto add a new note block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await page.locator('affine-edgeless-note').click({ force: true });
|
||||
await pressBackspace(page);
|
||||
await switchEditorMode(page);
|
||||
const edgelessNote = await page.evaluate(() => {
|
||||
return document.querySelector('affine-edgeless-note');
|
||||
});
|
||||
expect(edgelessNote).toBeNull();
|
||||
await click(page, { x: 200, y: 280 });
|
||||
|
||||
const pageNote = await page.evaluate(() => {
|
||||
return document.querySelector('affine-note');
|
||||
});
|
||||
expect(pageNote).not.toBeNull();
|
||||
});
|
||||
|
||||
test(scoped`automatic identify url text`, async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'abc https://google.com ');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('ctrl+delete to delete one word forward', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'aaa bbb ccc');
|
||||
await pressArrowLeft(page, 8);
|
||||
await pressForwardDeleteWord(page);
|
||||
await assertText(page, 'aaa ccc');
|
||||
});
|
||||
|
||||
test('extended inline format', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'aaabbbaaa');
|
||||
|
||||
const { boldBtn, italicBtn, underlineBtn, strikeBtn, codeBtn } =
|
||||
getFormatBar(page);
|
||||
await setSelection(page, 0, 3, 0, 6);
|
||||
await boldBtn.click();
|
||||
await italicBtn.click();
|
||||
await underlineBtn.click();
|
||||
await strikeBtn.click();
|
||||
await codeBtn.click();
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
insert: 'bbb',
|
||||
attributes: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
underline: true,
|
||||
strike: true,
|
||||
code: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
]);
|
||||
|
||||
// aaa|bbbccc
|
||||
await setSelection(page, 2, 3, 2, 3);
|
||||
await captureHistory(page);
|
||||
await type(page, 'c');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aaac',
|
||||
},
|
||||
{
|
||||
insert: 'bbb',
|
||||
attributes: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
underline: true,
|
||||
strike: true,
|
||||
code: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
|
||||
// aaab|bbccc
|
||||
await setSelection(page, 2, 4, 2, 4);
|
||||
await type(page, 'c');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
insert: 'bcbb',
|
||||
attributes: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
underline: true,
|
||||
strike: true,
|
||||
code: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
|
||||
// aaab|b|bccc
|
||||
await setSelection(page, 2, 4, 2, 5);
|
||||
await type(page, 'c');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
insert: 'bcb',
|
||||
attributes: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
underline: true,
|
||||
strike: true,
|
||||
code: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
|
||||
// aaabbb|ccc
|
||||
await setSelection(page, 2, 6, 2, 6);
|
||||
await type(page, 'c');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
insert: 'bbb',
|
||||
attributes: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
underline: true,
|
||||
strike: true,
|
||||
code: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'c',
|
||||
attributes: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
underline: true,
|
||||
strike: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
]);
|
||||
});
|
||||
461
blocksuite/tests-legacy/e2e/bookmark.spec.ts
Normal file
461
blocksuite/tests-legacy/e2e/bookmark.spec.ts
Normal file
@@ -0,0 +1,461 @@
|
||||
import './utils/declare-test-window.js';
|
||||
|
||||
import type { BlockSnapshot } from '@blocksuite/store';
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
activeNoteInEdgeless,
|
||||
copyByKeyboard,
|
||||
dragBlockToPoint,
|
||||
enterPlaygroundRoom,
|
||||
expectConsoleMessage,
|
||||
focusRichText,
|
||||
getPageSnapshot,
|
||||
initEmptyEdgelessState,
|
||||
initEmptyParagraphState,
|
||||
pasteByKeyboard,
|
||||
pressArrowDown,
|
||||
pressArrowRight,
|
||||
pressArrowUp,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressShiftTab,
|
||||
pressTab,
|
||||
selectAllByKeyboard,
|
||||
setInlineRangeInSelectedRichText,
|
||||
SHORT_KEY,
|
||||
switchEditorMode,
|
||||
type,
|
||||
waitForInlineEditorStateUpdated,
|
||||
waitNextFrame,
|
||||
} from './utils/actions/index.js';
|
||||
import {
|
||||
assertAlmostEqual,
|
||||
assertBlockChildrenIds,
|
||||
assertBlockCount,
|
||||
assertBlockFlavour,
|
||||
assertBlockSelections,
|
||||
assertExists,
|
||||
assertParentBlockFlavour,
|
||||
assertRichTextInlineRange,
|
||||
} from './utils/asserts.js';
|
||||
import { ignoreSnapshotId } from './utils/ignore.js';
|
||||
import { scoped, test } from './utils/playwright.js';
|
||||
import { getEmbedCardToolbar } from './utils/query.js';
|
||||
|
||||
const LOCAL_HOST_URL = 'http://localhost';
|
||||
|
||||
const YOUTUBE_URL = 'https://www.youtube.com/watch?v=fakeid';
|
||||
|
||||
const FIGMA_URL = 'https://www.figma.com/design/JuXs6uOAICwf4I4tps0xKZ123';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.route(
|
||||
'https://affine-worker.toeverything.workers.dev/api/worker/link-preview',
|
||||
async route => {
|
||||
await route.fulfill({
|
||||
json: {},
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const createBookmarkBlockBySlashMenu = async (
|
||||
page: Page,
|
||||
url = LOCAL_HOST_URL
|
||||
) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await page.waitForTimeout(100);
|
||||
await type(page, '/link', 100);
|
||||
await pressEnter(page);
|
||||
await page.waitForTimeout(100);
|
||||
await type(page, url);
|
||||
await pressEnter(page);
|
||||
};
|
||||
|
||||
test(scoped`create bookmark by slash menu`, async ({ page }, testInfo) => {
|
||||
await createBookmarkBlockBySlashMenu(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test(scoped`covert bookmark block to link text`, async ({ page }, testInfo) => {
|
||||
await createBookmarkBlockBySlashMenu(page);
|
||||
const bookmark = page.locator('affine-bookmark');
|
||||
await bookmark.click();
|
||||
await page.waitForTimeout(100);
|
||||
await page.getByRole('button', { name: 'Switch view' }).click();
|
||||
await page.getByRole('button', { name: 'Inline view' }).click();
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`copy url to create bookmark in page mode`,
|
||||
async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, LOCAL_HOST_URL);
|
||||
await setInlineRangeInSelectedRichText(page, 0, LOCAL_HOST_URL.length);
|
||||
await copyByKeyboard(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '/link');
|
||||
await pressEnter(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+v`);
|
||||
await pressEnter(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test(
|
||||
scoped`copy url to create bookmark in edgeless mode`,
|
||||
async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const ids = await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, LOCAL_HOST_URL);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
await activeNoteInEdgeless(page, ids.noteId);
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
await pressArrowRight(page);
|
||||
await waitNextFrame(page);
|
||||
await type(page, '/link', 100);
|
||||
await pressEnter(page);
|
||||
await page.waitForTimeout(100);
|
||||
await waitNextFrame(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+v`);
|
||||
await pressEnter(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test.fixme(
|
||||
scoped`support dragging bookmark block directly`,
|
||||
async ({ page }, testInfo) => {
|
||||
await createBookmarkBlockBySlashMenu(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
const bookmark = page.locator('affine-bookmark');
|
||||
const rect = await bookmark.boundingBox();
|
||||
if (!rect) {
|
||||
throw new Error('image not found');
|
||||
}
|
||||
|
||||
// add new paragraph blocks
|
||||
await page.mouse.click(rect.x + 20, rect.y + rect.height + 20);
|
||||
await focusRichText(page);
|
||||
await type(page, '111');
|
||||
await page.waitForTimeout(200);
|
||||
await pressEnter(page);
|
||||
|
||||
await type(page, '222');
|
||||
await page.waitForTimeout(200);
|
||||
await pressEnter(page);
|
||||
|
||||
await type(page, '333');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.waitForTimeout(200);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_after_add_paragraph.json`
|
||||
);
|
||||
|
||||
// drag bookmark block
|
||||
await page.mouse.move(rect.x + 20, rect.y + 20);
|
||||
await page.mouse.down();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.mouse.move(rect.x + 40, rect.y + rect.height + 80, {
|
||||
steps: 5,
|
||||
});
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.mouse.up();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
const rects = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(rects).toHaveCount(1);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_after_drag.json`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test('press backspace after bookmark block can select bookmark block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await pressEnter(page);
|
||||
await pressArrowUp(page);
|
||||
await type(page, '/link');
|
||||
await pressEnter(page);
|
||||
await page.waitForTimeout(100);
|
||||
await type(page, LOCAL_HOST_URL);
|
||||
await pressEnter(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await assertBlockCount(page, 'paragraph', 1);
|
||||
await assertRichTextInlineRange(page, 0, 0);
|
||||
await pressBackspace(page);
|
||||
await assertBlockSelections(page, ['4']);
|
||||
await assertBlockCount(page, 'paragraph', 0);
|
||||
});
|
||||
|
||||
test.describe('embed card toolbar', () => {
|
||||
async function showEmbedCardToolbar(page: Page) {
|
||||
await createBookmarkBlockBySlashMenu(page);
|
||||
const bookmark = page.locator('affine-bookmark');
|
||||
await bookmark.click();
|
||||
await page.waitForTimeout(100);
|
||||
const { embedCardToolbar } = getEmbedCardToolbar(page);
|
||||
await expect(embedCardToolbar).toBeVisible();
|
||||
}
|
||||
|
||||
test('show toolbar when bookmark selected', async ({ page }) => {
|
||||
await showEmbedCardToolbar(page);
|
||||
});
|
||||
|
||||
test('copy bookmark url by copy button', async ({ page }, testInfo) => {
|
||||
await showEmbedCardToolbar(page);
|
||||
const { copyButton } = getEmbedCardToolbar(page);
|
||||
await copyButton.click();
|
||||
await page.mouse.click(600, 600);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await pasteByKeyboard(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('change card style', async ({ page }) => {
|
||||
await showEmbedCardToolbar(page);
|
||||
const bookmark = page.locator('affine-bookmark');
|
||||
const { openCardStyleMenu } = getEmbedCardToolbar(page);
|
||||
await openCardStyleMenu();
|
||||
const { cardStyleHorizontalButton, cardStyleListButton } =
|
||||
getEmbedCardToolbar(page);
|
||||
await cardStyleListButton.click();
|
||||
await waitNextFrame(page);
|
||||
const listStyleBookmarkBox = await bookmark.boundingBox();
|
||||
assertExists(listStyleBookmarkBox);
|
||||
assertAlmostEqual(listStyleBookmarkBox.width, 752, 2);
|
||||
assertAlmostEqual(listStyleBookmarkBox.height, 48, 2);
|
||||
|
||||
await openCardStyleMenu();
|
||||
await cardStyleHorizontalButton.click();
|
||||
await waitNextFrame(page);
|
||||
const horizontalStyleBookmarkBox = await bookmark.boundingBox();
|
||||
assertExists(horizontalStyleBookmarkBox);
|
||||
assertAlmostEqual(horizontalStyleBookmarkBox.width, 752, 2);
|
||||
assertAlmostEqual(horizontalStyleBookmarkBox.height, 116, 2);
|
||||
});
|
||||
});
|
||||
|
||||
test('indent bookmark block to paragraph', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await pressEnter(page);
|
||||
await type(page, '/link', 100);
|
||||
await pressEnter(page);
|
||||
await type(page, LOCAL_HOST_URL);
|
||||
await pressEnter(page);
|
||||
|
||||
await assertBlockChildrenIds(page, '1', ['2', '4']);
|
||||
await assertBlockFlavour(page, '1', 'affine:note');
|
||||
await assertBlockFlavour(page, '2', 'affine:paragraph');
|
||||
await assertBlockFlavour(page, '4', 'affine:bookmark');
|
||||
|
||||
await focusRichText(page);
|
||||
await pressArrowDown(page);
|
||||
await assertBlockSelections(page, ['4']);
|
||||
await pressTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['2']);
|
||||
await assertBlockChildrenIds(page, '2', ['4']);
|
||||
|
||||
await pressShiftTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['2', '4']);
|
||||
});
|
||||
|
||||
test('indent bookmark block to list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '- a');
|
||||
await pressEnter(page);
|
||||
await type(page, '/link', 100);
|
||||
await pressEnter(page);
|
||||
await type(page, LOCAL_HOST_URL);
|
||||
await pressEnter(page);
|
||||
|
||||
await assertBlockChildrenIds(page, '1', ['3', '5']);
|
||||
await assertBlockFlavour(page, '1', 'affine:note');
|
||||
await assertBlockFlavour(page, '3', 'affine:list');
|
||||
await assertBlockFlavour(page, '5', 'affine:bookmark');
|
||||
|
||||
await focusRichText(page);
|
||||
await pressArrowDown(page);
|
||||
await assertBlockSelections(page, ['5']);
|
||||
await pressTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['3']);
|
||||
await assertBlockChildrenIds(page, '3', ['5']);
|
||||
|
||||
await pressShiftTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['3', '5']);
|
||||
});
|
||||
|
||||
test('bookmark can be dragged from note to surface top level block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await page.waitForTimeout(100);
|
||||
await type(page, '/link', 100);
|
||||
await pressEnter(page);
|
||||
await page.waitForTimeout(100);
|
||||
await type(page, LOCAL_HOST_URL);
|
||||
await pressEnter(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await page.mouse.dblclick(450, 450);
|
||||
|
||||
await dragBlockToPoint(page, '4', { x: 200, y: 200 });
|
||||
|
||||
await waitNextFrame(page);
|
||||
await assertParentBlockFlavour(page, '4', 'affine:surface');
|
||||
});
|
||||
|
||||
test.describe('embed youtube card', () => {
|
||||
test(scoped`create youtube card by slash menu`, async ({ page }) => {
|
||||
expectConsoleMessage(page, /Unrecognized feature/, 'warning');
|
||||
expectConsoleMessage(page, /Failed to load resource/);
|
||||
await createBookmarkBlockBySlashMenu(page, YOUTUBE_URL);
|
||||
const snapshot = (await getPageSnapshot(page)) as BlockSnapshot;
|
||||
expect(ignoreSnapshotId(snapshot)).toMatchSnapshot('embed-youtube.json');
|
||||
});
|
||||
|
||||
test(scoped`change youtube card style`, async ({ page }) => {
|
||||
expectConsoleMessage(page, /Unrecognized feature/, 'warning');
|
||||
expectConsoleMessage(page, /Failed to load resource/);
|
||||
|
||||
await createBookmarkBlockBySlashMenu(page, YOUTUBE_URL);
|
||||
const youtube = page.locator('affine-embed-youtube-block');
|
||||
await youtube.click();
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
// change to card view
|
||||
const embedToolbar = page.locator('affine-embed-card-toolbar');
|
||||
await expect(embedToolbar).toBeVisible();
|
||||
const embedView = page.locator('editor-menu-button', {
|
||||
hasText: 'embed view',
|
||||
});
|
||||
await expect(embedView).toBeVisible();
|
||||
await embedView.click();
|
||||
const cardView = page.locator('editor-menu-action', {
|
||||
hasText: 'card view',
|
||||
});
|
||||
await expect(cardView).toBeVisible();
|
||||
await cardView.click();
|
||||
const snapshot = (await getPageSnapshot(page)) as BlockSnapshot;
|
||||
expect(ignoreSnapshotId(snapshot)).toMatchSnapshot(
|
||||
'horizontal-youtube.json'
|
||||
);
|
||||
|
||||
// change to embed view
|
||||
const bookmark = page.locator('affine-bookmark');
|
||||
await bookmark.click();
|
||||
await page.waitForTimeout(100);
|
||||
const cardView2 = page.locator('editor-icon-button', {
|
||||
hasText: 'card view',
|
||||
});
|
||||
await expect(cardView2).toBeVisible();
|
||||
await cardView2.click();
|
||||
const embedView2 = page.locator('editor-menu-action', {
|
||||
hasText: 'embed view',
|
||||
});
|
||||
await expect(embedView2).toBeVisible();
|
||||
await embedView2.click();
|
||||
const snapshot2 = (await getPageSnapshot(page)) as BlockSnapshot;
|
||||
expect(ignoreSnapshotId(snapshot2)).toMatchSnapshot('embed-youtube.json');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('embed figma card', () => {
|
||||
test(scoped`create figma card by slash menu`, async ({ page }) => {
|
||||
expectConsoleMessage(page, /Failed to load resource/);
|
||||
expectConsoleMessage(page, /Refused to frame/);
|
||||
await createBookmarkBlockBySlashMenu(page, FIGMA_URL);
|
||||
const snapshot = (await getPageSnapshot(page)) as BlockSnapshot;
|
||||
expect(ignoreSnapshotId(snapshot)).toMatchSnapshot('embed-figma.json');
|
||||
});
|
||||
|
||||
test(scoped`change figma card style`, async ({ page }) => {
|
||||
expectConsoleMessage(page, /Failed to load resource/);
|
||||
expectConsoleMessage(page, /Refused to frame/);
|
||||
expectConsoleMessage(page, /Running frontend commit/, 'log');
|
||||
await createBookmarkBlockBySlashMenu(page, FIGMA_URL);
|
||||
const youtube = page.locator('affine-embed-figma-block');
|
||||
await youtube.click();
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
// change to card view
|
||||
const embedToolbar = page.locator('affine-embed-card-toolbar');
|
||||
await expect(embedToolbar).toBeVisible();
|
||||
const embedView = page.locator('editor-menu-button', {
|
||||
hasText: 'embed view',
|
||||
});
|
||||
await expect(embedView).toBeVisible();
|
||||
await embedView.click();
|
||||
const cardView = page.locator('editor-menu-action', {
|
||||
hasText: 'card view',
|
||||
});
|
||||
await expect(cardView).toBeVisible();
|
||||
await cardView.click();
|
||||
const snapshot = (await getPageSnapshot(page)) as BlockSnapshot;
|
||||
expect(ignoreSnapshotId(snapshot)).toMatchSnapshot('horizontal-figma.json');
|
||||
|
||||
// change to embed view
|
||||
const bookmark = page.locator('affine-bookmark');
|
||||
await bookmark.click();
|
||||
await page.waitForTimeout(100);
|
||||
const cardView2 = page.locator('editor-icon-button', {
|
||||
hasText: 'card view',
|
||||
});
|
||||
await expect(cardView2).toBeVisible();
|
||||
await cardView2.click();
|
||||
const embedView2 = page.locator('editor-menu-action', {
|
||||
hasText: 'embed view',
|
||||
});
|
||||
await expect(embedView2).toBeVisible();
|
||||
await embedView2.click();
|
||||
const snapshot2 = (await getPageSnapshot(page)) as BlockSnapshot;
|
||||
expect(ignoreSnapshotId(snapshot2)).toMatchSnapshot('embed-figma.json');
|
||||
});
|
||||
});
|
||||
409
blocksuite/tests-legacy/e2e/clipboard/clipboard.spec.ts
Normal file
409
blocksuite/tests-legacy/e2e/clipboard/clipboard.spec.ts
Normal file
@@ -0,0 +1,409 @@
|
||||
import '../utils/declare-test-window.js';
|
||||
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
captureHistory,
|
||||
copyByKeyboard,
|
||||
dragBetweenCoords,
|
||||
dragOverTitle,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
focusTitle,
|
||||
getClipboardHTML,
|
||||
getClipboardSnapshot,
|
||||
getClipboardText,
|
||||
getCurrentEditorDocId,
|
||||
getEditorLocator,
|
||||
getPageSnapshot,
|
||||
initEmptyParagraphState,
|
||||
mockParseDocUrlService,
|
||||
pasteByKeyboard,
|
||||
pasteContent,
|
||||
pressEnter,
|
||||
pressShiftTab,
|
||||
pressTab,
|
||||
resetHistory,
|
||||
setInlineRangeInSelectedRichText,
|
||||
setSelection,
|
||||
SHORT_KEY,
|
||||
type,
|
||||
undoByClick,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockTypes,
|
||||
assertClipItems,
|
||||
assertExists,
|
||||
assertRichTexts,
|
||||
assertText,
|
||||
assertTitle,
|
||||
} from '../utils/asserts.js';
|
||||
import { scoped, test } from '../utils/playwright.js';
|
||||
|
||||
test(scoped`clipboard copy paste`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'test');
|
||||
await setInlineRangeInSelectedRichText(page, 0, 3);
|
||||
await waitNextFrame(page);
|
||||
await copyByKeyboard(page);
|
||||
await focusRichText(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+v`);
|
||||
await assertText(page, 'testtes');
|
||||
});
|
||||
|
||||
test(scoped`clipboard copy paste title`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusTitle(page);
|
||||
|
||||
await type(page, 'test');
|
||||
await dragOverTitle(page);
|
||||
await waitNextFrame(page);
|
||||
await copyByKeyboard(page);
|
||||
await focusTitle(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+v`);
|
||||
await assertTitle(page, 'testtest');
|
||||
});
|
||||
|
||||
test(scoped`clipboard paste html`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
// set up clipboard data using html
|
||||
const clipData = {
|
||||
'text/html': `<span>aaa</span><span>bbb</span><span>ccc</span><bdi>ddd</bdi>`,
|
||||
};
|
||||
await waitNextFrame(page);
|
||||
await page.evaluate(
|
||||
({ clipData }) => {
|
||||
const dT = new DataTransfer();
|
||||
const e = new ClipboardEvent('paste', { clipboardData: dT });
|
||||
Object.defineProperty(e, 'target', {
|
||||
writable: false,
|
||||
value: document,
|
||||
});
|
||||
e.clipboardData?.setData('text/html', clipData['text/html']);
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
{ clipData }
|
||||
);
|
||||
await assertText(page, 'aaabbbcccddd');
|
||||
});
|
||||
|
||||
test(scoped`split block when paste`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await resetHistory(page);
|
||||
|
||||
const clipData = {
|
||||
'text/plain': `# text
|
||||
# h1
|
||||
`,
|
||||
};
|
||||
await type(page, 'abc');
|
||||
await captureHistory(page);
|
||||
|
||||
await setInlineRangeInSelectedRichText(page, 1, 1);
|
||||
await pasteContent(page, clipData);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertRichTexts(page, ['atext', 'h1c']);
|
||||
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['abc']);
|
||||
|
||||
await type(page, 'aa');
|
||||
await pressEnter(page);
|
||||
await type(page, 'bb');
|
||||
const topLeft123 = await getEditorLocator(page)
|
||||
.locator('[data-block-id="2"] .inline-editor')
|
||||
.boundingBox();
|
||||
const bottomRight789 = await getEditorLocator(page)
|
||||
.locator('[data-block-id="4"] .inline-editor')
|
||||
.boundingBox();
|
||||
assertExists(topLeft123);
|
||||
assertExists(bottomRight789);
|
||||
await dragBetweenCoords(page, topLeft123, bottomRight789);
|
||||
|
||||
// FIXME see https://github.com/toeverything/blocksuite/pull/878
|
||||
// await pasteContent(page, clipData);
|
||||
// await assertRichTexts(page, ['aaa', 'bbc', 'text', 'h1']);
|
||||
});
|
||||
|
||||
test(scoped`copy clipItems format`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await captureHistory(page);
|
||||
|
||||
const clipData = `
|
||||
- aa
|
||||
- bb
|
||||
- cc
|
||||
- dd
|
||||
`;
|
||||
|
||||
await pasteContent(page, { 'text/plain': clipData });
|
||||
await page.waitForTimeout(100);
|
||||
await setSelection(page, 4, 1, 5, 1);
|
||||
assertClipItems(page, 'text/plain', 'bc');
|
||||
assertClipItems(page, 'text/html', '<ul><li>b<ul><li>c</li></ul></li></ul>');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test(scoped`copy partially selected text`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, '123 456 789');
|
||||
|
||||
// select 456
|
||||
await setInlineRangeInSelectedRichText(page, 4, 3);
|
||||
await copyByKeyboard(page);
|
||||
assertClipItems(page, 'text/plain', '456');
|
||||
|
||||
// move to line end
|
||||
await setInlineRangeInSelectedRichText(page, 11, 0);
|
||||
await pressEnter(page);
|
||||
await pasteByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertRichTexts(page, ['123 456 789', '456']);
|
||||
});
|
||||
|
||||
test(scoped`copy & paste outside editor`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await page.evaluate(() => {
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('id', 'input-test');
|
||||
input.value = '123';
|
||||
document.body.querySelector('#app')?.append(input);
|
||||
});
|
||||
await page.focus('#input-test');
|
||||
await page.dblclick('#input-test');
|
||||
await copyByKeyboard(page);
|
||||
await focusRichText(page);
|
||||
await pasteByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['123']);
|
||||
});
|
||||
|
||||
test('should keep first line format when pasted into a new line', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const clipData = `
|
||||
- [ ] aaa
|
||||
`;
|
||||
|
||||
await pasteContent(page, { 'text/plain': clipData });
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['aaa']);
|
||||
await assertBlockTypes(page, ['todo']);
|
||||
});
|
||||
|
||||
test(scoped`auto identify url`, async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
// set up clipboard data using html
|
||||
const clipData = {
|
||||
'text/plain': `test https://www.google.com`,
|
||||
};
|
||||
await waitNextFrame(page);
|
||||
await page.evaluate(
|
||||
({ clipData }) => {
|
||||
const dT = new DataTransfer();
|
||||
const e = new ClipboardEvent('paste', { clipboardData: dT });
|
||||
Object.defineProperty(e, 'target', {
|
||||
writable: false,
|
||||
value: document,
|
||||
});
|
||||
e.clipboardData?.setData('text/plain', clipData['text/plain']);
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
{ clipData }
|
||||
);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test(scoped`pasting internal url`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusTitle(page);
|
||||
await type(page, 'test page');
|
||||
|
||||
await focusRichText(page);
|
||||
const docId = await getCurrentEditorDocId(page);
|
||||
await mockParseDocUrlService(page, {
|
||||
'http://workspace/doc-id': docId,
|
||||
});
|
||||
await pasteContent(page, {
|
||||
'text/plain': 'http://workspace/doc-id',
|
||||
});
|
||||
await expect(page.locator('affine-reference')).toContainText('test page');
|
||||
});
|
||||
|
||||
test(scoped`pasting internal url with params`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusTitle(page);
|
||||
await type(page, 'test page');
|
||||
|
||||
await focusRichText(page);
|
||||
const docId = await getCurrentEditorDocId(page);
|
||||
await mockParseDocUrlService(page, {
|
||||
'http://workspace/doc-id?mode=page&blockIds=rL2_GXbtLU2SsJVfCSmh_': docId,
|
||||
});
|
||||
await pasteContent(page, {
|
||||
'text/plain':
|
||||
'http://workspace/doc-id?mode=page&blockIds=rL2_GXbtLU2SsJVfCSmh_',
|
||||
});
|
||||
await expect(page.locator('affine-reference')).toContainText('test page');
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`pasting an external URL from clipboard to automatically creating a link from selection`,
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusTitle(page);
|
||||
await type(page, 'test page');
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, 'title alias');
|
||||
await setSelection(page, 1, 6, 1, 11);
|
||||
|
||||
await pasteContent(page, {
|
||||
'text/plain': 'https://affine.pro/',
|
||||
});
|
||||
await expect(page.locator('affine-link')).toContainText('alias');
|
||||
}
|
||||
);
|
||||
|
||||
test(
|
||||
scoped`pasting an internal URL from clipboard to automatically creating a link from selection`,
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusTitle(page);
|
||||
await type(page, 'test page');
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, 'title alias');
|
||||
await setSelection(page, 1, 6, 1, 11);
|
||||
|
||||
const docId = await getCurrentEditorDocId(page);
|
||||
await mockParseDocUrlService(page, {
|
||||
'http://workspace/doc-id': docId,
|
||||
});
|
||||
await pasteContent(page, {
|
||||
'text/plain': 'http://workspace/doc-id',
|
||||
});
|
||||
await expect(page.locator('affine-reference')).toContainText('alias');
|
||||
}
|
||||
);
|
||||
|
||||
test(scoped`paste parent block`, async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/3153',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'This is parent');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.keyboard.press('Tab');
|
||||
await type(page, 'This is child 1');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.keyboard.press('Tab');
|
||||
await type(page, 'This is child 2');
|
||||
await setInlineRangeInSelectedRichText(page, 0, 3);
|
||||
await copyByKeyboard(page);
|
||||
await focusRichText(page, 2);
|
||||
await page.keyboard.press(`${SHORT_KEY}+v`);
|
||||
await assertRichTexts(page, [
|
||||
'This is parent',
|
||||
'This is child 1',
|
||||
'This is child 2Thi',
|
||||
]);
|
||||
});
|
||||
|
||||
test(scoped`clipboard copy multi selection`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'abc');
|
||||
await pressEnter(page);
|
||||
await type(page, 'def');
|
||||
await setSelection(page, 2, 1, 3, 1);
|
||||
await waitNextFrame(page);
|
||||
await copyByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await focusRichText(page, 1);
|
||||
await pasteByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'cursor');
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['abc', 'defbc', 'dcursor']);
|
||||
});
|
||||
|
||||
test(scoped`clipboard copy nested items`, async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'abc');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'def');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'ghi');
|
||||
await pressEnter(page);
|
||||
await pressShiftTab(page);
|
||||
await pressShiftTab(page);
|
||||
await type(page, 'jkl');
|
||||
await setSelection(page, 2, 1, 3, 1);
|
||||
await waitNextFrame(page);
|
||||
await copyByKeyboard(page);
|
||||
|
||||
const text = await getClipboardText(page);
|
||||
const html = await getClipboardHTML(page);
|
||||
const snapshot = await getClipboardSnapshot(page);
|
||||
expect(text).toMatchSnapshot(`${testInfo.title}-clipboard.md`);
|
||||
expect(JSON.stringify(snapshot.snapshot.content, null, 2)).toMatchSnapshot(
|
||||
`${testInfo.title}-clipboard.json`
|
||||
);
|
||||
expect(html).toMatchSnapshot(`${testInfo.title}-clipboard.html`);
|
||||
|
||||
await setSelection(page, 4, 1, 5, 1);
|
||||
await waitNextFrame(page);
|
||||
await copyByKeyboard(page);
|
||||
|
||||
const text2 = await getClipboardText(page);
|
||||
const html2 = await getClipboardHTML(page);
|
||||
const snapshot2 = await getClipboardSnapshot(page);
|
||||
expect(text2).toMatchSnapshot(`${testInfo.title}-clipboard2.md`);
|
||||
expect(JSON.stringify(snapshot2.snapshot.content, null, 2)).toMatchSnapshot(
|
||||
`${testInfo.title}-clipboard2.json`
|
||||
);
|
||||
expect(html2).toMatchSnapshot(`${testInfo.title}-clipboard2.html`);
|
||||
});
|
||||
58
blocksuite/tests-legacy/e2e/clipboard/image.spec.ts
Normal file
58
blocksuite/tests-legacy/e2e/clipboard/image.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyParagraphState,
|
||||
pasteContent,
|
||||
pressArrowDown,
|
||||
pressArrowUp,
|
||||
pressEscape,
|
||||
waitEmbedLoaded,
|
||||
} from '../utils/actions/index.js';
|
||||
import { assertRichImage, assertText } from '../utils/asserts.js';
|
||||
import { scoped, test } from '../utils/playwright.js';
|
||||
|
||||
test(
|
||||
scoped`clipboard paste end with image, the cursor should be controlled by up/down keys`,
|
||||
async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/3639',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
// set up clipboard data using html
|
||||
const clipData = {
|
||||
'text/html': `<p>Lorem Ipsum placeholder text.</p>
|
||||
<figure ><img src='https://placehold.co/600x400' /></figure>
|
||||
`,
|
||||
};
|
||||
await page.evaluate(
|
||||
({ clipData }) => {
|
||||
const dT = new DataTransfer();
|
||||
const e = new ClipboardEvent('paste', { clipboardData: dT });
|
||||
Object.defineProperty(e, 'target', {
|
||||
writable: false,
|
||||
value: document,
|
||||
});
|
||||
e.clipboardData?.setData('text/html', clipData['text/html']);
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
{ clipData }
|
||||
);
|
||||
const str = 'Lorem Ipsum placeholder text.';
|
||||
await waitEmbedLoaded(page);
|
||||
await assertRichImage(page, 1);
|
||||
await pressEscape(page);
|
||||
await pressArrowUp(page, 1);
|
||||
await pasteContent(page, clipData);
|
||||
await assertRichImage(page, 2);
|
||||
await assertText(page, str + str);
|
||||
await pressArrowDown(page, 1);
|
||||
await pressEscape(page);
|
||||
await pasteContent(page, clipData);
|
||||
await assertRichImage(page, 3);
|
||||
await assertText(page, 'Lorem Ipsum placeholder text.', 1);
|
||||
}
|
||||
);
|
||||
645
blocksuite/tests-legacy/e2e/clipboard/list.spec.ts
Normal file
645
blocksuite/tests-legacy/e2e/clipboard/list.spec.ts
Normal file
@@ -0,0 +1,645 @@
|
||||
import type { BlockSnapshot } from '@blocksuite/store';
|
||||
import { expect } from '@playwright/test';
|
||||
import { lightThemeV2 } from '@toeverything/theme/v2';
|
||||
|
||||
import { initDatabaseColumn } from '../database/actions.js';
|
||||
import {
|
||||
activeNoteInEdgeless,
|
||||
changeEdgelessNoteBackground,
|
||||
copyByKeyboard,
|
||||
createShapeElement,
|
||||
cutByKeyboard,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getAllNoteIds,
|
||||
getClipboardHTML,
|
||||
getClipboardSnapshot,
|
||||
getClipboardText,
|
||||
getEdgelessSelectedRectModel,
|
||||
getInlineSelectionIndex,
|
||||
getInlineSelectionText,
|
||||
getPageSnapshot,
|
||||
getRichTextBoundingBox,
|
||||
initDatabaseDynamicRowWithData,
|
||||
initEmptyDatabaseWithParagraphState,
|
||||
initEmptyEdgelessState,
|
||||
initEmptyParagraphState,
|
||||
initThreeParagraphs,
|
||||
pasteByKeyboard,
|
||||
pasteContent,
|
||||
pressArrowLeft,
|
||||
pressArrowRight,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
pressShiftTab,
|
||||
pressSpace,
|
||||
pressTab,
|
||||
selectAllByKeyboard,
|
||||
selectNoteInEdgeless,
|
||||
setInlineRangeInSelectedRichText,
|
||||
SHORT_KEY,
|
||||
switchEditorMode,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
type,
|
||||
undoByKeyboard,
|
||||
waitForInlineEditorStateUpdated,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockTypes,
|
||||
assertEdgelessNoteBackground,
|
||||
assertEdgelessSelectedModelRect,
|
||||
assertExists,
|
||||
assertRichTextModelType,
|
||||
assertRichTexts,
|
||||
assertText,
|
||||
} from '../utils/asserts.js';
|
||||
import { scoped, test } from '../utils/playwright.js';
|
||||
|
||||
test('paste a non-nested list to a non-nested list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const clipData = {
|
||||
'text/plain': `
|
||||
- a
|
||||
`,
|
||||
};
|
||||
await type(page, '-');
|
||||
await pressSpace(page);
|
||||
await type(page, '123');
|
||||
await page.keyboard.press('Control+ArrowLeft');
|
||||
|
||||
// paste on start
|
||||
await waitNextFrame(page);
|
||||
await pasteContent(page, clipData);
|
||||
await pressArrowLeft(page);
|
||||
await assertRichTexts(page, ['a123']);
|
||||
|
||||
// paste in middle
|
||||
await pressArrowRight(page, 2);
|
||||
await pasteContent(page, clipData);
|
||||
await pressArrowRight(page);
|
||||
await assertRichTexts(page, ['a1a23']);
|
||||
|
||||
// paste on end
|
||||
await pressArrowRight(page);
|
||||
await pasteContent(page, clipData);
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['a1a23a']);
|
||||
|
||||
await assertBlockTypes(page, ['bulleted']);
|
||||
});
|
||||
|
||||
test('copy a nested list by clicking button, the clipboard data should be complete', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const clipData = {
|
||||
'text/plain': `
|
||||
- aaa
|
||||
- bbb
|
||||
- ccc
|
||||
`,
|
||||
};
|
||||
await pasteContent(page, clipData);
|
||||
|
||||
const rootListBound = await page.locator('affine-list').first().boundingBox();
|
||||
assertExists(rootListBound);
|
||||
|
||||
// use drag element to test.
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: rootListBound.x + 1, y: rootListBound.y - 1 },
|
||||
{ x: rootListBound.x + 1, y: rootListBound.y + rootListBound.height - 1 }
|
||||
);
|
||||
await copyByKeyboard(page);
|
||||
|
||||
const text = await getClipboardText(page);
|
||||
const html = await getClipboardHTML(page);
|
||||
const snapshot = await getClipboardSnapshot(page);
|
||||
expect(text).toMatchSnapshot(`${testInfo.title}-clipboard.md`);
|
||||
expect(JSON.stringify(snapshot.snapshot.content, null, 2)).toMatchSnapshot(
|
||||
`${testInfo.title}-clipboard.json`
|
||||
);
|
||||
expect(html).toMatchSnapshot(`${testInfo.title}-clipboard.html`);
|
||||
});
|
||||
|
||||
test('paste a nested list to a nested list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const clipData = {
|
||||
'text/plain': `
|
||||
- aaa
|
||||
- bbb
|
||||
- ccc
|
||||
`,
|
||||
};
|
||||
await pasteContent(page, clipData);
|
||||
await focusRichText(page, 1);
|
||||
|
||||
// paste on start
|
||||
await page.keyboard.press('Control+ArrowLeft');
|
||||
|
||||
/**
|
||||
* - aaa
|
||||
* - |bbb
|
||||
* - ccc
|
||||
*/
|
||||
await pasteContent(page, clipData);
|
||||
/**
|
||||
* - aaa
|
||||
* - aaa
|
||||
* - bbb
|
||||
* - ccc|bbb
|
||||
* -ccc
|
||||
*/
|
||||
|
||||
await assertRichTexts(page, ['aaa', 'aaa', 'bbb', 'cccbbb', 'ccc']);
|
||||
expect(await getInlineSelectionText(page)).toEqual('cccbbb');
|
||||
expect(await getInlineSelectionIndex(page)).toEqual(3);
|
||||
|
||||
// paste in middle
|
||||
await undoByKeyboard(page);
|
||||
await pressArrowRight(page);
|
||||
|
||||
/**
|
||||
* - aaa
|
||||
* - b|bb
|
||||
* - ccc
|
||||
*/
|
||||
await pasteContent(page, clipData);
|
||||
/**
|
||||
* - aaa
|
||||
* - baaa
|
||||
* - bbb
|
||||
* - ccc|bb
|
||||
* - ccc
|
||||
*/
|
||||
|
||||
await assertRichTexts(page, ['aaa', 'baaa', 'bbb', 'cccbb', 'ccc']);
|
||||
expect(await getInlineSelectionText(page)).toEqual('cccbb');
|
||||
expect(await getInlineSelectionIndex(page)).toEqual(3);
|
||||
|
||||
// paste on end
|
||||
await undoByKeyboard(page);
|
||||
await page.keyboard.press('Control+ArrowRight');
|
||||
|
||||
/**
|
||||
* - aaa
|
||||
* - bbb|
|
||||
* - ccc
|
||||
*/
|
||||
await pasteContent(page, clipData);
|
||||
/**
|
||||
* - aaa
|
||||
* - bbbaaa
|
||||
* - bbb
|
||||
* - ccc|
|
||||
* - ccc
|
||||
*/
|
||||
|
||||
await assertRichTexts(page, ['aaa', 'bbbaaa', 'bbb', 'ccc', 'ccc']);
|
||||
expect(await getInlineSelectionText(page)).toEqual('ccc');
|
||||
expect(await getInlineSelectionIndex(page)).toEqual(3);
|
||||
});
|
||||
|
||||
test('paste nested lists to a nested list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const clipData = {
|
||||
'text/plain': `
|
||||
- aaa
|
||||
- bbb
|
||||
- ccc
|
||||
`,
|
||||
};
|
||||
await pasteContent(page, clipData);
|
||||
await focusRichText(page, 1);
|
||||
|
||||
const clipData2 = {
|
||||
'text/plain': `
|
||||
- 111
|
||||
- 222
|
||||
- 111
|
||||
- 222
|
||||
`,
|
||||
};
|
||||
|
||||
// paste on start
|
||||
await page.keyboard.press('Control+ArrowLeft');
|
||||
|
||||
/**
|
||||
* - aaa
|
||||
* - |bbb
|
||||
* - ccc
|
||||
*/
|
||||
await pasteContent(page, clipData2);
|
||||
/**
|
||||
* - aaa
|
||||
* - 111
|
||||
* - 222
|
||||
* - 111
|
||||
* - 222|bbb
|
||||
* - ccc
|
||||
*/
|
||||
|
||||
await assertRichTexts(page, ['aaa', '111', '222', '111', '222bbb', 'ccc']);
|
||||
expect(await getInlineSelectionText(page)).toEqual('222bbb');
|
||||
expect(await getInlineSelectionIndex(page)).toEqual(3);
|
||||
|
||||
// paste in middle
|
||||
await undoByKeyboard(page);
|
||||
await pressArrowRight(page);
|
||||
|
||||
/**
|
||||
* - aaa
|
||||
* - b|bb
|
||||
* - ccc
|
||||
*/
|
||||
await pasteContent(page, clipData2);
|
||||
/**
|
||||
* - aaa
|
||||
* - b111
|
||||
* - 222
|
||||
* - 111
|
||||
* - 222|bb
|
||||
* - ccc
|
||||
*/
|
||||
|
||||
await assertRichTexts(page, ['aaa', 'b111', '222', '111', '222bb', 'ccc']);
|
||||
expect(await getInlineSelectionText(page)).toEqual('222bb');
|
||||
expect(await getInlineSelectionIndex(page)).toEqual(3);
|
||||
|
||||
// paste on end
|
||||
await undoByKeyboard(page);
|
||||
await page.keyboard.press('Control+ArrowRight');
|
||||
|
||||
/**
|
||||
* - aaa
|
||||
* - bbb|
|
||||
* - ccc
|
||||
*/
|
||||
await pasteContent(page, clipData2);
|
||||
/**
|
||||
* - aaa
|
||||
* - bbb111
|
||||
* - 222
|
||||
* - 111
|
||||
* - 222|
|
||||
* - ccc
|
||||
*/
|
||||
|
||||
await assertRichTexts(page, ['aaa', 'bbb111', '222', '111', '222', 'ccc']);
|
||||
expect(await getInlineSelectionText(page)).toEqual('222');
|
||||
expect(await getInlineSelectionIndex(page)).toEqual(3);
|
||||
});
|
||||
|
||||
test('paste non-nested lists to a nested list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const clipData = {
|
||||
'text/plain': `
|
||||
- aaa
|
||||
- bbb
|
||||
`,
|
||||
};
|
||||
await pasteContent(page, clipData);
|
||||
await focusRichText(page, 0);
|
||||
|
||||
const clipData2 = {
|
||||
'text/plain': `
|
||||
- 123
|
||||
- 456
|
||||
`,
|
||||
};
|
||||
|
||||
// paste on start
|
||||
await page.keyboard.press('Control+ArrowLeft');
|
||||
|
||||
/**
|
||||
* - |aaa
|
||||
* - bbb
|
||||
*/
|
||||
await pasteContent(page, clipData2);
|
||||
/**
|
||||
* - 123
|
||||
* - 456|aaa
|
||||
* - bbb
|
||||
*/
|
||||
|
||||
await assertRichTexts(page, ['123', '456aaa', 'bbb']);
|
||||
expect(await getInlineSelectionText(page)).toEqual('456aaa');
|
||||
expect(await getInlineSelectionIndex(page)).toEqual(3);
|
||||
});
|
||||
|
||||
test(scoped`cut should work for multi-block selection`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'a');
|
||||
await pressEnter(page);
|
||||
await type(page, 'b');
|
||||
await pressEnter(page);
|
||||
await type(page, 'c');
|
||||
await selectAllByKeyboard(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await cutByKeyboard(page);
|
||||
await page.locator('.affine-page-viewport').click();
|
||||
await waitNextFrame(page);
|
||||
await assertText(page, '');
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`pasting into empty list should not convert the list into paragraph`,
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'test');
|
||||
await setInlineRangeInSelectedRichText(page, 0, 4);
|
||||
await copyByKeyboard(page);
|
||||
await type(page, '- ');
|
||||
await page.keyboard.press(`${SHORT_KEY}+v`);
|
||||
await assertRichTexts(page, ['test']);
|
||||
await assertRichTextModelType(page, 'bulleted');
|
||||
}
|
||||
);
|
||||
|
||||
test('cut will delete all content, and copy will reappear content', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '-');
|
||||
await pressSpace(page);
|
||||
await type(page, '1');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, '2');
|
||||
await pressEnter(page);
|
||||
await type(page, '3');
|
||||
await pressEnter(page);
|
||||
await pressShiftTab(page);
|
||||
await type(page, '4');
|
||||
|
||||
const box123 = await getRichTextBoundingBox(page, '1');
|
||||
const inside123 = { x: box123.left + 1, y: box123.top + 1 };
|
||||
|
||||
const box789 = await getRichTextBoundingBox(page, '6');
|
||||
const inside789 = { x: box789.right - 1, y: box789.bottom - 1 };
|
||||
// from top to bottom
|
||||
await dragBetweenCoords(page, inside123, inside789);
|
||||
|
||||
await cutByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_after-cut.json`
|
||||
);
|
||||
await waitNextFrame(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await pasteByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_after-paste.json`
|
||||
);
|
||||
});
|
||||
|
||||
test(scoped`should copy and paste of database work`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseWithParagraphState(page);
|
||||
|
||||
// init database columns and rows
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, 'abc', true);
|
||||
await pressEscape(page);
|
||||
await focusRichText(page, 1);
|
||||
await selectAllByKeyboard(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
await pressEnter(page);
|
||||
await pasteByKeyboard(page);
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
let pageJson = await getPageSnapshot(page, false);
|
||||
let note = (pageJson as BlockSnapshot).children[0];
|
||||
const database = note.children[0];
|
||||
expect(database.flavour).toBe('affine:database');
|
||||
|
||||
await undoByKeyboard(page);
|
||||
|
||||
pageJson = await getPageSnapshot(page, false);
|
||||
note = (pageJson as BlockSnapshot).children[0];
|
||||
const db = note.children.find(child => child.flavour === 'affine:database');
|
||||
expect(db).toBeDefined();
|
||||
});
|
||||
|
||||
test(`copy canvas element and text note in edgeless mode`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await initThreeParagraphs(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100]);
|
||||
await selectAllByKeyboard(page);
|
||||
const bound = await getEdgelessSelectedRectModel(page);
|
||||
await copyByKeyboard(page);
|
||||
const coord = await toViewCoord(page, [
|
||||
bound[0] + bound[2] / 2,
|
||||
bound[1] + bound[3] / 2 + 200,
|
||||
]);
|
||||
await page.mouse.move(coord[0], coord[1]);
|
||||
await page.waitForTimeout(300);
|
||||
await pasteByKeyboard(page, false);
|
||||
bound[1] = bound[1] + 200;
|
||||
await assertEdgelessSelectedModelRect(page, bound);
|
||||
});
|
||||
|
||||
test(scoped`copy when text note active in edgeless`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const ids = await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '1234');
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
await activeNoteInEdgeless(page, ids.noteId);
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
await setInlineRangeInSelectedRichText(page, 0, 4);
|
||||
await copyByKeyboard(page);
|
||||
await pressArrowRight(page);
|
||||
await type(page, '555');
|
||||
await pasteByKeyboard(page, false);
|
||||
await assertText(page, '12345551234');
|
||||
});
|
||||
|
||||
test(scoped`paste note block with background`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const ids = await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '1234');
|
||||
|
||||
await switchEditorMode(page);
|
||||
await selectNoteInEdgeless(page, ids.noteId);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeNoteColor');
|
||||
await changeEdgelessNoteBackground(page, 'White');
|
||||
await assertEdgelessNoteBackground(
|
||||
page,
|
||||
ids.noteId,
|
||||
lightThemeV2['edgeless/note/white']
|
||||
);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
|
||||
await page.mouse.move(0, 0);
|
||||
await pasteByKeyboard(page, false);
|
||||
const noteIds = await getAllNoteIds(page);
|
||||
for (const noteId of noteIds) {
|
||||
await assertEdgelessNoteBackground(
|
||||
page,
|
||||
noteId,
|
||||
lightThemeV2['edgeless/note/white']
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test(scoped`copy and paste to selection block selection`, async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2265',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '1234');
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
await pressArrowRight(page);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['12341234']);
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`should keep paragraph block's type when pasting at the start of empty paragraph block except type text`,
|
||||
async ({ page }, testInfo) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2336',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '>');
|
||||
await page.keyboard.press('Space', { delay: 50 });
|
||||
|
||||
await page.evaluate(() => {
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('id', 'input-test');
|
||||
input.value = '123';
|
||||
document.body.querySelector('#app')?.append(input);
|
||||
});
|
||||
await page.focus('#input-test');
|
||||
await page.dblclick('#input-test');
|
||||
await copyByKeyboard(page);
|
||||
await focusRichText(page);
|
||||
await pasteByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_after-paste-1.json`
|
||||
);
|
||||
|
||||
await pressEnter(page);
|
||||
await waitNextFrame(page);
|
||||
await pressEnter(page);
|
||||
await waitNextFrame(page);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_after-paste-2.json`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test(scoped`paste from FeiShu list format`, async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2438',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
// set up clipboard data using html
|
||||
const clipData = {
|
||||
'text/html': `<div><li><div><span>aaaa</span></div></li></div>`,
|
||||
};
|
||||
await waitNextFrame(page);
|
||||
await page.evaluate(
|
||||
({ clipData }) => {
|
||||
const dT = new DataTransfer();
|
||||
const e = new ClipboardEvent('paste', { clipboardData: dT });
|
||||
Object.defineProperty(e, 'target', {
|
||||
writable: false,
|
||||
value: document,
|
||||
});
|
||||
e.clipboardData?.setData('text/html', clipData['text/html']);
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
{ clipData }
|
||||
);
|
||||
await assertText(page, 'aaaa');
|
||||
await assertBlockTypes(page, ['bulleted']);
|
||||
});
|
||||
|
||||
test(scoped`paste in list format`, async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2281',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, '- test');
|
||||
await focusRichText(page);
|
||||
|
||||
const clipData = {
|
||||
'text/html': `<ul><li>111<ul><li>222</li></ul></li></ul>`,
|
||||
};
|
||||
await waitNextFrame(page);
|
||||
await page.evaluate(
|
||||
({ clipData }) => {
|
||||
const dT = new DataTransfer();
|
||||
const e = new ClipboardEvent('paste', { clipboardData: dT });
|
||||
Object.defineProperty(e, 'target', {
|
||||
writable: false,
|
||||
value: document,
|
||||
});
|
||||
e.clipboardData?.setData('text/html', clipData['text/html']);
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
{ clipData }
|
||||
);
|
||||
await assertRichTexts(page, ['test111', '222']);
|
||||
});
|
||||
170
blocksuite/tests-legacy/e2e/clipboard/markdown.spec.ts
Normal file
170
blocksuite/tests-legacy/e2e/clipboard/markdown.spec.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyParagraphState,
|
||||
pasteContent,
|
||||
resetHistory,
|
||||
undoByClick,
|
||||
waitEmbedLoaded,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockTypes,
|
||||
assertRichImage,
|
||||
assertRichTexts,
|
||||
assertTextFormats,
|
||||
} from '../utils/asserts.js';
|
||||
import { scoped, test } from '../utils/playwright.js';
|
||||
|
||||
test(scoped`markdown format parse`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await resetHistory(page);
|
||||
|
||||
let clipData = {
|
||||
'text/plain': `# h1
|
||||
|
||||
## h2
|
||||
|
||||
### h3
|
||||
|
||||
#### h4
|
||||
|
||||
##### h5
|
||||
|
||||
###### h6
|
||||
|
||||
- [ ] todo
|
||||
|
||||
- [ ] todo
|
||||
|
||||
- [x] todo
|
||||
|
||||
* bulleted
|
||||
|
||||
- bulleted
|
||||
|
||||
1. numbered
|
||||
|
||||
> quote
|
||||
`,
|
||||
};
|
||||
await waitNextFrame(page);
|
||||
await pasteContent(page, clipData);
|
||||
await page.waitForTimeout(200);
|
||||
await assertBlockTypes(page, [
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'todo',
|
||||
'todo',
|
||||
'todo',
|
||||
'bulleted',
|
||||
'bulleted',
|
||||
'numbered',
|
||||
'quote',
|
||||
]);
|
||||
await assertRichTexts(page, [
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'todo',
|
||||
'todo',
|
||||
'todo',
|
||||
'bulleted',
|
||||
'bulleted',
|
||||
'numbered',
|
||||
'quote',
|
||||
]);
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
await focusRichText(page);
|
||||
|
||||
clipData = {
|
||||
'text/plain': `# ***bolditalic***
|
||||
# **bold**
|
||||
|
||||
*italic*
|
||||
|
||||
~~strikethrough~~
|
||||
|
||||
[link](linktest)
|
||||
|
||||
\`code\`
|
||||
`,
|
||||
};
|
||||
await waitNextFrame(page);
|
||||
await pasteContent(page, clipData);
|
||||
await page.waitForTimeout(200);
|
||||
await assertTextFormats(page, [
|
||||
{ bold: true, italic: true },
|
||||
{ bold: true },
|
||||
{ italic: true },
|
||||
{ strike: true },
|
||||
{ link: 'linktest' },
|
||||
{ code: true },
|
||||
]);
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test(scoped`import markdown`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await resetHistory(page);
|
||||
const clipData = `# text
|
||||
# h1
|
||||
`;
|
||||
await pasteContent(page, { 'text/plain': clipData });
|
||||
await page.waitForTimeout(100);
|
||||
await assertRichTexts(page, ['text', 'h1']);
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`clipboard paste HTML containing markdown syntax code and image `,
|
||||
async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2855',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
// set up clipboard data using html
|
||||
const clipData = {
|
||||
'text/html': `<p>符合 Markdown 格式的 URL 放到笔记中,此时需要的格式如下:</p>
|
||||
<pre><code>md [任务管理这件事 - 少数派](https://sspai.com/post/61092)</code></pre>
|
||||
<p>(将一段文字包裹在<code >[[]]</code>中)此时需要的格式如下:</p>
|
||||
<figure ><img src="https://placehold.co/600x400"></figure>
|
||||
<p>上图中,当我们处在 Obsidian 的「预览模式」时,点击这个「双向链接」</p>
|
||||
`,
|
||||
};
|
||||
await page.evaluate(
|
||||
({ clipData }) => {
|
||||
const dT = new DataTransfer();
|
||||
const e = new ClipboardEvent('paste', { clipboardData: dT });
|
||||
Object.defineProperty(e, 'target', {
|
||||
writable: false,
|
||||
value: document,
|
||||
});
|
||||
e.clipboardData?.setData('text/html', clipData['text/html']);
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
{ clipData }
|
||||
);
|
||||
await waitEmbedLoaded(page);
|
||||
// await page.waitForTimeout(500);
|
||||
await assertRichImage(page, 1);
|
||||
}
|
||||
);
|
||||
149
blocksuite/tests-legacy/e2e/code/copy-paste.spec.ts
Normal file
149
blocksuite/tests-legacy/e2e/code/copy-paste.spec.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
copyByKeyboard,
|
||||
pasteByKeyboard,
|
||||
pressArrowLeft,
|
||||
pressEnter,
|
||||
pressEnterWithShortkey,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getInlineSelectionText,
|
||||
getPageSnapshot,
|
||||
initEmptyCodeBlockState,
|
||||
setSelection,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { assertRichTextInlineRange } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import { getCodeBlock } from './utils.js';
|
||||
|
||||
test('keyboard selection and copy paste', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'use');
|
||||
await page.keyboard.down('Shift');
|
||||
await pressArrowLeft(page, 'use'.length);
|
||||
await page.keyboard.up('Shift');
|
||||
await copyByKeyboard(page);
|
||||
await pressArrowLeft(page, 1);
|
||||
await pasteByKeyboard(page);
|
||||
|
||||
const content = await getInlineSelectionText(page);
|
||||
expect(content).toBe('useuse');
|
||||
|
||||
await assertRichTextInlineRange(page, 0, 3, 0);
|
||||
});
|
||||
|
||||
test('paste with more than one continuous breakline should remain in code block, ', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.setContent(`<div contenteditable>use super::*;
|
||||
use fern::{
|
||||
colors::{Color, ColoredLevelConfig},
|
||||
Dispatch,
|
||||
};
|
||||
<br><br>
|
||||
#[inline]</div>`);
|
||||
await page.focus('div');
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
await pasteByKeyboard(page);
|
||||
|
||||
const locator = page.locator('affine-paragraph');
|
||||
await expect(locator).toBeHidden();
|
||||
});
|
||||
|
||||
test('drag copy paste', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'use');
|
||||
|
||||
await setSelection(page, 2, 0, 2, 3);
|
||||
await copyByKeyboard(page);
|
||||
await pressArrowLeft(page);
|
||||
await pasteByKeyboard(page);
|
||||
|
||||
const content = await getInlineSelectionText(page);
|
||||
expect(content).toBe('useuse');
|
||||
|
||||
await assertRichTextInlineRange(page, 0, 3, 0);
|
||||
});
|
||||
|
||||
test.skip('use keyboard copy inside code block copy', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'use');
|
||||
await page.keyboard.down('Shift');
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
||||
for (let i = 0; i < 'use'.length; i++) {
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
}
|
||||
await page.keyboard.up('Shift');
|
||||
await copyByKeyboard(page);
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await pressEnter(page);
|
||||
await pressEnter(page);
|
||||
await pasteByKeyboard(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_pasted.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('code block has content, click code block copy menu, copy whole code block', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page, { language: 'javascript' });
|
||||
await focusRichText(page);
|
||||
await page.keyboard.type('use');
|
||||
await pressEnterWithShortkey(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
|
||||
await expect(codeBlockController.copyButton).toBeVisible();
|
||||
await codeBlockController.copyButton.click();
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await pasteByKeyboard(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_pasted.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('code block is empty, click code block copy menu, copy the empty code block', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page, { language: 'javascript' });
|
||||
await focusRichText(page);
|
||||
|
||||
await pressEnterWithShortkey(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await expect(codeBlockController.copyButton).toBeVisible();
|
||||
await codeBlockController.copyButton.click();
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await pasteByKeyboard(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_pasted.json`
|
||||
);
|
||||
});
|
||||
604
blocksuite/tests-legacy/e2e/code/crud.spec.ts
Normal file
604
blocksuite/tests-legacy/e2e/code/crud.spec.ts
Normal file
@@ -0,0 +1,604 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { updateBlockType } from '../utils/actions/block.js';
|
||||
import { dragBetweenIndices } from '../utils/actions/drag.js';
|
||||
import {
|
||||
createCodeBlock,
|
||||
pressArrowLeft,
|
||||
pressArrowUp,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
pressShiftTab,
|
||||
pressTab,
|
||||
redoByKeyboard,
|
||||
type,
|
||||
undoByKeyboard,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
focusRichTextEnd,
|
||||
getPageSnapshot,
|
||||
initEmptyCodeBlockState,
|
||||
initEmptyParagraphState,
|
||||
setSelection,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/misc.js';
|
||||
import {
|
||||
assertBlockCount,
|
||||
assertRichTexts,
|
||||
assertTitle,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import { getFormatBar } from '../utils/query.js';
|
||||
import { getCodeBlock } from './utils.js';
|
||||
|
||||
test('use debug menu can create code block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await updateBlockType(page, 'affine:code');
|
||||
|
||||
const locator = page.locator('affine-code');
|
||||
await expect(locator).toBeVisible();
|
||||
});
|
||||
|
||||
test('use markdown syntax can create code block', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'aaa');
|
||||
await pressEnter(page);
|
||||
await type(page, 'bbb');
|
||||
await pressTab(page);
|
||||
await pressEnter(page);
|
||||
await type(page, 'ccc');
|
||||
await pressTab(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await setSelection(page, 2, 0, 2, 0);
|
||||
// |aaa
|
||||
// bbb
|
||||
// ccc
|
||||
|
||||
await type(page, '``` ');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_markdown_syntax.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('use markdown syntax with trailing characters can create code block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '```JavaScript');
|
||||
await type(page, ' ');
|
||||
|
||||
const locator = page.locator('affine-code');
|
||||
await expect(locator).toBeVisible();
|
||||
});
|
||||
|
||||
test('support ```[lang] to add code block with language', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/1314',
|
||||
});
|
||||
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '```ts');
|
||||
await type(page, ' ');
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
const codeLocator = codeBlockController.codeBlock;
|
||||
await expect(codeLocator).toBeVisible();
|
||||
|
||||
const codeRect = await codeLocator.boundingBox();
|
||||
if (!codeRect) {
|
||||
throw new Error('Failed to get bounding box of code block.');
|
||||
}
|
||||
const position = {
|
||||
x: codeRect.x + codeRect.width / 2,
|
||||
y: codeRect.y + codeRect.height / 2,
|
||||
};
|
||||
await page.mouse.move(position.x, position.y);
|
||||
|
||||
const languageButton = codeBlockController.languageButton;
|
||||
await expect(languageButton).toBeVisible();
|
||||
await expect(languageButton).toHaveText('TypeScript');
|
||||
});
|
||||
|
||||
test('use more than three backticks can not create code block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '`````');
|
||||
await type(page, ' ');
|
||||
|
||||
const codeBlockLocator = page.locator('affine-code');
|
||||
await expect(codeBlockLocator).toBeHidden();
|
||||
const inlineCodelocator = page.getByText('```');
|
||||
await expect(inlineCodelocator).toBeVisible();
|
||||
expect(await inlineCodelocator.count()).toEqual(1);
|
||||
});
|
||||
|
||||
test('use shortcut can create code block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await createCodeBlock(page);
|
||||
|
||||
const locator = page.locator('affine-code');
|
||||
await expect(locator).toBeVisible();
|
||||
});
|
||||
|
||||
test('change code language can work', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await codeBlockController.clickLanguageButton();
|
||||
const locator = codeBlockController.langList;
|
||||
await expect(locator).toBeVisible();
|
||||
|
||||
await type(page, 'rust');
|
||||
await page.click(
|
||||
'.affine-filterable-list > .items-container > icon-button:nth-child(1)'
|
||||
);
|
||||
await expect(locator).toBeHidden();
|
||||
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await expect(codeBlockController.languageButton).toHaveText('Rust');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_1.json`
|
||||
);
|
||||
await undoByKeyboard(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_2.json`
|
||||
);
|
||||
|
||||
// Can switch to another language
|
||||
await codeBlockController.clickLanguageButton();
|
||||
await type(page, 'ty');
|
||||
await pressEnter(page);
|
||||
await expect(locator).toBeHidden();
|
||||
await expect(codeBlockController.languageButton).toHaveText('TypeScript');
|
||||
});
|
||||
|
||||
test('duplicate code block', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page, { language: 'javascript' });
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
|
||||
// change language
|
||||
await codeBlockController.clickLanguageButton();
|
||||
const langLocator = codeBlockController.langList;
|
||||
await expect(langLocator).toBeVisible();
|
||||
await type(page, 'rust');
|
||||
await page.click(
|
||||
'.affine-filterable-list > .items-container > icon-button:nth-child(1)'
|
||||
);
|
||||
|
||||
// add text
|
||||
await focusRichTextEnd(page);
|
||||
await type(page, 'let a: u8 = 7');
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
// add a caption
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await codeBlockController.captionButton.click();
|
||||
await type(page, 'BlockSuite');
|
||||
await pressEnter(page);
|
||||
await pressBackspace(page); // remove paragraph
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
// turn on wrap
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await (await codeBlockController.openMore()).wrapButton.click();
|
||||
|
||||
// duplicate
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await (await codeBlockController.openMore()).duplicateButton.click();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('delete code block in more menu', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page, { language: 'javascript' });
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
const moreMenu = await codeBlockController.openMore();
|
||||
|
||||
await expect(moreMenu.menu).toBeVisible();
|
||||
await moreMenu.deleteButton.click();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('undo and redo works in code block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'const a = 10;');
|
||||
await assertRichTexts(page, ['const a = 10;']);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await assertRichTexts(page, ['const a = 10;']);
|
||||
});
|
||||
|
||||
test('toggle code block wrap can work', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_1.json`
|
||||
);
|
||||
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await (await codeBlockController.openMore()).wrapButton.click();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_2.json`
|
||||
);
|
||||
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await (await codeBlockController.openMore()).cancelWrapButton.click();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_3.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('add caption works', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await codeBlockController.captionButton.click();
|
||||
await type(page, 'BlockSuite');
|
||||
await pressEnter(page);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('undo code block wrap can work', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_1.json`
|
||||
);
|
||||
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await (await codeBlockController.openMore()).wrapButton.click();
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_2.json`
|
||||
);
|
||||
|
||||
await focusRichText(page);
|
||||
await undoByKeyboard(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_3.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('code block toolbar widget can appear and disappear during mousemove', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const position = await page.locator('affine-code').boundingBox();
|
||||
if (!position) throw new Error('Failed to get affine code position');
|
||||
await page.mouse.move(position.x, position.y);
|
||||
|
||||
const locator = page.locator('.code-toolbar-container');
|
||||
const toolbarPosition = await locator.boundingBox();
|
||||
if (!toolbarPosition) throw new Error('Failed to get option position');
|
||||
await page.mouse.move(toolbarPosition.x, toolbarPosition.y);
|
||||
await expect(locator).toBeVisible();
|
||||
await page.mouse.move(position.x - 10, position.y - 10);
|
||||
await expect(locator).toBeHidden();
|
||||
});
|
||||
|
||||
test('should tab works in code block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'const a = 10;');
|
||||
await assertRichTexts(page, ['const a = 10;']);
|
||||
await page.keyboard.press('Tab', { delay: 50 });
|
||||
await assertRichTexts(page, [' const a = 10;']);
|
||||
await page.keyboard.press(`Shift+Tab`, { delay: 50 });
|
||||
await assertRichTexts(page, ['const a = 10;']);
|
||||
|
||||
await page.keyboard.press('Enter', { delay: 50 });
|
||||
await type(page, 'const b = "NothingToSay');
|
||||
await page.keyboard.press('ArrowUp', { delay: 50 });
|
||||
await page.keyboard.press('Enter', { delay: 50 });
|
||||
await page.keyboard.press('Tab', { delay: 50 });
|
||||
await assertRichTexts(page, ['const a = 10;\n \nconst b = "NothingToSay"']);
|
||||
});
|
||||
|
||||
test('should open more menu and close on selecting', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await expect(codeBlockController.codeToolbar).toBeVisible();
|
||||
const moreMenu = await codeBlockController.openMore();
|
||||
|
||||
await expect(moreMenu.menu).toBeVisible();
|
||||
await moreMenu.wrapButton.click();
|
||||
await expect(moreMenu.menu).toBeHidden();
|
||||
});
|
||||
|
||||
test('should code block lang input supports alias', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
const codeBlock = codeBlockController.codeBlock;
|
||||
await codeBlock.hover();
|
||||
await codeBlockController.clickLanguageButton();
|
||||
await expect(codeBlockController.langList).toBeVisible();
|
||||
await type(page, '文言');
|
||||
await pressEnter(page);
|
||||
await expect(codeBlockController.languageButton).toHaveText('Wenyan');
|
||||
});
|
||||
|
||||
test('multi-line indent', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'aaa');
|
||||
await pressEnter(page);
|
||||
|
||||
await type(page, 'bbb');
|
||||
await pressEnter(page);
|
||||
|
||||
await type(page, 'ccc');
|
||||
|
||||
await page.keyboard.down('Shift');
|
||||
await pressArrowUp(page, 2);
|
||||
await page.keyboard.up('Shift');
|
||||
|
||||
await pressTab(page);
|
||||
|
||||
await assertRichTexts(page, [' aaa\n bbb\n ccc']);
|
||||
|
||||
await pressShiftTab(page);
|
||||
|
||||
await assertRichTexts(page, ['aaa\nbbb\nccc']);
|
||||
|
||||
await pressShiftTab(page);
|
||||
|
||||
await assertRichTexts(page, ['aaa\nbbb\nccc']);
|
||||
});
|
||||
|
||||
test('should bracket complete works in code block', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/1800',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'const a = "');
|
||||
await assertRichTexts(page, ['const a = ""']);
|
||||
|
||||
await type(page, 'str');
|
||||
await assertRichTexts(page, ['const a = "str"']);
|
||||
await type(page, '(');
|
||||
await assertRichTexts(page, ['const a = "str()"']);
|
||||
await type(page, ']');
|
||||
await assertRichTexts(page, ['const a = "str(])"']);
|
||||
});
|
||||
|
||||
test('auto scroll horizontally when typing', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '``` ');
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
await type(page, String(i));
|
||||
}
|
||||
|
||||
const richTextScrollLeft1 = await page.evaluate(() => {
|
||||
const richText = document.querySelector('affine-code rich-text');
|
||||
if (!richText) {
|
||||
throw new Error('Failed to get rich text');
|
||||
}
|
||||
|
||||
return richText.scrollLeft;
|
||||
});
|
||||
expect(richTextScrollLeft1).toBeGreaterThan(200);
|
||||
|
||||
await pressArrowLeft(page, 5);
|
||||
await type(page, 'aa');
|
||||
|
||||
const richTextScrollLeft2 = await page.evaluate(() => {
|
||||
const richText = document.querySelector('affine-code rich-text');
|
||||
if (!richText) {
|
||||
throw new Error('Failed to get rich text');
|
||||
}
|
||||
|
||||
return richText.scrollLeft;
|
||||
});
|
||||
|
||||
expect(richTextScrollLeft2).toEqual(richTextScrollLeft1);
|
||||
});
|
||||
|
||||
test('code hotkey should not effect in global', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await pressEnter(page);
|
||||
await type(page, '``` ');
|
||||
|
||||
await assertTitle(page, '');
|
||||
await assertBlockCount(page, 'paragraph', 1);
|
||||
await assertBlockCount(page, 'code', 1);
|
||||
|
||||
await pressArrowUp(page);
|
||||
await pressBackspace(page);
|
||||
await type(page, 'aaa');
|
||||
|
||||
await assertTitle(page, 'aaa');
|
||||
await assertBlockCount(page, 'paragraph', 0);
|
||||
await assertBlockCount(page, 'code', 1);
|
||||
});
|
||||
|
||||
test('language selection list should not close when hovering out of code block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page, { language: 'javascript' });
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
|
||||
await codeBlockController.clickLanguageButton();
|
||||
const langLocator = codeBlockController.langList;
|
||||
await expect(langLocator).toBeVisible();
|
||||
|
||||
const bBox = await codeBlockController.codeBlock.boundingBox();
|
||||
if (!bBox) throw new Error('Expected bounding box');
|
||||
|
||||
const { x, y, width, height } = bBox;
|
||||
|
||||
// hovering inside the code block should keep the list open
|
||||
await page.mouse.move(x + width / 2, y + height / 2);
|
||||
await expect(langLocator).toBeVisible();
|
||||
|
||||
// hovering out should not close the list
|
||||
await page.mouse.move(x - 10, y - 10);
|
||||
await waitNextFrame(page);
|
||||
await expect(langLocator).toBeVisible();
|
||||
});
|
||||
|
||||
test('language selection list should not change when hovering over its elements', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await codeBlockController.clickLanguageButton();
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
const langListLocator = codeBlockController.langList;
|
||||
const langItemsLocator = langListLocator.locator('icon-button');
|
||||
|
||||
// checking first 4 language list items
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const item = langItemsLocator.nth(i); // current item in language list
|
||||
const nextItem = langItemsLocator.nth(i + 1); // next item in language list
|
||||
|
||||
await item.hover();
|
||||
|
||||
const initialItemText = await item.textContent();
|
||||
const initialNextItemText = await nextItem.textContent();
|
||||
|
||||
await nextItem.hover();
|
||||
|
||||
const currentItemText = await item.textContent();
|
||||
const currentNextItemText = await nextItem.textContent();
|
||||
|
||||
// text content should remain unchanged after next item receives focus
|
||||
expect(initialItemText).toBe(currentItemText);
|
||||
expect(initialNextItemText).toBe(currentNextItemText);
|
||||
}
|
||||
});
|
||||
|
||||
test('format text in code block', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '```ts ');
|
||||
await waitNextFrame(page, 100);
|
||||
await type(page, 'const aaa = 1000;');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
const line = page.locator('affine-code rich-text v-line > div');
|
||||
expect(await line.innerText()).toBe('const aaa = 1000;');
|
||||
|
||||
const { boldBtn, linkBtn } = getFormatBar(page);
|
||||
|
||||
await dragBetweenIndices(page, [0, 1], [0, 2]);
|
||||
await boldBtn.click();
|
||||
expect(await line.innerText()).toBe('const aaa = 1000;');
|
||||
await dragBetweenIndices(page, [0, 4], [0, 7]);
|
||||
await boldBtn.click();
|
||||
expect(await line.innerText()).toBe('const aaa = 1000;');
|
||||
await dragBetweenIndices(page, [0, 8], [0, 16]);
|
||||
await boldBtn.click();
|
||||
expect(await line.innerText()).toBe('const aaa = 1000;');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_format.json`
|
||||
);
|
||||
|
||||
await dragBetweenIndices(page, [0, 4], [0, 10]);
|
||||
await linkBtn.click();
|
||||
await type(page, 'https://www.baidu.com');
|
||||
await pressEnter(page);
|
||||
|
||||
expect(await line.innerText()).toBe('const aaa = 1000;');
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_link.json`
|
||||
);
|
||||
});
|
||||
59
blocksuite/tests-legacy/e2e/code/readonly.spec.ts
Normal file
59
blocksuite/tests-legacy/e2e/code/readonly.spec.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { switchReadonly } from '../utils/actions/click.js';
|
||||
import {
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressTab,
|
||||
type,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
focusRichTextEnd,
|
||||
initEmptyCodeBlockState,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { assertRichTexts } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import { getCodeBlock } from './utils.js';
|
||||
|
||||
test('should code block widget be disabled in read only mode', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichTextEnd(page);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await switchReadonly(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
const codeBlock = codeBlockController.codeBlock;
|
||||
await codeBlock.hover();
|
||||
await codeBlockController.clickLanguageButton();
|
||||
await expect(codeBlockController.langList).toBeHidden();
|
||||
|
||||
await codeBlock.hover();
|
||||
await expect(codeBlockController.codeToolbar).toBeVisible();
|
||||
await expect(codeBlockController.moreButton).toHaveAttribute('disabled');
|
||||
|
||||
await expect(codeBlockController.copyButton).toBeVisible();
|
||||
await expect(codeBlockController.moreMenu).toBeHidden();
|
||||
});
|
||||
|
||||
test('should not be able to modify code block in readonly mode', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'const a = 10;');
|
||||
await assertRichTexts(page, ['const a = 10;']);
|
||||
|
||||
await switchReadonly(page);
|
||||
await pressBackspace(page, 3);
|
||||
await pressTab(page, 3);
|
||||
await pressEnter(page, 2);
|
||||
await assertRichTexts(page, ['const a = 10;']);
|
||||
});
|
||||
195
blocksuite/tests-legacy/e2e/code/selections.spec.ts
Normal file
195
blocksuite/tests-legacy/e2e/code/selections.spec.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { dragBetweenCoords } from '../utils/actions/drag.js';
|
||||
import {
|
||||
pressArrowLeft,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEnterWithShortkey,
|
||||
redoByKeyboard,
|
||||
type,
|
||||
undoByKeyboard,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getInlineSelectionIndex,
|
||||
getInlineSelectionText,
|
||||
initEmptyCodeBlockState,
|
||||
} from '../utils/actions/misc.js';
|
||||
import {
|
||||
assertBlockCount,
|
||||
assertBlockSelections,
|
||||
assertRichTextInlineRange,
|
||||
assertRichTexts,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import { getCodeBlock } from './utils.js';
|
||||
|
||||
test('click outside should close language list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const codeBlock = getCodeBlock(page);
|
||||
await codeBlock.clickLanguageButton();
|
||||
const locator = codeBlock.langList;
|
||||
await expect(locator).toBeVisible();
|
||||
|
||||
const rect = await page.locator('affine-filterable-list').boundingBox();
|
||||
if (!rect) throw new Error('Failed to get bounding box of code block.');
|
||||
await page.mouse.click(rect.x - 10, rect.y - 10);
|
||||
|
||||
await expect(locator).toBeHidden();
|
||||
});
|
||||
|
||||
test('split code by enter', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
|
||||
// he|llo
|
||||
await pressArrowLeft(page, 3);
|
||||
|
||||
await pressEnter(page);
|
||||
await assertRichTexts(page, ['he\nllo']);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await assertRichTexts(page, ['he\nllo']);
|
||||
});
|
||||
|
||||
test('split code with selection by enter', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
|
||||
// select 'll'
|
||||
await pressArrowLeft(page, 1);
|
||||
await page.keyboard.down('Shift');
|
||||
await pressArrowLeft(page, 2);
|
||||
await page.keyboard.up('Shift');
|
||||
|
||||
await pressEnter(page);
|
||||
await assertRichTexts(page, ['he\no']);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await assertRichTexts(page, ['he\no']);
|
||||
});
|
||||
|
||||
test('drag select code block can delete it', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const codeBlock = page.locator('affine-code');
|
||||
const bbox = await codeBlock.boundingBox();
|
||||
if (!bbox) {
|
||||
throw new Error("Failed to get code block's bounding box");
|
||||
}
|
||||
const position = {
|
||||
startX: bbox.x - 10,
|
||||
startY: bbox.y - 10,
|
||||
endX: bbox.x + bbox.width,
|
||||
endY: bbox.y + bbox.height / 2,
|
||||
};
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: position.startX, y: position.startY },
|
||||
{ x: position.endX, y: position.endY },
|
||||
{ steps: 20 }
|
||||
);
|
||||
await page.waitForTimeout(10);
|
||||
await page.keyboard.press('Backspace');
|
||||
const locator = page.locator('affine-code');
|
||||
await expect(locator).toBeHidden();
|
||||
});
|
||||
|
||||
test('press short key and enter at end of code block can jump out', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await pressEnterWithShortkey(page);
|
||||
|
||||
const locator = page.locator('affine-paragraph');
|
||||
await expect(locator).toBeVisible();
|
||||
});
|
||||
|
||||
test('press short key and enter at end of code block with content can jump out', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'const a = 10;');
|
||||
await pressEnterWithShortkey(page);
|
||||
|
||||
const locator = page.locator('affine-paragraph');
|
||||
await expect(locator).toBeVisible();
|
||||
});
|
||||
|
||||
test('press backspace inside should select code block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
const codeBlock = page.locator('affine-code');
|
||||
const selectedRects = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await page.keyboard.press('Backspace');
|
||||
await expect(selectedRects).toHaveCount(1);
|
||||
await expect(codeBlock).toBeVisible();
|
||||
await page.keyboard.press('Backspace');
|
||||
await expect(selectedRects).toHaveCount(0);
|
||||
await expect(codeBlock).toBeHidden();
|
||||
});
|
||||
|
||||
test('press backspace after code block can select code block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
const code = 'const a = 1;';
|
||||
await type(page, code);
|
||||
|
||||
await assertRichTextInlineRange(page, 0, 12);
|
||||
await pressEnterWithShortkey(page);
|
||||
await assertRichTextInlineRange(page, 1, 0);
|
||||
await assertBlockCount(page, 'paragraph', 1);
|
||||
await pressBackspace(page);
|
||||
await assertBlockSelections(page, ['2']);
|
||||
await assertBlockCount(page, 'paragraph', 0);
|
||||
});
|
||||
|
||||
test('press ArrowUp after code block can enter code block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
const code = 'const a = 1;';
|
||||
await type(page, code);
|
||||
|
||||
await pressEnterWithShortkey(page);
|
||||
await page.keyboard.press('ArrowUp');
|
||||
|
||||
const index = await getInlineSelectionIndex(page);
|
||||
expect(index).toBe(0);
|
||||
|
||||
const text = await getInlineSelectionText(page);
|
||||
expect(text).toBe(code);
|
||||
});
|
||||
61
blocksuite/tests-legacy/e2e/code/utils.ts
Normal file
61
blocksuite/tests-legacy/e2e/code/utils.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const codeBlockController = getCodeBlock(page);
|
||||
* const codeBlock = codeBlockController.codeBlock;
|
||||
* ```
|
||||
*/
|
||||
export function getCodeBlock(page: Page) {
|
||||
const codeBlock = page.locator('affine-code');
|
||||
const languageButton = page.getByTestId('lang-button');
|
||||
|
||||
const clickLanguageButton = async () => {
|
||||
await codeBlock.hover();
|
||||
await languageButton.click({ delay: 50 });
|
||||
};
|
||||
|
||||
const langList = page.locator('affine-filterable-list');
|
||||
const langFilterInput = langList.locator('#filter-input');
|
||||
|
||||
const codeToolbar = page.locator('affine-code-toolbar');
|
||||
|
||||
const copyButton = codeToolbar.getByRole('button', { name: 'Copy code' });
|
||||
const captionButton = codeToolbar.getByRole('button', { name: 'Caption' });
|
||||
const moreButton = codeToolbar.getByRole('button', { name: 'More' });
|
||||
|
||||
const menu = page.locator('.more-popup-menu');
|
||||
|
||||
const openMore = async () => {
|
||||
await moreButton.click();
|
||||
|
||||
const wrapButton = menu.getByRole('button', { name: 'Wrap' });
|
||||
const cancelWrapButton = menu.getByRole('button', { name: 'Cancel wrap' });
|
||||
const duplicateButton = menu.getByRole('button', { name: 'Duplicate' });
|
||||
const deleteButton = menu.getByRole('button', { name: 'Delete' });
|
||||
|
||||
return {
|
||||
menu,
|
||||
wrapButton,
|
||||
cancelWrapButton,
|
||||
duplicateButton,
|
||||
deleteButton,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
codeBlock,
|
||||
codeToolbar,
|
||||
captionButton,
|
||||
languageButton,
|
||||
langList,
|
||||
copyButton,
|
||||
moreButton,
|
||||
langFilterInput,
|
||||
moreMenu: menu,
|
||||
|
||||
openMore,
|
||||
clickLanguageButton,
|
||||
};
|
||||
}
|
||||
592
blocksuite/tests-legacy/e2e/database/actions.ts
Normal file
592
blocksuite/tests-legacy/e2e/database/actions.ts
Normal file
@@ -0,0 +1,592 @@
|
||||
import type {
|
||||
RichTextCell,
|
||||
RichTextCellEditing,
|
||||
} from '@blocksuite/affine-block-database';
|
||||
import { ZERO_WIDTH_SPACE } from '@blocksuite/inline';
|
||||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
pressEnter,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import {
|
||||
getBoundingBox,
|
||||
getBoundingClientRect,
|
||||
getEditorLocator,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/misc.js';
|
||||
|
||||
export async function press(page: Page, content: string) {
|
||||
await page.keyboard.press(content, { delay: 50 });
|
||||
await page.waitForTimeout(50);
|
||||
}
|
||||
|
||||
export async function initDatabaseColumn(page: Page, title = '') {
|
||||
const editor = getEditorLocator(page);
|
||||
await editor.locator('affine-data-view-table-group').first().hover();
|
||||
const columnAddBtn = editor.locator('.header-add-column-button');
|
||||
await columnAddBtn.click();
|
||||
await waitNextFrame(page, 200);
|
||||
|
||||
if (title) {
|
||||
await selectAllByKeyboard(page);
|
||||
await type(page, title);
|
||||
await waitNextFrame(page);
|
||||
await pressEnter(page);
|
||||
} else {
|
||||
await pressEnter(page);
|
||||
}
|
||||
}
|
||||
|
||||
export const renameColumn = async (page: Page, name: string) => {
|
||||
const column = page.locator('affine-database-header-column', {
|
||||
hasText: name,
|
||||
});
|
||||
await column.click();
|
||||
};
|
||||
|
||||
export async function performColumnAction(
|
||||
page: Page,
|
||||
name: string,
|
||||
action: string
|
||||
) {
|
||||
await renameColumn(page, name);
|
||||
|
||||
const actionMenu = page.locator(`.affine-menu-button`, { hasText: action });
|
||||
await actionMenu.click();
|
||||
}
|
||||
|
||||
export async function switchColumnType(
|
||||
page: Page,
|
||||
columnType: string,
|
||||
columnIndex = 1
|
||||
) {
|
||||
const { typeIcon } = await getDatabaseHeaderColumn(page, columnIndex);
|
||||
await typeIcon.click();
|
||||
|
||||
await clickColumnType(page, columnType);
|
||||
}
|
||||
|
||||
export function clickColumnType(page: Page, columnType: string) {
|
||||
const typeMenu = page.locator(`.affine-menu-button`, {
|
||||
hasText: new RegExp(`${columnType}`),
|
||||
});
|
||||
return typeMenu.click();
|
||||
}
|
||||
|
||||
export function getDatabaseBodyRows(page: Page) {
|
||||
const rowContainer = page.locator('.affine-database-block-rows');
|
||||
return rowContainer.locator('.database-row');
|
||||
}
|
||||
|
||||
export function getDatabaseBodyRow(page: Page, rowIndex = 0) {
|
||||
const rows = getDatabaseBodyRows(page);
|
||||
return rows.nth(rowIndex);
|
||||
}
|
||||
|
||||
export function getDatabaseTableContainer(page: Page) {
|
||||
const container = page.locator('.affine-database-table-container');
|
||||
return container;
|
||||
}
|
||||
|
||||
export async function assertDatabaseTitleColumnText(
|
||||
page: Page,
|
||||
title: string,
|
||||
index = 0
|
||||
) {
|
||||
const text = await page.evaluate(index => {
|
||||
const rowContainer = document.querySelector('.affine-database-block-rows');
|
||||
const row = rowContainer?.querySelector(
|
||||
`.database-row:nth-child(${index + 1})`
|
||||
);
|
||||
const titleColumnCell = row?.querySelector('.database-cell:nth-child(1)');
|
||||
const titleSpan = titleColumnCell?.querySelector(
|
||||
'.data-view-header-area-rich-text'
|
||||
) as HTMLElement;
|
||||
if (!titleSpan) throw new Error('Cannot find database title column editor');
|
||||
return titleSpan.innerText;
|
||||
}, index);
|
||||
|
||||
if (title === '') {
|
||||
expect(text).toMatch(new RegExp(`^(|[${ZERO_WIDTH_SPACE}])$`));
|
||||
} else {
|
||||
expect(text).toBe(title);
|
||||
}
|
||||
}
|
||||
|
||||
export function getDatabaseBodyCell(
|
||||
page: Page,
|
||||
{
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
}: {
|
||||
rowIndex: number;
|
||||
columnIndex: number;
|
||||
}
|
||||
) {
|
||||
const row = getDatabaseBodyRow(page, rowIndex);
|
||||
const cell = row.locator('.database-cell').nth(columnIndex);
|
||||
return cell;
|
||||
}
|
||||
|
||||
export function getDatabaseBodyCellContent(
|
||||
page: Page,
|
||||
{
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
cellClass,
|
||||
}: {
|
||||
rowIndex: number;
|
||||
columnIndex: number;
|
||||
cellClass: string;
|
||||
}
|
||||
) {
|
||||
const cell = getDatabaseBodyCell(page, { rowIndex, columnIndex });
|
||||
const cellContent = cell.locator(`.${cellClass}`);
|
||||
return cellContent;
|
||||
}
|
||||
|
||||
export function getFirstColumnCell(page: Page, cellClass: string) {
|
||||
const cellContent = getDatabaseBodyCellContent(page, {
|
||||
rowIndex: 0,
|
||||
columnIndex: 1,
|
||||
cellClass,
|
||||
});
|
||||
return cellContent;
|
||||
}
|
||||
|
||||
export async function clickSelectOption(page: Page, index = 0) {
|
||||
await page.locator('.select-option-icon').nth(index).click();
|
||||
}
|
||||
|
||||
export async function performSelectColumnTagAction(
|
||||
page: Page,
|
||||
name: string,
|
||||
index = 0
|
||||
) {
|
||||
await clickSelectOption(page, index);
|
||||
await page
|
||||
.locator('.affine-menu-button', { hasText: new RegExp(name) })
|
||||
.click();
|
||||
}
|
||||
|
||||
export async function assertSelectedStyle(
|
||||
page: Page,
|
||||
key: keyof CSSStyleDeclaration,
|
||||
value: string
|
||||
) {
|
||||
const style = await getElementStyle(page, '.select-selected', key);
|
||||
expect(style).toBe(value);
|
||||
}
|
||||
|
||||
export async function clickDatabaseOutside(page: Page) {
|
||||
const docTitle = page.locator('.doc-title-container');
|
||||
await docTitle.click();
|
||||
}
|
||||
|
||||
export async function assertColumnWidth(locator: Locator, width: number) {
|
||||
const box = await getBoundingBox(locator);
|
||||
expect(box.width).toBe(width + 1);
|
||||
return box;
|
||||
}
|
||||
|
||||
export async function assertDatabaseCellRichTexts(
|
||||
page: Page,
|
||||
{
|
||||
rowIndex = 0,
|
||||
columnIndex = 1,
|
||||
text,
|
||||
}: {
|
||||
rowIndex?: number;
|
||||
columnIndex?: number;
|
||||
text: string;
|
||||
}
|
||||
) {
|
||||
const cellContainer = page.locator(
|
||||
`affine-database-cell-container[data-row-index='${rowIndex}'][data-column-index='${columnIndex}']`
|
||||
);
|
||||
|
||||
const cellEditing = cellContainer.locator(
|
||||
'affine-database-rich-text-cell-editing'
|
||||
);
|
||||
const cell = cellContainer.locator('affine-database-rich-text-cell');
|
||||
|
||||
const richText = (await cellEditing.count()) === 0 ? cell : cellEditing;
|
||||
const actualTexts = await richText.evaluate(ele => {
|
||||
return (ele as RichTextCellEditing).inlineEditor?.yTextString;
|
||||
});
|
||||
expect(actualTexts).toEqual(text);
|
||||
}
|
||||
|
||||
export async function assertDatabaseCellNumber(
|
||||
page: Page,
|
||||
{
|
||||
rowIndex = 0,
|
||||
columnIndex = 1,
|
||||
text,
|
||||
}: {
|
||||
rowIndex?: number;
|
||||
columnIndex?: number;
|
||||
text: string;
|
||||
}
|
||||
) {
|
||||
const actualText = await page
|
||||
.locator('.affine-database-block-rows')
|
||||
.locator('.database-row')
|
||||
.nth(rowIndex)
|
||||
.locator('.database-cell')
|
||||
.nth(columnIndex)
|
||||
.locator('.number')
|
||||
.textContent();
|
||||
expect(actualText?.trim()).toEqual(text);
|
||||
}
|
||||
|
||||
export async function assertDatabaseCellLink(
|
||||
page: Page,
|
||||
{
|
||||
rowIndex = 0,
|
||||
columnIndex = 1,
|
||||
text,
|
||||
}: {
|
||||
rowIndex?: number;
|
||||
columnIndex?: number;
|
||||
text: string;
|
||||
}
|
||||
) {
|
||||
const actualTexts = await page.evaluate(
|
||||
({ rowIndex, columnIndex }) => {
|
||||
const rows = document.querySelector('.affine-database-block-rows');
|
||||
const row = rows?.querySelector(
|
||||
`.database-row:nth-child(${rowIndex + 1})`
|
||||
);
|
||||
const cell = row?.querySelector(
|
||||
`.database-cell:nth-child(${columnIndex + 1})`
|
||||
);
|
||||
const richText =
|
||||
cell?.querySelector<RichTextCell>('affine-database-link-cell') ??
|
||||
cell?.querySelector<RichTextCellEditing>(
|
||||
'affine-database-link-cell-editing'
|
||||
);
|
||||
if (!richText) throw new Error('Missing database rich text cell');
|
||||
return richText.inlineEditor!.yText.toString();
|
||||
},
|
||||
{ rowIndex, columnIndex }
|
||||
);
|
||||
expect(actualTexts).toEqual(text);
|
||||
}
|
||||
|
||||
export async function assertDatabaseTitleText(page: Page, text: string) {
|
||||
const dbTitle = page.locator('[data-block-is-database-title="true"]');
|
||||
expect(await dbTitle.inputValue()).toEqual(text);
|
||||
}
|
||||
|
||||
export async function waitSearchTransitionEnd(page: Page) {
|
||||
await waitNextFrame(page, 400);
|
||||
}
|
||||
|
||||
export async function assertDatabaseSearching(
|
||||
page: Page,
|
||||
isSearching: boolean
|
||||
) {
|
||||
const searchExpand = page.locator('.search-container-expand');
|
||||
const count = await searchExpand.count();
|
||||
expect(count).toBe(isSearching ? 1 : 0);
|
||||
}
|
||||
|
||||
export async function focusDatabaseSearch(page: Page) {
|
||||
await (await getDatabaseMouse(page)).mouseOver();
|
||||
|
||||
const searchExpand = page.locator('.search-container-expand');
|
||||
const count = await searchExpand.count();
|
||||
if (count === 1) {
|
||||
const input = page.locator('.affine-database-search-input');
|
||||
await input.click();
|
||||
} else {
|
||||
const searchIcon = page.locator('.affine-database-search-input-icon');
|
||||
await searchIcon.click();
|
||||
await waitSearchTransitionEnd(page);
|
||||
}
|
||||
}
|
||||
|
||||
export async function blurDatabaseSearch(page: Page) {
|
||||
const dbTitle = page.locator('[data-block-is-database-title="true"]');
|
||||
await dbTitle.click();
|
||||
}
|
||||
|
||||
export async function focusDatabaseHeader(page: Page, columnIndex = 0) {
|
||||
const column = page.locator('.affine-database-column').nth(columnIndex);
|
||||
const box = await getBoundingBox(column);
|
||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||
await waitNextFrame(page);
|
||||
return column;
|
||||
}
|
||||
|
||||
export async function getDatabaseMouse(page: Page) {
|
||||
const databaseRect = await getBoundingClientRect(
|
||||
page,
|
||||
'.affine-database-table'
|
||||
);
|
||||
return {
|
||||
mouseOver: async () => {
|
||||
await page.mouse.move(databaseRect.x, databaseRect.y);
|
||||
},
|
||||
mouseLeave: async () => {
|
||||
await page.mouse.move(databaseRect.x - 1, databaseRect.y - 1);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function getDatabaseHeaderColumn(page: Page, index = 0) {
|
||||
const column = page.locator('.affine-database-column').nth(index);
|
||||
const box = await getBoundingBox(column);
|
||||
const textElement = column.locator('.affine-database-column-text-input');
|
||||
const text = await textElement.innerText();
|
||||
const typeIcon = column.locator('.affine-database-column-type-icon');
|
||||
|
||||
return {
|
||||
column,
|
||||
box,
|
||||
text,
|
||||
textElement,
|
||||
typeIcon,
|
||||
};
|
||||
}
|
||||
|
||||
export async function assertRowsSelection(
|
||||
page: Page,
|
||||
rowIndexes: [start: number, end: number]
|
||||
) {
|
||||
const rows = page.locator('data-view-table-row');
|
||||
const startIndex = rowIndexes[0];
|
||||
const endIndex = rowIndexes[1];
|
||||
for (let i = startIndex; i <= endIndex; i++) {
|
||||
const row = rows.nth(i);
|
||||
await row.locator('.row-select-checkbox .selected').isVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export async function assertCellsSelection(
|
||||
page: Page,
|
||||
cellIndexes: {
|
||||
start: [rowIndex: number, columnIndex: number];
|
||||
end?: [rowIndex: number, columnIndex: number];
|
||||
}
|
||||
) {
|
||||
const { start, end } = cellIndexes;
|
||||
|
||||
if (!end) {
|
||||
// single cell
|
||||
const focus = page.locator('.database-focus');
|
||||
const focusBox = await getBoundingBox(focus);
|
||||
|
||||
const [rowIndex, columnIndex] = start;
|
||||
const cell = getDatabaseBodyCell(page, { rowIndex, columnIndex });
|
||||
const cellBox = await getBoundingBox(cell);
|
||||
expect(focusBox).toEqual({
|
||||
x: cellBox.x,
|
||||
y: cellBox.y - 1,
|
||||
height: cellBox.height + 2,
|
||||
width: cellBox.width + 1,
|
||||
});
|
||||
} else {
|
||||
// multi cells
|
||||
const selection = page.locator('.database-selection');
|
||||
const selectionBox = await getBoundingBox(selection);
|
||||
|
||||
const [startRowIndex, startColumnIndex] = start;
|
||||
const [endRowIndex, endColumnIndex] = end;
|
||||
|
||||
const rowIndexStart = Math.min(startRowIndex, endRowIndex);
|
||||
const rowIndexEnd = Math.max(startRowIndex, endRowIndex);
|
||||
const columnIndexStart = Math.min(startColumnIndex, endColumnIndex);
|
||||
const columnIndexEnd = Math.max(startColumnIndex, endColumnIndex);
|
||||
|
||||
let height = 0;
|
||||
let width = 0;
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
for (let i = rowIndexStart; i <= rowIndexEnd; i++) {
|
||||
const cell = getDatabaseBodyCell(page, {
|
||||
rowIndex: i,
|
||||
columnIndex: columnIndexStart,
|
||||
});
|
||||
const box = await getBoundingBox(cell);
|
||||
height += box.height + 1;
|
||||
if (i === rowIndexStart) {
|
||||
y = box.y;
|
||||
}
|
||||
}
|
||||
|
||||
for (let j = columnIndexStart; j <= columnIndexEnd; j++) {
|
||||
const cell = getDatabaseBodyCell(page, {
|
||||
rowIndex: rowIndexStart,
|
||||
columnIndex: j,
|
||||
});
|
||||
const box = await getBoundingBox(cell);
|
||||
width += box.width;
|
||||
if (j === columnIndexStart) {
|
||||
x = box.x;
|
||||
}
|
||||
}
|
||||
|
||||
expect(selectionBox).toEqual({
|
||||
x,
|
||||
y,
|
||||
height,
|
||||
width: width + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function getElementStyle(
|
||||
page: Page,
|
||||
selector: string,
|
||||
key: keyof CSSStyleDeclaration
|
||||
) {
|
||||
const style = await page.evaluate(
|
||||
({ key, selector }) => {
|
||||
const el = document.querySelector<HTMLElement>(selector);
|
||||
if (!el) throw new Error(`Missing ${selector} tag`);
|
||||
// @ts-ignore
|
||||
return el.style[key];
|
||||
},
|
||||
{
|
||||
key,
|
||||
selector,
|
||||
}
|
||||
);
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
export async function focusKanbanCardHeader(page: Page, index = 0) {
|
||||
const cardHeader = page.locator('data-view-header-area-text').nth(index);
|
||||
await cardHeader.click();
|
||||
}
|
||||
|
||||
export async function clickKanbanCardHeader(page: Page, index = 0) {
|
||||
const cardHeader = page.locator('data-view-header-area-text').nth(index);
|
||||
await cardHeader.click();
|
||||
await cardHeader.click();
|
||||
}
|
||||
|
||||
export async function assertKanbanCardHeaderText(
|
||||
page: Page,
|
||||
text: string,
|
||||
index = 0
|
||||
) {
|
||||
const cardHeader = page.locator('data-view-header-area-text').nth(index);
|
||||
|
||||
await expect(cardHeader).toHaveText(text);
|
||||
}
|
||||
|
||||
export async function assertKanbanCellSelected(
|
||||
page: Page,
|
||||
{
|
||||
groupIndex,
|
||||
cardIndex,
|
||||
cellIndex,
|
||||
}: {
|
||||
groupIndex: number;
|
||||
cardIndex: number;
|
||||
cellIndex: number;
|
||||
}
|
||||
) {
|
||||
const border = await page.evaluate(
|
||||
({ groupIndex, cardIndex, cellIndex }) => {
|
||||
const group = document.querySelector(
|
||||
`affine-data-view-kanban-group:nth-child(${groupIndex + 1})`
|
||||
);
|
||||
const card = group?.querySelector(
|
||||
`affine-data-view-kanban-card:nth-child(${cardIndex + 1})`
|
||||
);
|
||||
const cells = Array.from(
|
||||
card?.querySelectorAll<HTMLElement>(`affine-data-view-kanban-cell`) ??
|
||||
[]
|
||||
);
|
||||
const cell = cells[cellIndex];
|
||||
if (!cell) throw new Error(`Missing cell tag`);
|
||||
return cell.style.border;
|
||||
},
|
||||
{
|
||||
groupIndex,
|
||||
cardIndex,
|
||||
cellIndex,
|
||||
}
|
||||
);
|
||||
|
||||
expect(border).toEqual('1px solid var(--affine-primary-color)');
|
||||
}
|
||||
|
||||
export async function assertKanbanCardSelected(
|
||||
page: Page,
|
||||
{
|
||||
groupIndex,
|
||||
cardIndex,
|
||||
}: {
|
||||
groupIndex: number;
|
||||
cardIndex: number;
|
||||
}
|
||||
) {
|
||||
const border = await page.evaluate(
|
||||
({ groupIndex, cardIndex }) => {
|
||||
const group = document.querySelector(
|
||||
`affine-data-view-kanban-group:nth-child(${groupIndex + 1})`
|
||||
);
|
||||
const card = group?.querySelector<HTMLElement>(
|
||||
`affine-data-view-kanban-card:nth-child(${cardIndex + 1})`
|
||||
);
|
||||
if (!card) throw new Error(`Missing card tag`);
|
||||
return card.style.border;
|
||||
},
|
||||
{
|
||||
groupIndex,
|
||||
cardIndex,
|
||||
}
|
||||
);
|
||||
|
||||
expect(border).toEqual('1px solid var(--affine-primary-color)');
|
||||
}
|
||||
|
||||
export function getKanbanCard(
|
||||
page: Page,
|
||||
{
|
||||
groupIndex,
|
||||
cardIndex,
|
||||
}: {
|
||||
groupIndex: number;
|
||||
cardIndex: number;
|
||||
}
|
||||
) {
|
||||
const group = page.locator('affine-data-view-kanban-group').nth(groupIndex);
|
||||
const card = group.locator('affine-data-view-kanban-card').nth(cardIndex);
|
||||
return card;
|
||||
}
|
||||
export const moveToCenterOf = async (page: Page, locator: Locator) => {
|
||||
const box = (await locator.boundingBox())!;
|
||||
expect(box).toBeDefined();
|
||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||
};
|
||||
export const changeColumnType = async (
|
||||
page: Page,
|
||||
column: number,
|
||||
name: string
|
||||
) => {
|
||||
await waitNextFrame(page);
|
||||
await page.locator('affine-database-header-column').nth(column).click();
|
||||
await waitNextFrame(page, 200);
|
||||
await pressKey(page, 'Escape');
|
||||
await pressKey(page, 'ArrowDown');
|
||||
await pressKey(page, 'Enter');
|
||||
await type(page, name);
|
||||
await pressKey(page, 'ArrowDown');
|
||||
await pressKey(page, 'Enter');
|
||||
};
|
||||
export const pressKey = async (page: Page, key: string, count: number = 1) => {
|
||||
for (let i = 0; i < count; i++) {
|
||||
await waitNextFrame(page);
|
||||
await press(page, key);
|
||||
}
|
||||
await waitNextFrame(page);
|
||||
};
|
||||
176
blocksuite/tests-legacy/e2e/database/clipboard.spec.ts
Normal file
176
blocksuite/tests-legacy/e2e/database/clipboard.spec.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { dragBetweenCoords } from '../utils/actions/drag.js';
|
||||
import {
|
||||
copyByKeyboard,
|
||||
pasteByKeyboard,
|
||||
pressArrowDown,
|
||||
pressArrowUp,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
type,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getBoundingBox,
|
||||
initDatabaseDynamicRowWithData,
|
||||
initDatabaseRowWithData,
|
||||
initEmptyDatabaseState,
|
||||
initEmptyDatabaseWithParagraphState,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { assertRichTexts } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import {
|
||||
assertDatabaseTitleColumnText,
|
||||
getDatabaseBodyCell,
|
||||
getElementStyle,
|
||||
initDatabaseColumn,
|
||||
switchColumnType,
|
||||
} from './actions.js';
|
||||
|
||||
test.describe('copy&paste when editing', () => {
|
||||
test.skip('should support copy&paste of the title column', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseWithParagraphState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseRowWithData(page, 'abc123');
|
||||
await pressEscape(page);
|
||||
|
||||
await pressEnter(page);
|
||||
await page.keyboard.down('Shift');
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
}
|
||||
await page.keyboard.up('Shift');
|
||||
await copyByKeyboard(page);
|
||||
|
||||
const bgValue = await getElementStyle(page, '.database-focus', 'boxShadow');
|
||||
expect(bgValue).not.toBe('unset');
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await pasteByKeyboard(page);
|
||||
await assertRichTexts(page, ['Database 1', 'c123']);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('copy&paste when selecting', () => {
|
||||
test.skip('should support copy&paste of a single cell', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseRowWithData(page, 'abc123');
|
||||
await initDatabaseRowWithData(page, '');
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await pressArrowUp(page);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
await pressArrowDown(page);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page);
|
||||
await assertDatabaseTitleColumnText(page, 'abc123', 1);
|
||||
});
|
||||
|
||||
test.skip('should support copy&paste of multi cells', 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, '');
|
||||
await initDatabaseRowWithData(page, '');
|
||||
|
||||
const startCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 0,
|
||||
columnIndex: 0,
|
||||
});
|
||||
const startCellBox = await getBoundingBox(startCell);
|
||||
const endCell = getDatabaseBodyCell(page, { rowIndex: 1, columnIndex: 1 });
|
||||
const endCellBox = await getBoundingBox(endCell);
|
||||
const startX = startCellBox.x + startCellBox.width / 2;
|
||||
const startY = startCellBox.y + startCellBox.height / 2;
|
||||
const endX = endCellBox.x + endCellBox.width / 2;
|
||||
const endY = endCellBox.y + endCellBox.height / 2;
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: startX, y: startY },
|
||||
{ x: endX, y: endY },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
await copyByKeyboard(page);
|
||||
|
||||
await pressArrowDown(page);
|
||||
await pressArrowDown(page);
|
||||
await waitNextFrame(page);
|
||||
await pasteByKeyboard(page, false);
|
||||
|
||||
await assertDatabaseTitleColumnText(page, 'text1', 2);
|
||||
await assertDatabaseTitleColumnText(page, 'text2', 3);
|
||||
const selectCell21 = getDatabaseBodyCell(page, {
|
||||
rowIndex: 2,
|
||||
columnIndex: 1,
|
||||
});
|
||||
const selectCell31 = getDatabaseBodyCell(page, {
|
||||
rowIndex: 3,
|
||||
columnIndex: 1,
|
||||
});
|
||||
expect(await selectCell21.innerText()).toBe('123');
|
||||
expect(await selectCell31.innerText()).toBe('a');
|
||||
});
|
||||
|
||||
test.skip('should support copy&paste of a single row', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseRowWithData(page, 'text1');
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await initDatabaseDynamicRowWithData(page, 'abc', false);
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await initDatabaseColumn(page);
|
||||
await switchColumnType(page, 'Number', 2);
|
||||
const numberCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 0,
|
||||
columnIndex: 2,
|
||||
});
|
||||
await numberCell.click();
|
||||
await waitNextFrame(page);
|
||||
await type(page, '123');
|
||||
await pressEscape(page);
|
||||
await pressEscape(page);
|
||||
await copyByKeyboard(page);
|
||||
|
||||
await initDatabaseRowWithData(page, '');
|
||||
await pressEscape(page);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertDatabaseTitleColumnText(page, 'text1', 1);
|
||||
const selectCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 1,
|
||||
columnIndex: 1,
|
||||
});
|
||||
expect(await selectCell.innerText()).toBe('abc');
|
||||
const selectNumberCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 1,
|
||||
columnIndex: 2,
|
||||
});
|
||||
expect(await selectNumberCell.innerText()).toBe('123');
|
||||
});
|
||||
});
|
||||
599
blocksuite/tests-legacy/e2e/database/column.spec.ts
Normal file
599
blocksuite/tests-legacy/e2e/database/column.spec.ts
Normal file
@@ -0,0 +1,599 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
assertDatabaseColumnOrder,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
getBoundingBox,
|
||||
initDatabaseDynamicRowWithData,
|
||||
initEmptyDatabaseState,
|
||||
pressArrowRight,
|
||||
pressArrowUp,
|
||||
pressArrowUpWithShiftKey,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
redoByClick,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
undoByClick,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import {
|
||||
assertDatabaseCellNumber,
|
||||
assertDatabaseCellRichTexts,
|
||||
assertSelectedStyle,
|
||||
changeColumnType,
|
||||
clickDatabaseOutside,
|
||||
clickSelectOption,
|
||||
getDatabaseHeaderColumn,
|
||||
getFirstColumnCell,
|
||||
initDatabaseColumn,
|
||||
performColumnAction,
|
||||
performSelectColumnTagAction,
|
||||
switchColumnType,
|
||||
} from './actions.js';
|
||||
|
||||
test.describe('column operations', () => {
|
||||
test('should support rename column', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, 'abc');
|
||||
|
||||
const { textElement } = await getDatabaseHeaderColumn(page, 1);
|
||||
expect(await textElement.innerText()).toBe('abc');
|
||||
await textElement.click();
|
||||
await waitNextFrame(page, 200);
|
||||
await pressArrowRight(page);
|
||||
await type(page, '123');
|
||||
await pressEnter(page);
|
||||
expect(await textElement.innerText()).toBe('abc123');
|
||||
|
||||
await undoByClick(page);
|
||||
expect(await textElement.innerText()).toBe('abc');
|
||||
await redoByClick(page);
|
||||
expect(await textElement.innerText()).toBe('abc123');
|
||||
});
|
||||
|
||||
test('should support add new column', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await pressEscape(page);
|
||||
const { text: title1 } = await getDatabaseHeaderColumn(page, 1);
|
||||
expect(title1).toBe('Column 1');
|
||||
|
||||
const selected = getFirstColumnCell(page, 'select-selected');
|
||||
expect(await selected.innerText()).toBe('123');
|
||||
|
||||
await initDatabaseColumn(page, 'abc');
|
||||
const { text: title2 } = await getDatabaseHeaderColumn(page, 2);
|
||||
expect(title2).toBe('abc');
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
const { text: title3 } = await getDatabaseHeaderColumn(page, 3);
|
||||
expect(title3).toBe('Column 2');
|
||||
});
|
||||
|
||||
test('should support right insert column', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, '1');
|
||||
|
||||
await performColumnAction(page, '1', 'Insert right');
|
||||
await selectAllByKeyboard(page);
|
||||
await type(page, '2');
|
||||
await pressEnter(page);
|
||||
const columns = page.locator('.affine-database-column');
|
||||
expect(await columns.count()).toBe(3);
|
||||
|
||||
await assertDatabaseColumnOrder(page, ['1', '2']);
|
||||
});
|
||||
|
||||
test('should support left insert column', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, '1');
|
||||
|
||||
await performColumnAction(page, '1', 'Insert left');
|
||||
await selectAllByKeyboard(page);
|
||||
await type(page, '2');
|
||||
await pressEnter(page);
|
||||
const columns = page.locator('.affine-database-column');
|
||||
expect(await columns.count()).toBe(3);
|
||||
|
||||
await assertDatabaseColumnOrder(page, ['2', '1']);
|
||||
});
|
||||
|
||||
test('should support delete column', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, '1');
|
||||
|
||||
const columns = page.locator('.affine-database-column');
|
||||
expect(await columns.count()).toBe(2);
|
||||
|
||||
await performColumnAction(page, '1', 'Delete');
|
||||
expect(await columns.count()).toBe(1);
|
||||
});
|
||||
|
||||
test('should support duplicate column', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, '1');
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await pressEscape(page);
|
||||
await performColumnAction(page, '1', 'duplicate');
|
||||
await pressEscape(page);
|
||||
const cells = page.locator('affine-database-multi-select-cell');
|
||||
expect(await cells.count()).toBe(2);
|
||||
|
||||
const secondCell = cells.nth(1);
|
||||
const selected = secondCell.locator('.select-selected');
|
||||
expect(await selected.innerText()).toBe('123');
|
||||
});
|
||||
|
||||
test('should support move column right', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, '1');
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await pressEscape(page);
|
||||
await initDatabaseColumn(page, '2');
|
||||
await initDatabaseDynamicRowWithData(page, 'abc', false, 1);
|
||||
await pressEscape(page);
|
||||
await assertDatabaseColumnOrder(page, ['1', '2']);
|
||||
await waitNextFrame(page, 350);
|
||||
await performColumnAction(page, '1', 'Move right');
|
||||
await assertDatabaseColumnOrder(page, ['2', '1']);
|
||||
|
||||
await undoByClick(page);
|
||||
const { column } = await getDatabaseHeaderColumn(page, 2);
|
||||
await column.click();
|
||||
const moveLeft = page.locator('.action', { hasText: 'Move right' });
|
||||
expect(await moveLeft.count()).toBe(0);
|
||||
});
|
||||
|
||||
test('should support move column left', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, '1');
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await pressEscape(page);
|
||||
await initDatabaseColumn(page, '2');
|
||||
await initDatabaseDynamicRowWithData(page, 'abc', false, 1);
|
||||
await pressEscape(page);
|
||||
await assertDatabaseColumnOrder(page, ['1', '2']);
|
||||
|
||||
const { column } = await getDatabaseHeaderColumn(page, 0);
|
||||
await column.click();
|
||||
const moveLeft = page.locator('.action', { hasText: 'Move left' });
|
||||
expect(await moveLeft.count()).toBe(0);
|
||||
await waitNextFrame(page, 200);
|
||||
await pressEscape(page);
|
||||
await pressEscape(page);
|
||||
|
||||
await performColumnAction(page, '2', 'Move left');
|
||||
await assertDatabaseColumnOrder(page, ['2', '1']);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('switch column type', () => {
|
||||
test('switch to number', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123abc', true);
|
||||
await pressEscape(page);
|
||||
await changeColumnType(page, 1, 'Number');
|
||||
|
||||
const cell = getFirstColumnCell(page, 'number');
|
||||
await assertDatabaseCellNumber(page, {
|
||||
text: '',
|
||||
});
|
||||
await pressEnter(page);
|
||||
await type(page, '123abc');
|
||||
await pressEscape(page);
|
||||
expect((await cell.textContent())?.trim()).toBe('123');
|
||||
});
|
||||
|
||||
test('switch to rich-text', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123abc', true);
|
||||
await pressEscape(page);
|
||||
await switchColumnType(page, 'Text');
|
||||
|
||||
// For now, rich-text will only be initialized on click
|
||||
// Therefore, for the time being, here is to detect whether there is '.affine-database-rich-text'
|
||||
const cell = getFirstColumnCell(page, 'affine-database-rich-text');
|
||||
expect(await cell.count()).toBe(1);
|
||||
await pressEnter(page);
|
||||
await type(page, '123');
|
||||
await pressEscape(page);
|
||||
await pressEnter(page);
|
||||
await type(page, 'abc');
|
||||
await pressEscape(page);
|
||||
await assertDatabaseCellRichTexts(page, { text: '123abc123abc' });
|
||||
});
|
||||
|
||||
test('switch between multi-select and select', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await type(page, 'abc');
|
||||
await pressEnter(page);
|
||||
await pressEscape(page);
|
||||
const cell = getFirstColumnCell(page, 'select-selected');
|
||||
expect(await cell.count()).toBe(2);
|
||||
|
||||
await switchColumnType(page, 'Select', 1);
|
||||
expect(await cell.count()).toBe(1);
|
||||
expect(await cell.innerText()).toBe('123');
|
||||
|
||||
await pressEnter(page);
|
||||
await type(page, 'def');
|
||||
await pressEnter(page);
|
||||
expect(await cell.innerText()).toBe('def');
|
||||
|
||||
await switchColumnType(page, 'Multi-select');
|
||||
await pressEnter(page);
|
||||
await type(page, '666');
|
||||
await pressEnter(page);
|
||||
await pressEscape(page);
|
||||
expect(await cell.count()).toBe(2);
|
||||
expect(await cell.nth(0).innerText()).toBe('def');
|
||||
expect(await cell.nth(1).innerText()).toBe('666');
|
||||
|
||||
await switchColumnType(page, 'Select');
|
||||
expect(await cell.count()).toBe(1);
|
||||
expect(await cell.innerText()).toBe('def');
|
||||
|
||||
await pressEnter(page);
|
||||
await type(page, '888');
|
||||
await pressEnter(page);
|
||||
expect(await cell.innerText()).toBe('888');
|
||||
});
|
||||
|
||||
test('switch between number and rich-text', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await switchColumnType(page, 'Number');
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, '123abc', true);
|
||||
await assertDatabaseCellNumber(page, {
|
||||
text: '123',
|
||||
});
|
||||
|
||||
await switchColumnType(page, 'Text');
|
||||
await pressEnter(page);
|
||||
await type(page, 'abc');
|
||||
await pressEscape(page);
|
||||
await assertDatabaseCellRichTexts(page, { text: '123abc' });
|
||||
|
||||
await switchColumnType(page, 'Number');
|
||||
await assertDatabaseCellNumber(page, {
|
||||
text: '123',
|
||||
});
|
||||
});
|
||||
|
||||
test('switch number to select', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await switchColumnType(page, 'Number');
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
const cell = getFirstColumnCell(page, 'number');
|
||||
expect((await cell.textContent())?.trim()).toBe('123');
|
||||
|
||||
await switchColumnType(page, 'Select');
|
||||
await initDatabaseDynamicRowWithData(page, 'abc');
|
||||
const selectCell = getFirstColumnCell(page, 'select-selected');
|
||||
expect(await selectCell.innerText()).toBe('abc');
|
||||
|
||||
await switchColumnType(page, 'Number');
|
||||
await assertDatabaseCellNumber(page, {
|
||||
text: '',
|
||||
});
|
||||
});
|
||||
|
||||
test('switch to checkbox', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
await changeColumnType(page, 1, 'Checkbox');
|
||||
|
||||
const checkbox = getFirstColumnCell(page, 'checkbox');
|
||||
await expect(checkbox).not.toHaveClass('checked');
|
||||
|
||||
await waitNextFrame(page, 500);
|
||||
await checkbox.click();
|
||||
await expect(checkbox).toHaveClass(/checked/);
|
||||
|
||||
await undoByClick(page);
|
||||
await expect(checkbox).not.toHaveClass('checked');
|
||||
});
|
||||
|
||||
test('checkbox to text', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
await changeColumnType(page, 1, 'Checkbox');
|
||||
|
||||
let checkbox = getFirstColumnCell(page, 'checkbox');
|
||||
await expect(checkbox).not.toHaveClass('checked');
|
||||
|
||||
// checked
|
||||
await checkbox.click();
|
||||
await changeColumnType(page, 1, 'Text');
|
||||
await clickDatabaseOutside(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await assertDatabaseCellRichTexts(page, { text: 'Yes' });
|
||||
await clickDatabaseOutside(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await changeColumnType(page, 1, 'Checkbox');
|
||||
checkbox = getFirstColumnCell(page, 'checkbox');
|
||||
await expect(checkbox).toHaveClass(/checked/);
|
||||
|
||||
// not checked
|
||||
await checkbox.click();
|
||||
await changeColumnType(page, 1, 'Text');
|
||||
await clickDatabaseOutside(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await assertDatabaseCellRichTexts(page, { text: 'No' });
|
||||
await clickDatabaseOutside(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await changeColumnType(page, 1, 'Checkbox');
|
||||
checkbox = getFirstColumnCell(page, 'checkbox');
|
||||
await expect(checkbox).not.toHaveClass('checked');
|
||||
});
|
||||
|
||||
test('switch to progress', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
await switchColumnType(page, 'Progress');
|
||||
|
||||
const progress = getFirstColumnCell(page, 'progress');
|
||||
expect(await progress.textContent()).toBe('0');
|
||||
await waitNextFrame(page, 500);
|
||||
const progressBg = page.locator('.affine-database-progress-bg');
|
||||
const {
|
||||
x: progressBgX,
|
||||
y: progressBgY,
|
||||
width: progressBgWidth,
|
||||
} = await getBoundingBox(progressBg);
|
||||
await page.mouse.move(progressBgX, progressBgY);
|
||||
await page.mouse.click(progressBgX, progressBgY);
|
||||
const dragHandle = page.locator('.affine-database-progress-drag-handle');
|
||||
const {
|
||||
x: dragX,
|
||||
y: dragY,
|
||||
width,
|
||||
height,
|
||||
} = await getBoundingBox(dragHandle);
|
||||
const dragCenterX = dragX + width / 2;
|
||||
const dragCenterY = dragY + height / 2;
|
||||
await page.mouse.move(dragCenterX, dragCenterY);
|
||||
|
||||
const endX = dragCenterX + progressBgWidth;
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: dragCenterX, y: dragCenterY },
|
||||
{ x: endX, y: dragCenterY }
|
||||
);
|
||||
expect(await progress.textContent()).toBe('100');
|
||||
await pressEscape(page);
|
||||
await undoByClick(page);
|
||||
expect(await progress.textContent()).toBe('0');
|
||||
});
|
||||
|
||||
test('switch to link', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
|
||||
await switchColumnType(page, 'Link');
|
||||
|
||||
const linkText = 'http://example.com';
|
||||
const cell = getFirstColumnCell(page, 'affine-database-link');
|
||||
await pressEnter(page);
|
||||
await type(page, linkText);
|
||||
await pressEscape(page);
|
||||
const link = cell.locator('affine-database-link-node > a');
|
||||
const linkContent = link.locator('.link-node-text');
|
||||
await expect(link).toHaveAttribute('href', linkText);
|
||||
expect(await linkContent.textContent()).toBe(linkText);
|
||||
|
||||
// not link text
|
||||
await cell.hover();
|
||||
const linkEdit = getFirstColumnCell(page, 'affine-database-link-icon');
|
||||
await linkEdit.click();
|
||||
await selectAllByKeyboard(page);
|
||||
await type(page, 'abc');
|
||||
await pressEnter(page);
|
||||
await expect(link).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('select column tag action', () => {
|
||||
test('should support select tag renaming', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await type(page, 'abc');
|
||||
await pressEnter(page);
|
||||
await clickSelectOption(page);
|
||||
await waitNextFrame(page);
|
||||
await pressArrowRight(page);
|
||||
await type(page, '4567abc00');
|
||||
await pressEnter(page);
|
||||
const options = page.locator('.select-options-container .tag-text');
|
||||
expect(await options.nth(0).innerText()).toBe('abc4567abc00');
|
||||
expect(await options.nth(1).innerText()).toBe('123');
|
||||
});
|
||||
|
||||
test('should select tag renaming support shortcut key', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await clickSelectOption(page);
|
||||
await waitNextFrame(page);
|
||||
await pressArrowRight(page);
|
||||
await type(page, '456');
|
||||
// esc
|
||||
await pressEscape(page);
|
||||
await pressEscape(page);
|
||||
const options = page.locator('.select-options-container .tag-text');
|
||||
const option1 = options.nth(0);
|
||||
expect(await option1.innerText()).toBe('123456');
|
||||
});
|
||||
|
||||
test('should support select tag deletion', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await performSelectColumnTagAction(page, 'Delete');
|
||||
const options = page.locator('.select-option-name');
|
||||
expect(await options.count()).toBe(0);
|
||||
});
|
||||
|
||||
test('should support modifying select tag color', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await performSelectColumnTagAction(page, 'Red');
|
||||
await pressEscape(page);
|
||||
await assertSelectedStyle(
|
||||
page,
|
||||
'backgroundColor',
|
||||
'var(--affine-v2-chip-label-red)'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('drag-to-fill', () => {
|
||||
test('should show when cell in focus and hide on blur', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
|
||||
await pressEscape(page);
|
||||
|
||||
const dragToFillHandle = page.locator('.drag-to-fill');
|
||||
|
||||
await expect(dragToFillHandle).toBeVisible();
|
||||
|
||||
await pressEscape(page);
|
||||
|
||||
await expect(dragToFillHandle).toBeHidden();
|
||||
});
|
||||
|
||||
test('should not show in multi (row or column) selection', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
|
||||
const dragToFillHandle = page.locator('.drag-to-fill');
|
||||
|
||||
await expect(dragToFillHandle).toBeVisible();
|
||||
|
||||
await pressArrowUpWithShiftKey(page);
|
||||
|
||||
await expect(dragToFillHandle).toBeHidden();
|
||||
await pressArrowUp(page);
|
||||
|
||||
await expect(dragToFillHandle).toBeVisible();
|
||||
});
|
||||
|
||||
test('should fill columns with data', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, 'thing', true);
|
||||
await pressEscape(page);
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressBackspace(page);
|
||||
await type(page, 'aaa');
|
||||
await pressEnter(page);
|
||||
await pressEnter(page);
|
||||
|
||||
await pressEscape(page);
|
||||
await pressArrowUp(page);
|
||||
|
||||
const cells = page.locator('affine-database-multi-select-cell');
|
||||
|
||||
expect(await cells.nth(0).innerText()).toBe('thing');
|
||||
expect(await cells.nth(1).innerText()).toBe('aaa');
|
||||
|
||||
const dragToFillHandle = page.locator('.drag-to-fill');
|
||||
|
||||
await expect(dragToFillHandle).toBeVisible();
|
||||
|
||||
const bbox = await getBoundingBox(dragToFillHandle);
|
||||
|
||||
if (!bbox) throw new Error('Expected a bounding box');
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: bbox.x + bbox.width / 2, y: bbox.y + bbox.height / 2 },
|
||||
{ x: bbox.x, y: bbox.y + 200 }
|
||||
);
|
||||
|
||||
expect(await cells.nth(0).innerText()).toBe('thing');
|
||||
expect(await cells.nth(1).innerText()).toBe('thing');
|
||||
});
|
||||
});
|
||||
670
blocksuite/tests-legacy/e2e/database/database.spec.ts
Normal file
670
blocksuite/tests-legacy/e2e/database/database.spec.ts
Normal file
@@ -0,0 +1,670 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
567
blocksuite/tests-legacy/e2e/database/selection.spec.ts
Normal file
567
blocksuite/tests-legacy/e2e/database/selection.spec.ts
Normal file
@@ -0,0 +1,567 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { dragBetweenCoords } from '../utils/actions/drag.js';
|
||||
import { shiftClick } from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
pressArrowDown,
|
||||
pressArrowDownWithShiftKey,
|
||||
pressArrowLeft,
|
||||
pressArrowRight,
|
||||
pressArrowUp,
|
||||
pressArrowUpWithShiftKey,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
type,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
getBoundingBox,
|
||||
initDatabaseDynamicRowWithData,
|
||||
initDatabaseRowWithData,
|
||||
initEmptyDatabaseState,
|
||||
initKanbanViewState,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import {
|
||||
assertCellsSelection,
|
||||
assertDatabaseTitleColumnText,
|
||||
assertKanbanCardHeaderText,
|
||||
assertKanbanCardSelected,
|
||||
assertKanbanCellSelected,
|
||||
assertRowsSelection,
|
||||
clickKanbanCardHeader,
|
||||
focusKanbanCardHeader,
|
||||
getDatabaseBodyCell,
|
||||
getKanbanCard,
|
||||
initDatabaseColumn,
|
||||
switchColumnType,
|
||||
} from './actions.js';
|
||||
|
||||
test.describe('focus', () => {
|
||||
test('should support move focus by arrow key', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await pressEscape(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
});
|
||||
|
||||
test('should support multi row selection', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
await switchColumnType(page, 'Number');
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
|
||||
const selectColumn = getDatabaseBodyCell(page, {
|
||||
rowIndex: 1,
|
||||
columnIndex: 1,
|
||||
});
|
||||
|
||||
const endBox = await getBoundingBox(selectColumn);
|
||||
const endX = endBox.x + endBox.width / 2;
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: endX, y: endBox.y },
|
||||
{ x: endX, y: endBox.y + endBox.height }
|
||||
);
|
||||
await pressEscape(page);
|
||||
await assertRowsSelection(page, [0, 1]);
|
||||
});
|
||||
|
||||
test('should support row selection with dynamic height', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123123', true);
|
||||
await type(page, '456456');
|
||||
await pressEnter(page);
|
||||
await type(page, 'abcabc');
|
||||
await pressEnter(page);
|
||||
await type(page, 'defdef');
|
||||
await pressEnter(page);
|
||||
await pressEscape(page);
|
||||
|
||||
await pressEscape(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('row-level selection', () => {
|
||||
test('should support title selection', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseRowWithData(page, 'title');
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await assertCellsSelection(page, {
|
||||
start: [0, 0],
|
||||
});
|
||||
|
||||
await pressEscape(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
});
|
||||
|
||||
test('should support pressing esc to trigger row selection', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await pressEscape(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
});
|
||||
|
||||
test('should support multi row selection', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
await switchColumnType(page, 'Number');
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
|
||||
const selectColumn = getDatabaseBodyCell(page, {
|
||||
rowIndex: 1,
|
||||
columnIndex: 1,
|
||||
});
|
||||
|
||||
const endBox = await getBoundingBox(selectColumn);
|
||||
const endX = endBox.x + endBox.width / 2;
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: endX, y: endBox.y },
|
||||
{ x: endX, y: endBox.y + endBox.height }
|
||||
);
|
||||
await pressEscape(page);
|
||||
await assertRowsSelection(page, [0, 1]);
|
||||
});
|
||||
|
||||
test('should support row selection with dynamic height', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123123', true);
|
||||
await type(page, '456456');
|
||||
await pressEnter(page);
|
||||
await type(page, 'abcabc');
|
||||
await pressEnter(page);
|
||||
await type(page, 'defdef');
|
||||
await pressEnter(page);
|
||||
await pressEscape(page);
|
||||
|
||||
await pressEscape(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
});
|
||||
|
||||
test('move row selection with (up | down)', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
|
||||
// add two rows
|
||||
await initDatabaseDynamicRowWithData(page, '123123', true);
|
||||
await pressEscape(page);
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, '123123', true);
|
||||
await pressEscape(page);
|
||||
|
||||
await pressEscape(page); // switch to row selection
|
||||
|
||||
await assertRowsSelection(page, [1, 1]);
|
||||
|
||||
await pressArrowUp(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
|
||||
// should not allow under selection
|
||||
await pressArrowUp(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
|
||||
await pressArrowDown(page);
|
||||
await assertRowsSelection(page, [1, 1]);
|
||||
|
||||
// should not allow over selection
|
||||
await pressArrowDown(page);
|
||||
await assertRowsSelection(page, [1, 1]);
|
||||
});
|
||||
|
||||
test('increment decrement row selection with shift+(up | down)', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
|
||||
// add two rows
|
||||
await initDatabaseDynamicRowWithData(page, '123123', true);
|
||||
await pressEscape(page);
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, '123123', true);
|
||||
await pressEscape(page);
|
||||
|
||||
await pressEscape(page); // switch to row selection
|
||||
|
||||
await pressArrowUpWithShiftKey(page);
|
||||
await assertRowsSelection(page, [0, 1]);
|
||||
|
||||
await pressArrowDownWithShiftKey(page);
|
||||
await assertRowsSelection(page, [1, 1]); // should decrement back
|
||||
|
||||
await pressArrowUp(page); // go to first row
|
||||
|
||||
await pressArrowDownWithShiftKey(page);
|
||||
await assertRowsSelection(page, [0, 1]);
|
||||
|
||||
await pressArrowUpWithShiftKey(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('cell-level selection', () => {
|
||||
test('should support multi cell selection', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
await switchColumnType(page, 'Number');
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
|
||||
const startCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 0,
|
||||
columnIndex: 0,
|
||||
});
|
||||
const endCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 1,
|
||||
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 }
|
||||
);
|
||||
|
||||
await assertCellsSelection(page, {
|
||||
start: [0, 0],
|
||||
end: [1, 1],
|
||||
});
|
||||
});
|
||||
|
||||
test("should support backspace key to delete cell's content", async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseRowWithData(page, 'row1');
|
||||
await initDatabaseDynamicRowWithData(page, 'abc', false);
|
||||
await pressEscape(page);
|
||||
await initDatabaseRowWithData(page, 'row2');
|
||||
await initDatabaseDynamicRowWithData(page, '123', false);
|
||||
await pressEscape(page);
|
||||
|
||||
const startCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 0,
|
||||
columnIndex: 0,
|
||||
});
|
||||
const endCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 1,
|
||||
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 }
|
||||
);
|
||||
|
||||
await pressBackspace(page);
|
||||
await assertDatabaseTitleColumnText(page, '', 0);
|
||||
await assertDatabaseTitleColumnText(page, '', 1);
|
||||
const selectCell1 = getDatabaseBodyCell(page, {
|
||||
rowIndex: 0,
|
||||
columnIndex: 1,
|
||||
});
|
||||
expect(await selectCell1.innerText()).toBe('');
|
||||
const selectCell2 = getDatabaseBodyCell(page, {
|
||||
rowIndex: 1,
|
||||
columnIndex: 1,
|
||||
});
|
||||
expect(await selectCell2.innerText()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('kanban view selection', () => {
|
||||
test("should support move cell's focus by arrow key(up&down) within a card", async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initKanbanViewState(page, {
|
||||
rows: ['row1'],
|
||||
columns: [
|
||||
{
|
||||
type: 'number',
|
||||
value: [1],
|
||||
},
|
||||
{
|
||||
type: 'rich-text',
|
||||
value: ['text'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await focusKanbanCardHeader(page);
|
||||
await assertKanbanCellSelected(page, {
|
||||
// group by `number` column, the first(groupIndex: 0) group is `Ungroups`
|
||||
groupIndex: 1,
|
||||
cardIndex: 0,
|
||||
cellIndex: 0,
|
||||
});
|
||||
|
||||
await pressArrowDown(page, 3);
|
||||
await assertKanbanCellSelected(page, {
|
||||
groupIndex: 1,
|
||||
cardIndex: 0,
|
||||
cellIndex: 0,
|
||||
});
|
||||
|
||||
await pressArrowUp(page);
|
||||
await assertKanbanCellSelected(page, {
|
||||
groupIndex: 1,
|
||||
cardIndex: 0,
|
||||
cellIndex: 2,
|
||||
});
|
||||
});
|
||||
|
||||
test("should support move cell's focus by arrow key(up&down) within multi cards", async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initKanbanViewState(page, {
|
||||
rows: ['row1', 'row2'],
|
||||
columns: [
|
||||
{
|
||||
type: 'number',
|
||||
value: [1, 2],
|
||||
},
|
||||
{
|
||||
type: 'rich-text',
|
||||
value: ['text'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await focusKanbanCardHeader(page);
|
||||
await pressArrowUp(page);
|
||||
await assertKanbanCellSelected(page, {
|
||||
groupIndex: 1,
|
||||
cardIndex: 1,
|
||||
cellIndex: 2,
|
||||
});
|
||||
|
||||
await pressArrowDown(page);
|
||||
await assertKanbanCellSelected(page, {
|
||||
groupIndex: 1,
|
||||
cardIndex: 0,
|
||||
cellIndex: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test("should support move cell's focus by arrow key(left&right)", async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initKanbanViewState(page, {
|
||||
rows: ['row1', 'row2', 'row3'],
|
||||
columns: [
|
||||
{
|
||||
type: 'number',
|
||||
value: [undefined, 1, 10],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await focusKanbanCardHeader(page);
|
||||
|
||||
await pressArrowRight(page, 3);
|
||||
await assertKanbanCellSelected(page, {
|
||||
groupIndex: 0,
|
||||
cardIndex: 0,
|
||||
cellIndex: 0,
|
||||
});
|
||||
|
||||
await pressArrowLeft(page);
|
||||
await assertKanbanCellSelected(page, {
|
||||
groupIndex: 2,
|
||||
cardIndex: 0,
|
||||
cellIndex: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test("should support move card's focus by arrow key(up&down)", async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initKanbanViewState(page, {
|
||||
rows: ['row1', 'row2', 'row3'],
|
||||
columns: [
|
||||
{
|
||||
type: 'number',
|
||||
value: [undefined, undefined, undefined],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await focusKanbanCardHeader(page);
|
||||
await pressEscape(page);
|
||||
await pressEscape(page);
|
||||
await assertKanbanCardSelected(page, {
|
||||
groupIndex: 0,
|
||||
cardIndex: 0,
|
||||
});
|
||||
|
||||
await pressArrowDown(page, 3);
|
||||
await assertKanbanCardSelected(page, {
|
||||
groupIndex: 0,
|
||||
cardIndex: 0,
|
||||
});
|
||||
|
||||
await pressArrowUp(page);
|
||||
await assertKanbanCardSelected(page, {
|
||||
groupIndex: 0,
|
||||
cardIndex: 2,
|
||||
});
|
||||
});
|
||||
|
||||
test("should support move card's focus by arrow key(left&right)", async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initKanbanViewState(page, {
|
||||
rows: ['row1', 'row2', 'row3'],
|
||||
columns: [
|
||||
{
|
||||
type: 'number',
|
||||
value: [undefined, 1, 10],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await focusKanbanCardHeader(page);
|
||||
await pressEscape(page);
|
||||
await pressEscape(page);
|
||||
|
||||
await pressArrowRight(page, 3);
|
||||
await assertKanbanCardSelected(page, {
|
||||
groupIndex: 0,
|
||||
cardIndex: 0,
|
||||
});
|
||||
|
||||
await pressArrowLeft(page);
|
||||
await assertKanbanCardSelected(page, {
|
||||
groupIndex: 2,
|
||||
cardIndex: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test('should support multi card selection', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initKanbanViewState(page, {
|
||||
rows: ['row1', 'row2'],
|
||||
columns: [
|
||||
{
|
||||
type: 'number',
|
||||
value: [undefined, 1],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await focusKanbanCardHeader(page);
|
||||
await pressEscape(page);
|
||||
await pressEscape(page);
|
||||
|
||||
const card = getKanbanCard(page, {
|
||||
groupIndex: 1,
|
||||
cardIndex: 0,
|
||||
});
|
||||
const box = await getBoundingBox(card);
|
||||
await shiftClick(page, {
|
||||
x: box.x + box.width / 2,
|
||||
y: box.y + box.height / 2,
|
||||
});
|
||||
|
||||
await assertKanbanCardSelected(page, {
|
||||
groupIndex: 0,
|
||||
cardIndex: 0,
|
||||
});
|
||||
await assertKanbanCardSelected(page, {
|
||||
groupIndex: 1,
|
||||
cardIndex: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test("should support move cursor in card's title by arrow key(left&right)", async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initKanbanViewState(page, {
|
||||
rows: ['row1'],
|
||||
columns: [
|
||||
{
|
||||
type: 'rich-text',
|
||||
value: ['text'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await clickKanbanCardHeader(page);
|
||||
await type(page, 'abc');
|
||||
await pressArrowLeft(page, 2);
|
||||
await pressArrowRight(page);
|
||||
await pressBackspace(page);
|
||||
await pressEscape(page);
|
||||
|
||||
await assertKanbanCardHeaderText(page, 'row1ac');
|
||||
});
|
||||
});
|
||||
112
blocksuite/tests-legacy/e2e/database/sort.spec.ts
Normal file
112
blocksuite/tests-legacy/e2e/database/sort.spec.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { expect, type Locator } from '@playwright/test';
|
||||
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
initDatabaseDynamicRowWithData,
|
||||
initEmptyDatabaseState,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import { initDatabaseColumn, switchColumnType } from './actions.js';
|
||||
|
||||
test('database sort with multiple rules', async ({ page }) => {
|
||||
// Initialize database
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
// Add test columns: Name (text) and Age (number)
|
||||
await initDatabaseColumn(page, 'Name');
|
||||
await switchColumnType(page, 'Text', 1);
|
||||
await initDatabaseColumn(page, 'Age');
|
||||
await switchColumnType(page, 'Number', 2);
|
||||
|
||||
// Add test data
|
||||
const testData = [
|
||||
{ name: 'Alice', age: '25' },
|
||||
{ name: 'Bob', age: '30' },
|
||||
{ name: 'Alice', age: '20' },
|
||||
{ name: 'Charlie', age: '25' },
|
||||
];
|
||||
|
||||
for (const data of testData) {
|
||||
await initDatabaseDynamicRowWithData(page, data.name, true, 0);
|
||||
await initDatabaseDynamicRowWithData(page, data.age, false, 1);
|
||||
}
|
||||
|
||||
// Open sort menu
|
||||
const sortButton = page.locator('data-view-header-tools-sort');
|
||||
await sortButton.click();
|
||||
|
||||
// Add first sort rule: Name ascending
|
||||
await page.locator('affine-menu').getByText('Name').click();
|
||||
await waitNextFrame(page);
|
||||
|
||||
// Add second sort rule: Age ascending
|
||||
await page.getByText('Add sort').click();
|
||||
await page.locator('affine-menu').getByText('Age').click();
|
||||
await waitNextFrame(page);
|
||||
|
||||
// Get all rows after sorting
|
||||
const rows = await page.locator('affine-database-row').all();
|
||||
const getCellText = async (row: Locator, index: number) => {
|
||||
const cell = row.locator('.cell').nth(index);
|
||||
return cell.innerText();
|
||||
};
|
||||
|
||||
// Verify sorting results
|
||||
// Should be sorted by Name first, then by Age
|
||||
const expectedOrder = [
|
||||
{ name: 'Alice', age: '20' },
|
||||
{ name: 'Alice', age: '25' },
|
||||
{ name: 'Bob', age: '30' },
|
||||
{ name: 'Charlie', age: '25' },
|
||||
];
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const name = await getCellText(rows[i], 1);
|
||||
const age = await getCellText(rows[i], 2);
|
||||
expect(name).toBe(expectedOrder[i].name);
|
||||
expect(age).toBe(expectedOrder[i].age);
|
||||
}
|
||||
|
||||
// Change sort order of Name to descending
|
||||
await page.locator('.sort-item').first().getByText('Ascending').click();
|
||||
await page.getByText('Descending').click();
|
||||
await waitNextFrame(page);
|
||||
|
||||
// Verify new sorting results
|
||||
const expectedOrderDesc = [
|
||||
{ name: 'Charlie', age: '25' },
|
||||
{ name: 'Bob', age: '30' },
|
||||
{ name: 'Alice', age: '20' },
|
||||
{ name: 'Alice', age: '25' },
|
||||
];
|
||||
|
||||
const rowsAfterDesc = await page.locator('affine-database-row').all();
|
||||
for (let i = 0; i < rowsAfterDesc.length; i++) {
|
||||
const name = await getCellText(rowsAfterDesc[i], 1);
|
||||
const age = await getCellText(rowsAfterDesc[i], 2);
|
||||
expect(name).toBe(expectedOrderDesc[i].name);
|
||||
expect(age).toBe(expectedOrderDesc[i].age);
|
||||
}
|
||||
|
||||
// Remove first sort rule
|
||||
await page.locator('.sort-item').first().getByRole('img').last().click();
|
||||
await waitNextFrame(page);
|
||||
|
||||
// Verify sorting now only by Age
|
||||
const expectedOrderAgeOnly = [
|
||||
{ name: 'Alice', age: '20' },
|
||||
{ name: 'Alice', age: '25' },
|
||||
{ name: 'Charlie', age: '25' },
|
||||
{ name: 'Bob', age: '30' },
|
||||
];
|
||||
|
||||
const rowsAfterRemove = await page.locator('affine-database-row').all();
|
||||
for (let i = 0; i < rowsAfterRemove.length; i++) {
|
||||
const name = await getCellText(rowsAfterRemove[i], 1);
|
||||
const age = await getCellText(rowsAfterRemove[i], 2);
|
||||
expect(name).toBe(expectedOrderAgeOnly[i].name);
|
||||
expect(age).toBe(expectedOrderAgeOnly[i].age);
|
||||
}
|
||||
});
|
||||
107
blocksuite/tests-legacy/e2e/database/statistics.spec.ts
Normal file
107
blocksuite/tests-legacy/e2e/database/statistics.spec.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import { type } from '../utils/actions/index.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
getAddRow,
|
||||
initEmptyDatabaseState,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import {
|
||||
changeColumnType,
|
||||
moveToCenterOf,
|
||||
press,
|
||||
pressKey,
|
||||
} from './actions.js';
|
||||
|
||||
const addRow = async (page: Page, count: number = 1) => {
|
||||
await waitNextFrame(page);
|
||||
const addRow = getAddRow(page);
|
||||
for (let i = 0; i < count; i++) {
|
||||
await addRow.click();
|
||||
}
|
||||
await press(page, 'Escape');
|
||||
await waitNextFrame(page);
|
||||
};
|
||||
const insertRightColumn = async (page: Page, index = 0) => {
|
||||
await waitNextFrame(page);
|
||||
await page.locator('affine-database-header-column').nth(index).click();
|
||||
await waitNextFrame(page, 200);
|
||||
await pressKey(page, 'Escape');
|
||||
const menu = page.locator('.affine-menu-button', {
|
||||
hasText: new RegExp('Insert Right'),
|
||||
});
|
||||
await menu.click();
|
||||
await waitNextFrame(page, 200);
|
||||
await pressKey(page, 'Enter');
|
||||
};
|
||||
const menuSelect = async (page: Page, selectors: string[]) => {
|
||||
await waitNextFrame(page);
|
||||
for (const name of selectors) {
|
||||
const menu = page.locator('.affine-menu-button', {
|
||||
hasText: new RegExp(name),
|
||||
});
|
||||
await menu.click();
|
||||
}
|
||||
};
|
||||
test.describe('title', () => {
|
||||
test('empty count', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
await addRow(page, 3);
|
||||
const statCell = page.locator('affine-database-column-stats-cell').nth(0);
|
||||
await moveToCenterOf(page, statCell);
|
||||
await statCell.click();
|
||||
await menuSelect(page, ['Count', 'Count Empty']);
|
||||
const value = statCell.locator('.value');
|
||||
expect((await value.textContent())?.trim()).toBe('3');
|
||||
await page.locator('affine-database-cell-container').nth(0).click();
|
||||
await pressKey(page, 'Enter');
|
||||
await type(page, 'asd');
|
||||
await pressKey(page, 'Escape');
|
||||
expect((await value.textContent())?.trim()).toBe('2');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('rich-text', () => {
|
||||
test('empty count', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
await addRow(page, 3);
|
||||
await insertRightColumn(page);
|
||||
await changeColumnType(page, 1, 'text');
|
||||
const statCell = page.locator('affine-database-column-stats-cell').nth(1);
|
||||
await moveToCenterOf(page, statCell);
|
||||
await statCell.click();
|
||||
await menuSelect(page, ['Count', 'Count Empty']);
|
||||
const value = statCell.locator('.value');
|
||||
expect((await value.textContent())?.trim()).toBe('3');
|
||||
await page.locator('affine-database-cell-container').nth(1).click();
|
||||
await pressKey(page, 'Enter');
|
||||
await type(page, 'asd');
|
||||
await pressKey(page, 'Escape');
|
||||
expect((await value.textContent())?.trim()).toBe('2');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('select', () => {
|
||||
test('empty count', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
await addRow(page, 3);
|
||||
await insertRightColumn(page);
|
||||
await changeColumnType(page, 1, 'select');
|
||||
const statCell = page.locator('affine-database-column-stats-cell').nth(1);
|
||||
await moveToCenterOf(page, statCell);
|
||||
await statCell.click();
|
||||
await menuSelect(page, ['Count', 'Count Empty']);
|
||||
const value = statCell.locator('.value');
|
||||
expect((await value.textContent())?.trim()).toBe('3');
|
||||
await page.locator('affine-database-cell-container').nth(1).click();
|
||||
await pressKey(page, 'Enter');
|
||||
await type(page, 'select');
|
||||
await pressKey(page, 'Enter');
|
||||
expect((await value.textContent())?.trim()).toBe('2');
|
||||
});
|
||||
});
|
||||
19
blocksuite/tests-legacy/e2e/database/title.spec.ts
Normal file
19
blocksuite/tests-legacy/e2e/database/title.spec.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
initDatabaseDynamicRowWithData,
|
||||
initEmptyDatabaseState,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import { press } from './actions.js';
|
||||
|
||||
test.describe('title', () => {
|
||||
test('should able to link doc by press @', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await press(page, '@');
|
||||
await expect(page.locator('.linked-doc-popover')).toBeVisible();
|
||||
});
|
||||
});
|
||||
767
blocksuite/tests-legacy/e2e/drag.spec.ts
Normal file
767
blocksuite/tests-legacy/e2e/drag.spec.ts
Normal file
@@ -0,0 +1,767 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
dragBetweenCoords,
|
||||
dragBetweenIndices,
|
||||
dragHandleFromBlockToBlockBottomById,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyParagraphState,
|
||||
initThreeLists,
|
||||
initThreeParagraphs,
|
||||
pressEnter,
|
||||
pressShiftTab,
|
||||
pressTab,
|
||||
type,
|
||||
} from './utils/actions/index.js';
|
||||
import {
|
||||
getBoundingClientRect,
|
||||
getEditorHostLocator,
|
||||
getPageSnapshot,
|
||||
initParagraphsByCount,
|
||||
} from './utils/actions/misc.js';
|
||||
import { assertRichTexts } from './utils/asserts.js';
|
||||
import { BLOCK_CHILDREN_CONTAINER_PADDING_LEFT } from './utils/bs-alternative.js';
|
||||
import { test } from './utils/playwright.js';
|
||||
|
||||
test('only have one drag handle in screen', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
const topLeft = await page.evaluate(() => {
|
||||
const paragraph = document.querySelector('[data-block-id="2"]');
|
||||
const box = paragraph?.getBoundingClientRect();
|
||||
if (!box) {
|
||||
throw new Error();
|
||||
}
|
||||
return { x: box.left, y: box.top + 2 };
|
||||
}, []);
|
||||
|
||||
const bottomRight = await page.evaluate(() => {
|
||||
const paragraph = document.querySelector('[data-block-id="4"]');
|
||||
const box = paragraph?.getBoundingClientRect();
|
||||
if (!box) {
|
||||
throw new Error();
|
||||
}
|
||||
return { x: box.right, y: box.bottom - 2 };
|
||||
}, []);
|
||||
|
||||
await page.mouse.move(topLeft.x, topLeft.y);
|
||||
const length1 = await page.evaluate(() => {
|
||||
const handles = document.querySelectorAll('affine-drag-handle-widget');
|
||||
return handles.length;
|
||||
}, []);
|
||||
expect(length1).toBe(1);
|
||||
await page.mouse.move(bottomRight.x, bottomRight.y);
|
||||
const length2 = await page.evaluate(() => {
|
||||
const handles = document.querySelectorAll('affine-drag-handle-widget');
|
||||
return handles.length;
|
||||
}, []);
|
||||
expect(length2).toBe(1);
|
||||
});
|
||||
|
||||
test('move drag handle in paragraphs', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
await dragHandleFromBlockToBlockBottomById(page, '2', '4');
|
||||
await expect(page.locator('.affine-drop-indicator')).toBeHidden();
|
||||
await assertRichTexts(page, ['456', '789', '123']);
|
||||
});
|
||||
|
||||
test('move drag handle in list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeLists(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
await dragHandleFromBlockToBlockBottomById(page, '5', '3', false);
|
||||
await expect(page.locator('.affine-drop-indicator')).toBeHidden();
|
||||
await assertRichTexts(page, ['123', '789', '456']);
|
||||
});
|
||||
|
||||
test('move drag handle in nested block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '-');
|
||||
await page.keyboard.press('Space', { delay: 50 });
|
||||
await type(page, '1');
|
||||
await pressEnter(page);
|
||||
await type(page, '2');
|
||||
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, '21');
|
||||
await pressEnter(page);
|
||||
await type(page, '22');
|
||||
await pressEnter(page);
|
||||
await type(page, '23');
|
||||
await pressEnter(page);
|
||||
await pressShiftTab(page);
|
||||
|
||||
await type(page, '3');
|
||||
|
||||
await assertRichTexts(page, ['1', '2', '21', '22', '23', '3']);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(page, '5', '7');
|
||||
await expect(page.locator('.affine-drop-indicator')).toBeHidden();
|
||||
await assertRichTexts(page, ['1', '2', '22', '23', '21', '3']);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(page, '3', '8');
|
||||
await expect(page.locator('.affine-drop-indicator')).toBeHidden();
|
||||
await assertRichTexts(page, ['2', '22', '23', '21', '3', '1']);
|
||||
});
|
||||
|
||||
test('move drag handle into another block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '-');
|
||||
await page.keyboard.press('Space', { delay: 50 });
|
||||
await type(page, '1');
|
||||
await pressEnter(page);
|
||||
await type(page, '2');
|
||||
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, '21');
|
||||
await pressEnter(page);
|
||||
await type(page, '22');
|
||||
await pressEnter(page);
|
||||
await type(page, '23');
|
||||
await pressEnter(page);
|
||||
await pressShiftTab(page);
|
||||
|
||||
await type(page, '3');
|
||||
|
||||
await assertRichTexts(page, ['1', '2', '21', '22', '23', '3']);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(
|
||||
page,
|
||||
'5',
|
||||
'7',
|
||||
true,
|
||||
2 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT
|
||||
);
|
||||
await expect(page.locator('.affine-drop-indicator')).toBeHidden();
|
||||
await assertRichTexts(page, ['1', '2', '22', '23', '21', '3']);
|
||||
// FIXME(DND)
|
||||
// await assertBlockChildrenIds(page, '7', ['5']);
|
||||
|
||||
// await dragHandleFromBlockToBlockBottomById(
|
||||
// page,
|
||||
// '3',
|
||||
// '8',
|
||||
// true,
|
||||
// 2 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT
|
||||
// );
|
||||
// await expect(page.locator('.affine-drop-indicator')).toBeHidden();
|
||||
// await assertRichTexts(page, ['2', '22', '23', '21', '3', '1']);
|
||||
// await assertBlockChildrenIds(page, '8', ['3']);
|
||||
});
|
||||
|
||||
test('move to the last block of each level in multi-level nesting', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '-');
|
||||
await page.keyboard.press('Space', { delay: 50 });
|
||||
await type(page, 'A');
|
||||
await pressEnter(page);
|
||||
await type(page, 'B');
|
||||
await pressEnter(page);
|
||||
await type(page, 'C');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'D');
|
||||
await pressEnter(page);
|
||||
await type(page, 'E');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'F');
|
||||
await pressEnter(page);
|
||||
await type(page, 'G');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(page, '3', '9');
|
||||
await expect(page.locator('.affine-drop-indicator')).toBeHidden();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_drag_3_9.json`
|
||||
);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(
|
||||
page,
|
||||
'4',
|
||||
'3',
|
||||
true,
|
||||
-(1 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT)
|
||||
);
|
||||
await expect(page.locator('.affine-drop-indicator')).toBeHidden();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_drag_4_3.json`
|
||||
);
|
||||
|
||||
await assertRichTexts(page, ['C', 'D', 'E', 'F', 'G', 'A', 'B']);
|
||||
await dragHandleFromBlockToBlockBottomById(
|
||||
page,
|
||||
'3',
|
||||
'4',
|
||||
true,
|
||||
-(2 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT)
|
||||
);
|
||||
await expect(page.locator('.affine-drop-indicator')).toBeHidden();
|
||||
|
||||
// FIXME(DND)
|
||||
// expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
// `${testInfo.title}_drag_3_4.json`
|
||||
// );
|
||||
//
|
||||
// await assertRichTexts(page, ['C', 'D', 'E', 'F', 'G', 'B', 'A']);
|
||||
});
|
||||
|
||||
test('should sync selected-blocks to session-manager when clicking drag handle', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await focusRichText(page, 1);
|
||||
const rect = await getBoundingClientRect(page, '[data-block-id="1"]');
|
||||
if (!rect) {
|
||||
throw new Error();
|
||||
}
|
||||
await page.mouse.move(rect.x + 10, rect.y + 10, { steps: 2 });
|
||||
|
||||
const handle = page.locator('.affine-drag-handle-container');
|
||||
await handle.click();
|
||||
|
||||
await page.keyboard.press('Backspace');
|
||||
await assertRichTexts(page, ['456', '789']);
|
||||
});
|
||||
|
||||
test.fixme(
|
||||
'should be able to drag & drop multiple blocks',
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[0, 0],
|
||||
[1, 3],
|
||||
{ x: -60, y: 0 },
|
||||
{ x: 80, y: 0 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(page, '2', '4', true);
|
||||
await expect(page.locator('.affine-drop-indicator')).toBeHidden();
|
||||
|
||||
await assertRichTexts(page, ['789', '123', '456']);
|
||||
|
||||
// Selection is still 2 after drop
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
}
|
||||
);
|
||||
|
||||
test.fixme(
|
||||
'should be able to drag & drop multiple blocks to nested block',
|
||||
async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '-');
|
||||
await page.keyboard.press('Space', { delay: 50 });
|
||||
await type(page, 'A');
|
||||
await pressEnter(page);
|
||||
await type(page, 'B');
|
||||
await pressEnter(page);
|
||||
await type(page, 'C');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'D');
|
||||
await pressEnter(page);
|
||||
await type(page, 'E');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'F');
|
||||
await pressEnter(page);
|
||||
await type(page, 'G');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[0, 0],
|
||||
[1, 1],
|
||||
{ x: -80, y: 0 },
|
||||
{ x: 80, y: 0 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(page, '3', '8');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_finial.json`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test('should blur rich-text first on starting block selection', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await expect(page.locator('*:focus')).toHaveCount(1);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(page, '2', '4');
|
||||
await expect(page.locator('.affine-drop-indicator')).toBeHidden();
|
||||
await assertRichTexts(page, ['456', '789', '123']);
|
||||
|
||||
await expect(page.locator('*:focus')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('hide drag handle when mouse is hovering over the title', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
|
||||
const rect = await getBoundingClientRect(
|
||||
page,
|
||||
'.affine-note-block-container'
|
||||
);
|
||||
const dragHandle = page.locator('.affine-drag-handle-container');
|
||||
// When there is a gap between paragraph blocks, it is the correct behavior for the drag handle to appear
|
||||
// when the mouse is over the gap. Therefore, we use rect.y - 20 to make the Y offset greater than the gap between the
|
||||
// paragraph blocks.
|
||||
await page.mouse.move(rect.x, rect.y - 20, { steps: 2 });
|
||||
await expect(dragHandle).toBeHidden();
|
||||
|
||||
await page.mouse.move(rect.x, rect.y, { steps: 2 });
|
||||
expect(await dragHandle.isVisible()).toBe(true);
|
||||
await expect(dragHandle).toBeVisible();
|
||||
});
|
||||
|
||||
test.fixme('should create preview when dragging', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
const dragPreview = page.locator('affine-drag-preview');
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[0, 0],
|
||||
[1, 3],
|
||||
{ x: -60, y: 0 },
|
||||
{ x: 80, y: 0 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(
|
||||
page,
|
||||
'2',
|
||||
'4',
|
||||
true,
|
||||
undefined,
|
||||
async () => {
|
||||
await expect(dragPreview).toBeVisible();
|
||||
await expect(dragPreview.locator('[data-block-id]')).toHaveCount(4);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test.fixme(
|
||||
'should drag and drop blocks under block-level selection',
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[0, 0],
|
||||
[1, 3],
|
||||
{ x: -60, y: 0 },
|
||||
{ x: 80, y: 0 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
|
||||
const editorHost = getEditorHostLocator(page);
|
||||
const editors = editorHost.locator('rich-text');
|
||||
const editorRect0 = await editors.nth(0).boundingBox();
|
||||
const editorRect2 = await editors.nth(2).boundingBox();
|
||||
if (!editorRect0 || !editorRect2) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: editorRect0.x - 10,
|
||||
y: editorRect0.y + editorRect0.height / 2,
|
||||
},
|
||||
{
|
||||
x: editorRect2.x + 10,
|
||||
y: editorRect2.y + editorRect2.height / 2 + 10,
|
||||
},
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
await assertRichTexts(page, ['789', '123', '456']);
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
}
|
||||
);
|
||||
|
||||
test('should trigger click event on editor container when clicking on blocks under block-level selection', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[0, 0],
|
||||
[1, 3],
|
||||
{ x: -60, y: 0 },
|
||||
{ x: 80, y: 0 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
await expect(page.locator('*:focus')).toHaveCount(0);
|
||||
|
||||
const editorHost = getEditorHostLocator(page);
|
||||
const editors = editorHost.locator('rich-text');
|
||||
const editorRect0 = await editors.nth(0).boundingBox();
|
||||
if (!editorRect0) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
await page.mouse.move(
|
||||
editorRect0.x + 10,
|
||||
editorRect0.y + editorRect0.height / 2
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
await expect(blockSelections).toHaveCount(0);
|
||||
await expect(page.locator('*:focus')).toHaveCount(1);
|
||||
});
|
||||
|
||||
test('should get to selected block when dragging unselected block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '123');
|
||||
await pressEnter(page);
|
||||
await type(page, '456');
|
||||
await assertRichTexts(page, ['123', '456']);
|
||||
|
||||
const editorHost = getEditorHostLocator(page);
|
||||
const editors = editorHost.locator('rich-text');
|
||||
const editorRect0 = await editors.nth(0).boundingBox();
|
||||
const editorRect1 = await editors.nth(1).boundingBox();
|
||||
|
||||
if (!editorRect0 || !editorRect1) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
await page.mouse.move(editorRect1.x - 5, editorRect0.y);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(1);
|
||||
|
||||
await page.mouse.move(editorRect1.x - 5, editorRect0.y);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(
|
||||
editorRect1.x - 5,
|
||||
editorRect1.y + editorRect1.height / 2 + 1,
|
||||
{
|
||||
steps: 10,
|
||||
}
|
||||
);
|
||||
await page.mouse.up();
|
||||
|
||||
await expect(blockSelections).toHaveCount(1);
|
||||
|
||||
// FIXME(DND)
|
||||
// await assertRichTexts(page, ['456', '123']);
|
||||
});
|
||||
|
||||
test.fixme(
|
||||
'should clear the currently selected block when clicked again',
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '123');
|
||||
await pressEnter(page);
|
||||
await type(page, '456');
|
||||
await assertRichTexts(page, ['123', '456']);
|
||||
|
||||
const editorHost = getEditorHostLocator(page);
|
||||
const editors = editorHost.locator('rich-text');
|
||||
const editorRect0 = await editors.nth(0).boundingBox();
|
||||
const editorRect1 = await editors.nth(1).boundingBox();
|
||||
|
||||
if (!editorRect0 || !editorRect1) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
await page.mouse.move(
|
||||
editorRect1.x + 5,
|
||||
editorRect1.y + editorRect1.height / 2
|
||||
);
|
||||
|
||||
await page.mouse.move(
|
||||
editorRect1.x - 10,
|
||||
editorRect1.y + editorRect1.height / 2
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(1);
|
||||
|
||||
let selectedBlockRect = await blockSelections.nth(0).boundingBox();
|
||||
|
||||
if (!selectedBlockRect) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
expect(editorRect1).toEqual(selectedBlockRect);
|
||||
|
||||
await page.mouse.move(
|
||||
editorRect0.x - 10,
|
||||
editorRect0.y + editorRect0.height / 2
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
|
||||
await expect(blockSelections).toHaveCount(1);
|
||||
|
||||
selectedBlockRect = await blockSelections.nth(0).boundingBox();
|
||||
|
||||
if (!selectedBlockRect) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
expect(editorRect0).toEqual(selectedBlockRect);
|
||||
}
|
||||
);
|
||||
|
||||
test.fixme(
|
||||
'should support moving blocks from multiple notes',
|
||||
async ({ page }) => {
|
||||
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);
|
||||
|
||||
['123', '456', '789', '987', '654', '321'].forEach(text => {
|
||||
const noteId = doc.addBlock('affine:note', {}, rootId);
|
||||
doc.addBlock(
|
||||
'affine:paragraph',
|
||||
{
|
||||
text: new window.$blocksuite.store.Text(text),
|
||||
},
|
||||
noteId
|
||||
);
|
||||
});
|
||||
|
||||
doc.resetHistory();
|
||||
});
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[1, 0],
|
||||
[2, 3],
|
||||
{ x: -60, y: 0 },
|
||||
{ x: 80, y: 0 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
|
||||
const editorHost = getEditorHostLocator(page);
|
||||
const editors = editorHost.locator('rich-text');
|
||||
const editorRect1 = await editors.nth(1).boundingBox();
|
||||
const editorRect3 = await editors.nth(3).boundingBox();
|
||||
if (!editorRect1 || !editorRect3) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: editorRect1.x - 10,
|
||||
y: editorRect1.y + editorRect1.height / 2,
|
||||
},
|
||||
{
|
||||
x: editorRect3.x + 10,
|
||||
y: editorRect3.y + editorRect3.height / 2 + 10,
|
||||
},
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
await assertRichTexts(page, ['123', '987', '456', '789', '654', '321']);
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[5, 0],
|
||||
[4, 3],
|
||||
{ x: -60, y: 0 },
|
||||
{ x: 80, y: 0 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const editorRect0 = await editors.nth(0).boundingBox();
|
||||
const editorRect5 = await editors.nth(5).boundingBox();
|
||||
if (!editorRect0 || !editorRect5) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: editorRect5.x - 10,
|
||||
y: editorRect5.y + editorRect5.height / 2,
|
||||
},
|
||||
{
|
||||
x: editorRect0.x + 10,
|
||||
y: editorRect0.y + editorRect0.height / 2 - 5,
|
||||
},
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
await assertRichTexts(page, ['654', '321', '123', '987', '456', '789']);
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
}
|
||||
);
|
||||
|
||||
test('drag handle should show on right block when scroll viewport', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initParagraphsByCount(page, 30);
|
||||
|
||||
await page.mouse.wheel(0, 200);
|
||||
|
||||
const editorHost = getEditorHostLocator(page);
|
||||
const editors = editorHost.locator('rich-text');
|
||||
const blockRect28 = await editors.nth(28).boundingBox();
|
||||
if (!blockRect28) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
await page.mouse.move(blockRect28.x + 10, blockRect28.y + 10);
|
||||
const dragHandle = page.locator('.affine-drag-handle-container');
|
||||
await expect(dragHandle).toBeVisible();
|
||||
|
||||
await page.mouse.move(
|
||||
blockRect28.x - 10,
|
||||
blockRect28.y + blockRect28.height / 2
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(1);
|
||||
|
||||
const selectedBlockRect = await blockSelections.nth(0).boundingBox();
|
||||
|
||||
if (!selectedBlockRect) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
expect(blockRect28).toEqual(selectedBlockRect);
|
||||
});
|
||||
435
blocksuite/tests-legacy/e2e/edgeless/align.spec.ts
Normal file
435
blocksuite/tests-legacy/e2e/edgeless/align.spec.ts
Normal file
@@ -0,0 +1,435 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addBasicBrushElement,
|
||||
createConnectorElement,
|
||||
createFrameElement,
|
||||
createNote,
|
||||
createShapeElement,
|
||||
setEdgelessTool,
|
||||
Shape,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
clickView,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessSelectedModelRect,
|
||||
getSelectedRect,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('auto arrange align', () => {
|
||||
test('arrange shapes', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond);
|
||||
await createShapeElement(page, [100, -100], [300, 100], Shape.Ellipse);
|
||||
await createShapeElement(page, [200, 300], [300, 400], Shape.Square);
|
||||
await createShapeElement(page, [400, 100], [500, 200], Shape.Triangle);
|
||||
await createShapeElement(
|
||||
page,
|
||||
[0, 200],
|
||||
[100, 300],
|
||||
Shape['Rounded rectangle']
|
||||
);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, -100, 500, 500]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoArrange');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 560, 320]);
|
||||
});
|
||||
|
||||
test('arrange rotated shapes', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Ellipse);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
|
||||
const point = await toViewCoord(page, [100, 100]);
|
||||
await page.mouse.click(point[0] + 50, point[1] + 50);
|
||||
await page.mouse.move(point[0] - 5, point[1] - 5);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(point[0] - 5, point[1] + 45);
|
||||
await page.mouse.up();
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 220, 220]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoArrange');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 261, 141]);
|
||||
});
|
||||
|
||||
test('arrange connected shapes', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Ellipse);
|
||||
await createConnectorElement(page, [50, 100], [150, 100]);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 200, 200]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoArrange');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, -21, 220, 141.4]);
|
||||
});
|
||||
|
||||
test('arrange connector', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [200, 200], [300, 200]);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 300, 200]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoArrange');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 220, 100]);
|
||||
});
|
||||
|
||||
test('arrange edgeless text', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
|
||||
const point = await toViewCoord(page, [200, -100]);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.dblclick(point[0], point[1], {
|
||||
delay: 100,
|
||||
});
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'a');
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, -125, 395, 225]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoArrange');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 340, 100]);
|
||||
});
|
||||
|
||||
test('arrange note', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [200, 200], 'Hello World');
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 668, 252]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoArrange');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 618, 100]);
|
||||
});
|
||||
|
||||
test('arrange group', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [200, 300], [300, 400], Shape.Square);
|
||||
await createShapeElement(page, [400, 100], [500, 200], Shape.Triangle);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond);
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 500, 400]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoArrange');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 420, 300]);
|
||||
});
|
||||
|
||||
test('arrange frame', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [200, 300], [300, 400], Shape.Square);
|
||||
await createShapeElement(page, [400, 100], [500, 200], Shape.Triangle);
|
||||
await selectAllByKeyboard(page);
|
||||
await createFrameElement(page, [150, 50], [550, 450]);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await page.mouse.move(75, 395);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(900, 900);
|
||||
await page.mouse.up();
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 550, 450]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoArrange');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 520, 400]);
|
||||
});
|
||||
|
||||
// TODO mindmap size different on CI
|
||||
test('arrange mindmap', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond);
|
||||
await page.keyboard.press('m');
|
||||
await clickView(page, [500, 200]);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
const box1 = await getSelectedRect(page);
|
||||
expect(box1.width).toBeGreaterThan(700);
|
||||
expect(box1.height).toBeGreaterThan(300);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoArrange');
|
||||
await waitNextFrame(page, 200);
|
||||
const box2 = await getSelectedRect(page);
|
||||
expect(box2.width).toBeLessThan(550);
|
||||
expect(box2.height).toBeLessThan(210);
|
||||
});
|
||||
|
||||
test('arrange shape, note, connector, brush and edgeless text', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
// shape
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [150, 150], [300, 300], Shape.Ellipse);
|
||||
//note
|
||||
await createNote(page, [200, 100], 'Hello World');
|
||||
// connector
|
||||
await createConnectorElement(page, [200, -200], [400, -100]);
|
||||
// brush
|
||||
const start = { x: 400, y: 400 };
|
||||
const end = { x: 480, y: 480 };
|
||||
await addBasicBrushElement(page, start, end);
|
||||
// edgeless text
|
||||
const point = await toViewCoord(page, [-100, -100]);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.dblclick(point[0], point[1], {
|
||||
delay: 100,
|
||||
});
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'edgeless text');
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await selectAllByKeyboard(page);
|
||||
|
||||
await assertEdgelessSelectedModelRect(page, [-125, -200, 793, 500]);
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoArrange');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [-125, -125, 668, 270]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('auto resize align', () => {
|
||||
test('resize and arrange shapes', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond);
|
||||
await createShapeElement(page, [100, -100], [300, 100], Shape.Ellipse);
|
||||
await createShapeElement(page, [200, 300], [300, 400], Shape.Square);
|
||||
await createShapeElement(page, [400, 100], [500, 200], Shape.Triangle);
|
||||
await createShapeElement(
|
||||
page,
|
||||
[0, 200],
|
||||
[100, 300],
|
||||
Shape['Rounded rectangle']
|
||||
);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await selectAllByKeyboard(page);
|
||||
|
||||
await assertEdgelessSelectedModelRect(page, [0, -100, 500, 500]);
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoResize');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 860, 420]);
|
||||
});
|
||||
|
||||
test('resize and arrange rotated shapes', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Ellipse);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
|
||||
const point = await toViewCoord(page, [100, 100]);
|
||||
await page.mouse.click(point[0] + 50, point[1] + 50);
|
||||
await page.mouse.move(point[0] - 5, point[1] - 5);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(point[0] - 5, point[1] + 45);
|
||||
await page.mouse.up();
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 220, 220]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoResize');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 420, 200]);
|
||||
});
|
||||
|
||||
test('resize and arrange connected shapes', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Ellipse);
|
||||
await createConnectorElement(page, [50, 100], [150, 100]);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 200, 200]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoResize');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, -16, 420, 232]);
|
||||
});
|
||||
|
||||
test('resize and arrange connector', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [200, 200], [300, 200]);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 300, 200]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoResize');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 320, 200]);
|
||||
});
|
||||
|
||||
test('resize and arrange edgeless text', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
|
||||
const point = await toViewCoord(page, [200, -100]);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.dblclick(point[0], point[1], {
|
||||
delay: 100,
|
||||
});
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'a');
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, -125, 395, 225]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoResize');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 1912.296875, 200]);
|
||||
});
|
||||
|
||||
test('resize and arrange note', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [200, 200], 'Hello World');
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 668, 252]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoResize');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 1302.5, 200]);
|
||||
});
|
||||
|
||||
test('resize and arrange group', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [200, 300], [300, 400], Shape.Square);
|
||||
await createShapeElement(page, [400, 100], [500, 200], Shape.Triangle);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond);
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 500, 400]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoResize');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 420, 200]);
|
||||
});
|
||||
|
||||
test('resize and arrange frame', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [200, 300], [300, 400], Shape.Square);
|
||||
await createShapeElement(page, [400, 100], [500, 200], Shape.Triangle);
|
||||
await selectAllByKeyboard(page);
|
||||
await createFrameElement(page, [150, 50], [550, 450]);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await page.mouse.move(75, 395);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(900, 900);
|
||||
await page.mouse.up();
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 550, 450]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoResize');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 420, 200]);
|
||||
});
|
||||
|
||||
// TODO mindmap size different on CI
|
||||
test('resize and arrange mindmap', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond);
|
||||
await page.keyboard.press('m');
|
||||
await clickView(page, [500, 200]);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
const box1 = await getSelectedRect(page);
|
||||
expect(box1.width).toBeGreaterThan(700);
|
||||
expect(box1.height).toBeGreaterThan(300);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoResize');
|
||||
await waitNextFrame(page, 200);
|
||||
const box2 = await getSelectedRect(page);
|
||||
expect(box2.width).toBeLessThan(650);
|
||||
expect(box2.height).toBeLessThan(210);
|
||||
});
|
||||
|
||||
test('resize and arrange shape, note, connector, brush and text', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
// shape
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [150, 150], [300, 300], Shape.Ellipse);
|
||||
//note
|
||||
await createNote(page, [200, 100], 'Hello World');
|
||||
// connector
|
||||
await createConnectorElement(page, [200, -200], [400, -100]);
|
||||
// brush
|
||||
const start = { x: 400, y: 400 };
|
||||
const end = { x: 480, y: 480 };
|
||||
await addBasicBrushElement(page, start, end);
|
||||
// edgeless text
|
||||
const point = await toViewCoord(page, [-100, -100]);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.dblclick(point[0], point[1], {
|
||||
delay: 100,
|
||||
});
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'edgeless text');
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await selectAllByKeyboard(page);
|
||||
|
||||
await assertEdgelessSelectedModelRect(page, [-125, -200, 793, 500]);
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoResize');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 2352.296875, 420]);
|
||||
});
|
||||
});
|
||||
250
blocksuite/tests-legacy/e2e/edgeless/auto-complete.spec.ts
Normal file
250
blocksuite/tests-legacy/e2e/edgeless/auto-complete.spec.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
import { lightThemeV2 } from '@toeverything/theme/v2';
|
||||
|
||||
import { clickView, moveView } from '../utils/actions/click.js';
|
||||
import { dragBetweenCoords } from '../utils/actions/drag.js';
|
||||
import {
|
||||
addNote,
|
||||
changeEdgelessNoteBackground,
|
||||
changeShapeFillColor,
|
||||
changeShapeStrokeColor,
|
||||
createShapeElement,
|
||||
deleteAll,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
getEdgelessSelectedRectModel,
|
||||
Shape,
|
||||
switchEditorMode,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
waitForInlineEditorStateUpdated,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/misc.js';
|
||||
import {
|
||||
assertConnectorStrokeColor,
|
||||
assertEdgelessCanvasText,
|
||||
assertEdgelessNoteBackground,
|
||||
assertExists,
|
||||
assertRichTexts,
|
||||
assertSelectedBound,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
function getAutoCompletePanelButton(page: Page, type: string) {
|
||||
return page
|
||||
.locator('.auto-complete-panel-container')
|
||||
.locator('edgeless-tool-icon-button')
|
||||
.filter({ hasText: `${type}` });
|
||||
}
|
||||
|
||||
test.describe('auto-complete', () => {
|
||||
test.describe('click on auto-complete button', () => {
|
||||
test('click on right auto-complete button', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
await clickView(page, [120, 50]);
|
||||
await assertSelectedBound(page, [200, 0, 100, 100]);
|
||||
});
|
||||
test('click on bottom auto-complete button', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
await clickView(page, [50, 120]);
|
||||
await assertSelectedBound(page, [0, 200, 100, 100]);
|
||||
});
|
||||
test('click on left auto-complete button', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
await clickView(page, [-20, 50]);
|
||||
await assertSelectedBound(page, [-200, 0, 100, 100]);
|
||||
});
|
||||
test('click on top auto-complete button', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
await clickView(page, [50, -20]);
|
||||
await assertSelectedBound(page, [0, -200, 100, 100]);
|
||||
});
|
||||
|
||||
test('click on note auto-complete button', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await addNote(page, 'note', 100, 100);
|
||||
await page.mouse.click(600, 50);
|
||||
await page.mouse.click(300, 50);
|
||||
await page.mouse.click(150, 120);
|
||||
const rect = await getEdgelessSelectedRectModel(page);
|
||||
await moveView(page, [rect[0] + rect[2] + 30, rect[1] + rect[3] / 2]);
|
||||
await clickView(page, [rect[0] + rect[2] + 30, rect[1] + rect[3] / 2]);
|
||||
const newRect = await getEdgelessSelectedRectModel(page);
|
||||
expect(rect[0]).not.toEqual(newRect[0]);
|
||||
expect(rect[1]).toEqual(newRect[1]);
|
||||
expect(rect[2]).toEqual(newRect[2]);
|
||||
expect(rect[3]).toEqual(newRect[3]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('drag on auto-complete button', () => {
|
||||
test('drag on right auto-complete button to add shape', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
await dragBetweenViewCoords(page, [120, 50], [200, 0]);
|
||||
|
||||
const ellipseButton = getAutoCompletePanelButton(page, 'ellipse');
|
||||
await expect(ellipseButton).toBeVisible();
|
||||
await ellipseButton.click();
|
||||
|
||||
await assertSelectedBound(page, [200, -50, 100, 100]);
|
||||
});
|
||||
|
||||
test('drag on right auto-complete button to add canvas text', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await page.evaluate(() => {
|
||||
window.doc
|
||||
.get(window.$blocksuite.blocks.FeatureFlagService)
|
||||
.setFlag('enable_edgeless_text', false);
|
||||
});
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await deleteAll(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
await dragBetweenViewCoords(page, [120, 50], [200, 0]);
|
||||
|
||||
const canvasTextButton = getAutoCompletePanelButton(page, 'text');
|
||||
await expect(canvasTextButton).toBeVisible();
|
||||
await canvasTextButton.click();
|
||||
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
await waitNextFrame(page);
|
||||
await page.keyboard.type('hello');
|
||||
await assertEdgelessCanvasText(page, 'hello');
|
||||
});
|
||||
|
||||
test('drag on right auto-complete button to add note', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
await triggerComponentToolbarAction(page, 'changeShapeStrokeColor');
|
||||
await changeShapeStrokeColor(page, 'MediumRed');
|
||||
await triggerComponentToolbarAction(page, 'changeShapeFillColor');
|
||||
await changeShapeFillColor(page, 'HeavyGreen');
|
||||
await dragBetweenViewCoords(page, [120, 50], [200, 0]);
|
||||
|
||||
const noteButton = getAutoCompletePanelButton(page, 'note');
|
||||
await expect(noteButton).toBeVisible();
|
||||
await noteButton.click();
|
||||
await waitNextFrame(page);
|
||||
|
||||
const edgelessNote = page.locator('affine-edgeless-note');
|
||||
|
||||
expect(await edgelessNote.count()).toBe(1);
|
||||
const [x, y] = await toViewCoord(page, [240, 20]);
|
||||
await page.mouse.click(x, y);
|
||||
await page.keyboard.type('hello');
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
const noteId = await page.evaluate(() => {
|
||||
const note = document.body.querySelector('affine-edgeless-note');
|
||||
return note?.getAttribute('data-block-id');
|
||||
});
|
||||
assertExists(noteId);
|
||||
await assertEdgelessNoteBackground(
|
||||
page,
|
||||
noteId,
|
||||
lightThemeV2['edgeless/note/white']
|
||||
);
|
||||
|
||||
const rect = await edgelessNote.boundingBox();
|
||||
assertExists(rect);
|
||||
|
||||
// blur note block
|
||||
await page.mouse.click(rect.x + rect.width / 2, rect.y + rect.height * 3);
|
||||
await waitNextFrame(page);
|
||||
|
||||
// select connector
|
||||
await dragBetweenViewCoords(page, [140, 50], [160, 0]);
|
||||
await waitNextFrame(page);
|
||||
await assertConnectorStrokeColor(
|
||||
page,
|
||||
'MediumRed',
|
||||
lightThemeV2['edgeless/palette/medium/redMedium']
|
||||
);
|
||||
|
||||
// select note block
|
||||
await page.mouse.click(rect.x + rect.width / 2, rect.y + rect.height / 2);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeNoteColor');
|
||||
await changeEdgelessNoteBackground(page, 'Red');
|
||||
|
||||
// move to arrow icon
|
||||
await page.mouse.move(
|
||||
rect.x + rect.width + 20,
|
||||
rect.y + rect.height / 2,
|
||||
{ steps: 5 }
|
||||
);
|
||||
await waitNextFrame(page);
|
||||
|
||||
// drag arrow
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: rect.x + rect.width + 20,
|
||||
y: rect.y + rect.height / 2,
|
||||
},
|
||||
{
|
||||
x: rect.x + rect.width + 20 + 50,
|
||||
y: rect.y + rect.height / 2 + 50,
|
||||
}
|
||||
);
|
||||
|
||||
// `Add a same object` button has the same type.
|
||||
const noteButton2 = getAutoCompletePanelButton(page, 'note').nth(0);
|
||||
await expect(noteButton2).toBeVisible();
|
||||
await noteButton2.click();
|
||||
await waitNextFrame(page);
|
||||
|
||||
const noteId2 = await page.evaluate(() => {
|
||||
const note = document.body.querySelectorAll('affine-edgeless-note')[1];
|
||||
return note?.getAttribute('data-block-id');
|
||||
});
|
||||
assertExists(noteId2);
|
||||
await assertEdgelessNoteBackground(
|
||||
page,
|
||||
noteId,
|
||||
lightThemeV2['edgeless/note/red']
|
||||
);
|
||||
|
||||
expect(await edgelessNote.count()).toBe(2);
|
||||
});
|
||||
|
||||
test('drag on right auto-complete button to add frame', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
await dragBetweenViewCoords(page, [120, 50], [200, 0]);
|
||||
|
||||
expect(await page.locator('.affine-frame-container').count()).toBe(0);
|
||||
|
||||
const frameButton = getAutoCompletePanelButton(page, 'frame');
|
||||
await expect(frameButton).toBeVisible();
|
||||
await frameButton.click();
|
||||
|
||||
expect(await page.locator('.affine-frame-container').count()).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
180
blocksuite/tests-legacy/e2e/edgeless/auto-connect.spec.ts
Normal file
180
blocksuite/tests-legacy/e2e/edgeless/auto-connect.spec.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addNote,
|
||||
changeNoteDisplayModeWithId,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
getNoteBoundBoxInEdgeless,
|
||||
getSelectedBound,
|
||||
selectNoteInEdgeless,
|
||||
zoomResetByKeyboard,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import { assertSelectedBound } from '../utils/asserts.js';
|
||||
import { NoteDisplayMode } from '../utils/bs-alternative.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('auto-connect', () => {
|
||||
async function init(page: Page) {
|
||||
await edgelessCommonSetup(page);
|
||||
}
|
||||
test('navigator', async ({ page }) => {
|
||||
await init(page);
|
||||
const id1 = await addNote(page, 'page1', 200, 300);
|
||||
const id2 = await addNote(page, 'page2', 300, 500);
|
||||
const id3 = await addNote(page, 'page3', 400, 700);
|
||||
|
||||
await page.mouse.click(200, 50);
|
||||
// Notes added in edgeless mode only visible in edgeless mode
|
||||
// To use index label navigator, we need to change display mode to PageAndEdgeless
|
||||
await changeNoteDisplayModeWithId(
|
||||
page,
|
||||
id1,
|
||||
NoteDisplayMode.DocAndEdgeless
|
||||
);
|
||||
await changeNoteDisplayModeWithId(
|
||||
page,
|
||||
id2,
|
||||
NoteDisplayMode.DocAndEdgeless
|
||||
);
|
||||
await changeNoteDisplayModeWithId(
|
||||
page,
|
||||
id3,
|
||||
NoteDisplayMode.DocAndEdgeless
|
||||
);
|
||||
|
||||
await selectNoteInEdgeless(page, id1);
|
||||
const bound = await getSelectedBound(page, 0);
|
||||
await page.locator('.page-visible-index-label').nth(0).click();
|
||||
await assertSelectedBound(page, bound);
|
||||
|
||||
await page.locator('.edgeless-auto-connect-next-button').click();
|
||||
bound[0] += 100;
|
||||
bound[1] += 200;
|
||||
await assertSelectedBound(page, bound);
|
||||
|
||||
await page.locator('.edgeless-auto-connect-next-button').click();
|
||||
bound[0] += 100;
|
||||
bound[1] += 200;
|
||||
await assertSelectedBound(page, bound);
|
||||
});
|
||||
|
||||
test('should display index label when select note', async ({ page }) => {
|
||||
await init(page);
|
||||
const id1 = await addNote(page, 'page1', 200, 300);
|
||||
const id2 = await addNote(page, 'page2', 300, 500);
|
||||
|
||||
await page.mouse.click(200, 50);
|
||||
|
||||
await changeNoteDisplayModeWithId(
|
||||
page,
|
||||
id1,
|
||||
NoteDisplayMode.DocAndEdgeless
|
||||
);
|
||||
|
||||
await selectNoteInEdgeless(page, id2);
|
||||
const edgelessOnlyIndexLabel = page.locator('.edgeless-only-index-label');
|
||||
await expect(edgelessOnlyIndexLabel).toBeVisible();
|
||||
await expect(edgelessOnlyIndexLabel).toHaveCount(1);
|
||||
|
||||
await selectNoteInEdgeless(page, id1);
|
||||
const pageVisibleIndexLabel = page.locator('.page-visible-index-label');
|
||||
await expect(pageVisibleIndexLabel).toBeVisible();
|
||||
await expect(pageVisibleIndexLabel).toHaveCount(1);
|
||||
});
|
||||
|
||||
test('should hide index label when dragging note', async ({ page }) => {
|
||||
await init(page);
|
||||
const id1 = await addNote(page, 'page1', 200, 300);
|
||||
|
||||
await page.mouse.click(200, 50);
|
||||
|
||||
await changeNoteDisplayModeWithId(
|
||||
page,
|
||||
id1,
|
||||
NoteDisplayMode.DocAndEdgeless
|
||||
);
|
||||
|
||||
const pageVisibleIndexLabel = page.locator('.page-visible-index-label');
|
||||
await expect(pageVisibleIndexLabel).toBeVisible();
|
||||
await expect(pageVisibleIndexLabel).toHaveCount(1);
|
||||
|
||||
const bound = await getNoteBoundBoxInEdgeless(page, id1);
|
||||
await page.mouse.move(
|
||||
bound.x + bound.width / 2,
|
||||
bound.y + bound.height / 2
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(
|
||||
bound.x + bound.width * 2,
|
||||
bound.y + bound.height * 2
|
||||
);
|
||||
|
||||
await expect(pageVisibleIndexLabel).not.toBeVisible();
|
||||
|
||||
await page.mouse.up();
|
||||
await expect(pageVisibleIndexLabel).toBeVisible();
|
||||
});
|
||||
|
||||
test('should update index label position after dragging', async ({
|
||||
page,
|
||||
}) => {
|
||||
await init(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
const id1 = await addNote(page, 'page1', 200, 300);
|
||||
const id2 = await addNote(page, 'page2', 300, 500);
|
||||
|
||||
await page.mouse.click(200, 50);
|
||||
|
||||
await changeNoteDisplayModeWithId(
|
||||
page,
|
||||
id1,
|
||||
NoteDisplayMode.DocAndEdgeless
|
||||
);
|
||||
|
||||
await selectNoteInEdgeless(page, id2);
|
||||
const edgelessOnlyIndexLabel = page.locator('.edgeless-only-index-label');
|
||||
await expect(edgelessOnlyIndexLabel).toBeVisible();
|
||||
|
||||
// check initial index label position
|
||||
const noteBound = await getNoteBoundBoxInEdgeless(page, id2);
|
||||
const edgelessOnlyIndexLabelBound =
|
||||
await edgelessOnlyIndexLabel.boundingBox();
|
||||
assertExists(edgelessOnlyIndexLabelBound);
|
||||
const border = 1;
|
||||
const offset = 16;
|
||||
expect(edgelessOnlyIndexLabelBound.x).toBeCloseTo(
|
||||
noteBound.x +
|
||||
noteBound.width / 2 -
|
||||
edgelessOnlyIndexLabelBound.width / 2 +
|
||||
border
|
||||
);
|
||||
expect(edgelessOnlyIndexLabelBound.y).toBeCloseTo(
|
||||
noteBound.y + noteBound.height + offset
|
||||
);
|
||||
|
||||
// move note
|
||||
await dragBetweenViewCoords(
|
||||
page,
|
||||
[noteBound.x + noteBound.width / 2, noteBound.y + noteBound.height / 2],
|
||||
[noteBound.x + noteBound.width, noteBound.y + noteBound.height]
|
||||
);
|
||||
|
||||
// check new index label position
|
||||
const newNoteBound = await getNoteBoundBoxInEdgeless(page, id2);
|
||||
const newEdgelessOnlyIndexLabelBound =
|
||||
await edgelessOnlyIndexLabel.boundingBox();
|
||||
assertExists(newEdgelessOnlyIndexLabelBound);
|
||||
expect(newEdgelessOnlyIndexLabelBound.x).toBeCloseTo(
|
||||
newNoteBound.x +
|
||||
newNoteBound.width / 2 -
|
||||
newEdgelessOnlyIndexLabelBound.width / 2 +
|
||||
border
|
||||
);
|
||||
expect(newEdgelessOnlyIndexLabelBound.y).toBeCloseTo(
|
||||
newNoteBound.y + newNoteBound.height + offset
|
||||
);
|
||||
});
|
||||
});
|
||||
407
blocksuite/tests-legacy/e2e/edgeless/basic.spec.ts
Normal file
407
blocksuite/tests-legacy/e2e/edgeless/basic.spec.ts
Normal file
@@ -0,0 +1,407 @@
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
createShapeElement,
|
||||
decreaseZoomLevel,
|
||||
deleteAll,
|
||||
edgelessCommonSetup,
|
||||
increaseZoomLevel,
|
||||
locatorEdgelessComponentToolButton,
|
||||
multiTouchDown,
|
||||
multiTouchMove,
|
||||
multiTouchUp,
|
||||
optionMouseDrag,
|
||||
Shape,
|
||||
shiftClickView,
|
||||
switchEditorMode,
|
||||
ZOOM_BAR_RESPONSIVE_SCREEN_WIDTH,
|
||||
zoomByMouseWheel,
|
||||
zoomResetByKeyboard,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
addBasicBrushElement,
|
||||
addBasicRectShapeElement,
|
||||
captureHistory,
|
||||
clickView,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyEdgelessState,
|
||||
redoByClick,
|
||||
switchReadonly,
|
||||
type,
|
||||
undoByClick,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessNonSelectedRect,
|
||||
assertEdgelessSelectedModelRect,
|
||||
assertEdgelessSelectedRect,
|
||||
assertNoteXYWH,
|
||||
assertRichTextInlineRange,
|
||||
assertRichTexts,
|
||||
assertSelectedBound,
|
||||
assertZoomLevel,
|
||||
} from '../utils/asserts.js';
|
||||
import {
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
} from '../utils/bs-alternative.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
const CENTER_X = 450;
|
||||
const CENTER_Y = 450;
|
||||
|
||||
test('switch to edgeless mode', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['hello']);
|
||||
await assertRichTextInlineRange(page, 0, 5, 0);
|
||||
|
||||
await switchEditorMode(page);
|
||||
const locator = page.locator('affine-edgeless-root gfx-viewport');
|
||||
await expect(locator).toHaveCount(1);
|
||||
await assertRichTexts(page, ['hello']);
|
||||
await waitNextFrame(page);
|
||||
|
||||
// FIXME: got very flaky result on cursor keeping
|
||||
// await assertNativeSelectionRangeCount(page, 1);
|
||||
});
|
||||
|
||||
test('can zoom viewport', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]);
|
||||
|
||||
await page.mouse.click(CENTER_X, CENTER_Y);
|
||||
const original = [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT];
|
||||
await assertEdgelessSelectedModelRect(page, original);
|
||||
await assertZoomLevel(page, 100);
|
||||
|
||||
await decreaseZoomLevel(page);
|
||||
await assertZoomLevel(page, 75);
|
||||
await decreaseZoomLevel(page);
|
||||
await assertZoomLevel(page, 50);
|
||||
|
||||
const zoomed = [0, 0, original[2] * 0.5, original[3] * 0.5];
|
||||
await assertEdgelessSelectedModelRect(page, zoomed);
|
||||
|
||||
await increaseZoomLevel(page);
|
||||
await assertZoomLevel(page, 75);
|
||||
await increaseZoomLevel(page);
|
||||
await assertZoomLevel(page, 100);
|
||||
await assertEdgelessSelectedModelRect(page, original);
|
||||
});
|
||||
|
||||
test('zoom by mouse', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await assertZoomLevel(page, 100);
|
||||
|
||||
await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]);
|
||||
|
||||
await page.mouse.click(CENTER_X, CENTER_Y);
|
||||
const original = [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT];
|
||||
await assertEdgelessSelectedModelRect(page, original);
|
||||
|
||||
await zoomByMouseWheel(page, 0, 125);
|
||||
await assertZoomLevel(page, 90);
|
||||
|
||||
const zoomed = [0, 0, original[2] * 0.9, original[3] * 0.9];
|
||||
await assertEdgelessSelectedModelRect(page, zoomed);
|
||||
});
|
||||
|
||||
test('zoom by mouse without ctrl pressed when edgelessScrollZoom is enabled', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await assertZoomLevel(page, 100);
|
||||
|
||||
await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]);
|
||||
|
||||
await page.mouse.click(CENTER_X, CENTER_Y);
|
||||
const original = [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT];
|
||||
await assertEdgelessSelectedModelRect(page, original);
|
||||
|
||||
// enable edgelessScrollZoom
|
||||
await page.evaluate(() => {
|
||||
// @ts-expect-error set a setting
|
||||
window.editorSetting$.value = {
|
||||
// @ts-expect-error set a setting
|
||||
...window.editorSetting$.value,
|
||||
edgelessScrollZoom: true,
|
||||
};
|
||||
});
|
||||
|
||||
// can zoom without ctrl pressed
|
||||
await zoomByMouseWheel(page, 0, 125, false);
|
||||
await assertZoomLevel(page, 90);
|
||||
|
||||
const zoomed = [0, 0, original[2] * 0.9, original[3] * 0.9];
|
||||
await assertEdgelessSelectedModelRect(page, zoomed);
|
||||
|
||||
// disable edgelessScrollZoom
|
||||
await page.evaluate(() => {
|
||||
// @ts-expect-error set a setting
|
||||
window.editorSetting$.value = {
|
||||
// @ts-expect-error set a setting
|
||||
...window.editorSetting$.value,
|
||||
edgelessScrollZoom: false,
|
||||
};
|
||||
});
|
||||
|
||||
// can't zoom without ctrl pressed
|
||||
await zoomByMouseWheel(page, 0, 125, false);
|
||||
await assertZoomLevel(page, 90);
|
||||
});
|
||||
|
||||
test('zoom by pinch', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await assertZoomLevel(page, 100);
|
||||
|
||||
await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]);
|
||||
|
||||
await page.mouse.click(CENTER_X, CENTER_Y);
|
||||
const original = [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT];
|
||||
await assertEdgelessSelectedModelRect(page, original);
|
||||
|
||||
const from = [
|
||||
{ x: CENTER_X - 100, y: CENTER_Y },
|
||||
{ x: CENTER_X + 100, y: CENTER_Y },
|
||||
];
|
||||
const to = [
|
||||
{ x: CENTER_X - 50, y: CENTER_Y - 35 },
|
||||
{ x: CENTER_X + 50, y: CENTER_Y + 35 },
|
||||
];
|
||||
await multiTouchDown(page, from);
|
||||
await multiTouchMove(page, from, to);
|
||||
await multiTouchUp(page, to);
|
||||
|
||||
await assertZoomLevel(page, 50);
|
||||
const zoomed = [0, 0, 0.5 * DEFAULT_NOTE_WIDTH, 46];
|
||||
await assertEdgelessSelectedModelRect(page, zoomed);
|
||||
});
|
||||
|
||||
test('zoom by pinch when edgeless is readonly', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await assertZoomLevel(page, 100);
|
||||
|
||||
await switchReadonly(page);
|
||||
|
||||
const from = [
|
||||
{ x: CENTER_X - 100, y: CENTER_Y },
|
||||
{ x: CENTER_X + 100, y: CENTER_Y },
|
||||
];
|
||||
const to = [
|
||||
{ x: CENTER_X - 50, y: CENTER_Y - 35 },
|
||||
{ x: CENTER_X + 50, y: CENTER_Y + 35 },
|
||||
];
|
||||
await multiTouchDown(page, from);
|
||||
await multiTouchMove(page, from, to);
|
||||
await multiTouchUp(page, to);
|
||||
|
||||
await switchReadonly(page, false);
|
||||
await waitNextFrame(page);
|
||||
await assertZoomLevel(page, 50);
|
||||
});
|
||||
|
||||
test('move by pan', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await assertZoomLevel(page, 100);
|
||||
|
||||
await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]);
|
||||
|
||||
await page.mouse.click(CENTER_X, CENTER_Y);
|
||||
const original = [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT];
|
||||
await assertEdgelessSelectedModelRect(page, original);
|
||||
|
||||
const from = [
|
||||
{ x: CENTER_X - 100, y: CENTER_Y },
|
||||
{ x: CENTER_X + 100, y: CENTER_Y },
|
||||
];
|
||||
const to = [
|
||||
{ x: CENTER_X - 50, y: CENTER_Y + 50 },
|
||||
{ x: CENTER_X + 150, y: CENTER_Y + 50 },
|
||||
];
|
||||
|
||||
await multiTouchDown(page, from);
|
||||
await multiTouchMove(page, from, to);
|
||||
await multiTouchUp(page, to);
|
||||
|
||||
const moved = [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT];
|
||||
await assertEdgelessSelectedModelRect(page, moved);
|
||||
});
|
||||
|
||||
test('option/alt mouse drag duplicate a new element', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await deleteAll(page);
|
||||
|
||||
const start = [0, 0];
|
||||
const end = [100, 100];
|
||||
await createShapeElement(page, start, end, Shape.Square);
|
||||
await optionMouseDrag(page, [50, 50], [150, 50]);
|
||||
await assertSelectedBound(page, [100, 0, 100, 100]);
|
||||
|
||||
await captureHistory(page);
|
||||
await undoByClick(page);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
|
||||
await redoByClick(page);
|
||||
await assertSelectedBound(page, [100, 0, 100, 100]);
|
||||
});
|
||||
|
||||
test('should cancel select when the selected point is outside the current selected element', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
const firstStart = { x: 100, y: 100 };
|
||||
const firstEnd = { x: 200, y: 200 };
|
||||
await addBasicRectShapeElement(page, firstStart, firstEnd);
|
||||
|
||||
const secondStart = { x: 300, y: 300 };
|
||||
const secondEnd = { x: 400, y: 400 };
|
||||
await addBasicRectShapeElement(page, secondStart, secondEnd);
|
||||
|
||||
// select the first rect
|
||||
await page.mouse.click(110, 150);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
// click outside the selected rect
|
||||
await page.mouse.click(200, 200);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test('the tooltip of more button should be hidden when the action menu is shown', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await addBasicBrushElement(page, start, end);
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y + 5);
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 104, 104]);
|
||||
|
||||
const moreButton = locatorEdgelessComponentToolButton(page, 'more');
|
||||
await expect(moreButton).toBeVisible();
|
||||
|
||||
const moreButtonBox = await moreButton.boundingBox();
|
||||
const tooltip = page.locator('.affine-tooltip');
|
||||
|
||||
assertExists(moreButtonBox);
|
||||
|
||||
// need to wait for previous tooltip to be hidden
|
||||
await page.waitForTimeout(100);
|
||||
await page.mouse.move(moreButtonBox.x + 10, moreButtonBox.y + 10);
|
||||
await expect(tooltip).toBeVisible();
|
||||
|
||||
await page.mouse.click(moreButtonBox.x + 10, moreButtonBox.y + 10);
|
||||
await expect(tooltip).toBeHidden();
|
||||
|
||||
await page.mouse.click(moreButtonBox.x + 10, moreButtonBox.y + 10);
|
||||
await expect(tooltip).toBeVisible();
|
||||
});
|
||||
|
||||
test('shift click multi select and de-select', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const start = [0, 0];
|
||||
const end = [100, 100];
|
||||
await createShapeElement(page, start, end, Shape.Square);
|
||||
start[0] = 100;
|
||||
end[0] = 200;
|
||||
await createShapeElement(page, start, end, Shape.Square);
|
||||
|
||||
await clickView(page, [50, 0]);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 100, 100]);
|
||||
|
||||
await shiftClickView(page, [150, 50]);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 200, 100]);
|
||||
|
||||
// we will try to write text on a shape element when we dbclick it
|
||||
|
||||
await waitNextFrame(page, 500);
|
||||
await shiftClickView(page, [150, 50]);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 100, 100]);
|
||||
});
|
||||
|
||||
test('Before and after switching to Edgeless, the previous zoom ratio and position when Edgeless was opened should be remembered', async ({
|
||||
page,
|
||||
}) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2479',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await assertZoomLevel(page, 100);
|
||||
await increaseZoomLevel(page);
|
||||
await assertZoomLevel(page, 125);
|
||||
await switchEditorMode(page);
|
||||
await switchEditorMode(page);
|
||||
await assertZoomLevel(page, 125);
|
||||
});
|
||||
|
||||
test('should close zoom bar when click blank area', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const screenWidth = page.viewportSize()?.width;
|
||||
assertExists(screenWidth);
|
||||
if (screenWidth > ZOOM_BAR_RESPONSIVE_SCREEN_WIDTH) {
|
||||
await page.setViewportSize({
|
||||
width: 1000,
|
||||
height: 1000,
|
||||
});
|
||||
}
|
||||
|
||||
await zoomResetByKeyboard(page);
|
||||
await assertZoomLevel(page, 100);
|
||||
await increaseZoomLevel(page);
|
||||
await assertZoomLevel(page, 125);
|
||||
|
||||
const verticalZoomBar = '.edgeless-zoom-toolbar-container.vertical';
|
||||
const zoomBar = page.locator(verticalZoomBar);
|
||||
await expect(zoomBar).toBeVisible();
|
||||
|
||||
// Click Blank Area
|
||||
await page.mouse.click(10, 100);
|
||||
await expect(zoomBar).toBeHidden();
|
||||
});
|
||||
195
blocksuite/tests-legacy/e2e/edgeless/brush.spec.ts
Normal file
195
blocksuite/tests-legacy/e2e/edgeless/brush.spec.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { lightThemeV2 } from '@toeverything/theme/v2';
|
||||
|
||||
import {
|
||||
assertEdgelessTool,
|
||||
deleteAll,
|
||||
pickColorAtPoints,
|
||||
selectBrushColor,
|
||||
selectBrushSize,
|
||||
setEdgelessTool,
|
||||
switchEditorMode,
|
||||
updateExistedBrushElementSize,
|
||||
zoomResetByKeyboard,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
addBasicBrushElement,
|
||||
click,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
resizeElementByHandle,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessColorSameWithHexColor,
|
||||
assertEdgelessSelectedRect,
|
||||
assertSameColor,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('change editor mode when brush color palette opening', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await setEdgelessTool(page, 'brush');
|
||||
|
||||
const brushMenu = page.locator('edgeless-brush-menu');
|
||||
await expect(brushMenu).toBeVisible();
|
||||
|
||||
await switchEditorMode(page);
|
||||
await expect(brushMenu).toBeHidden();
|
||||
});
|
||||
|
||||
test('add brush element', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await addBasicBrushElement(page, start, end, false);
|
||||
|
||||
await assertEdgelessTool(page, 'brush');
|
||||
});
|
||||
|
||||
test('resize brush element', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await addBasicBrushElement(page, start, end);
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y + 5);
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 104, 104]);
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y + 5);
|
||||
const delta = { x: 20, y: 40 };
|
||||
await resizeElementByHandle(page, delta, 'top-left', 10);
|
||||
|
||||
await page.mouse.click(start.x + 25, start.y + 45);
|
||||
await assertEdgelessSelectedRect(page, [118, 138, 84, 64]);
|
||||
});
|
||||
|
||||
test('add brush element with color', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await setEdgelessTool(page, 'brush');
|
||||
await selectBrushColor(page, 'Blue');
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await dragBetweenCoords(page, start, end, { steps: 100 });
|
||||
|
||||
const [pickedColor] = await pickColorAtPoints(page, [[110, 110]]);
|
||||
const color = lightThemeV2['edgeless/palette/medium/blueMedium'];
|
||||
await assertEdgelessColorSameWithHexColor(page, color, pickedColor);
|
||||
});
|
||||
|
||||
test('keep same color when mouse mode switched back to brush', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await deleteAll(page);
|
||||
|
||||
await setEdgelessTool(page, 'brush');
|
||||
await selectBrushColor(page, 'Blue');
|
||||
|
||||
const start = { x: 200, y: 200 };
|
||||
const end = { x: 300, y: 300 };
|
||||
await dragBetweenCoords(page, start, end, { steps: 100 });
|
||||
|
||||
await setEdgelessTool(page, 'default');
|
||||
await click(page, { x: 50, y: 50 });
|
||||
|
||||
await setEdgelessTool(page, 'brush');
|
||||
const origin = { x: 100, y: 100 };
|
||||
await dragBetweenCoords(page, origin, start, { steps: 100 });
|
||||
|
||||
const [pickedColor] = await pickColorAtPoints(page, [[110, 110]]);
|
||||
const color = lightThemeV2['edgeless/palette/medium/blueMedium'];
|
||||
await assertEdgelessColorSameWithHexColor(page, color, pickedColor);
|
||||
});
|
||||
|
||||
test('add brush element with different size', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await setEdgelessTool(page, 'brush');
|
||||
await selectBrushSize(page, 'ten');
|
||||
await selectBrushColor(page, 'Blue');
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 100 };
|
||||
await dragBetweenCoords(page, start, end, { steps: 100 });
|
||||
|
||||
const [topEdge, bottomEdge, nearTopEdge, nearBottomEdge] =
|
||||
await pickColorAtPoints(page, [
|
||||
// Select two points on the top and bottom border of the line,
|
||||
// their color should be the same as the specified color
|
||||
[110, 95],
|
||||
[110, 104],
|
||||
// Select two points close to the upper and lower boundaries of the line,
|
||||
// their color should be different from the specified color
|
||||
[110, 94],
|
||||
[110, 105],
|
||||
]);
|
||||
|
||||
const color = lightThemeV2['edgeless/palette/medium/blueMedium'];
|
||||
await assertEdgelessColorSameWithHexColor(page, color, topEdge);
|
||||
await assertEdgelessColorSameWithHexColor(page, color, bottomEdge);
|
||||
assertSameColor(nearTopEdge, '#53b2ef');
|
||||
assertSameColor(nearBottomEdge, '#53b2ef');
|
||||
});
|
||||
|
||||
test('change brush element size by component-toolbar', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await addBasicBrushElement(page, start, end);
|
||||
|
||||
// wait for menu hide animation
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// change to line width 12
|
||||
await page.mouse.click(110, 110);
|
||||
await updateExistedBrushElementSize(page, 6);
|
||||
await assertEdgelessSelectedRect(page, [94, 94, 112, 112]);
|
||||
|
||||
// change to line width 10
|
||||
await page.mouse.click(110, 110);
|
||||
await updateExistedBrushElementSize(page, 5);
|
||||
await assertEdgelessSelectedRect(page, [95, 95, 110, 110]);
|
||||
|
||||
// change to line width 8
|
||||
await page.mouse.click(110, 110);
|
||||
await updateExistedBrushElementSize(page, 4);
|
||||
await assertEdgelessSelectedRect(page, [96, 96, 108, 108]);
|
||||
|
||||
// change to line width 6
|
||||
await page.mouse.click(110, 110);
|
||||
await updateExistedBrushElementSize(page, 3);
|
||||
await assertEdgelessSelectedRect(page, [97, 97, 106, 106]);
|
||||
|
||||
// change to line width 4
|
||||
await page.mouse.click(110, 110);
|
||||
await updateExistedBrushElementSize(page, 2);
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 104, 104]);
|
||||
|
||||
// change to line width 2
|
||||
await page.mouse.click(110, 110);
|
||||
await updateExistedBrushElementSize(page, 1);
|
||||
await assertEdgelessSelectedRect(page, [99, 99, 102, 102]);
|
||||
});
|
||||
235
blocksuite/tests-legacy/e2e/edgeless/clipboard.spec.ts
Normal file
235
blocksuite/tests-legacy/e2e/edgeless/clipboard.spec.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
createNote,
|
||||
createShapeElement,
|
||||
decreaseZoomLevel,
|
||||
deleteAll,
|
||||
getAllSortedIds,
|
||||
Shape,
|
||||
switchEditorMode,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
copyByKeyboard,
|
||||
cutByKeyboard,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
enterPlaygroundRoom,
|
||||
expectConsoleMessage,
|
||||
focusTitle,
|
||||
getCurrentEditorDocId,
|
||||
initEmptyEdgelessState,
|
||||
mockParseDocUrlService,
|
||||
pasteByKeyboard,
|
||||
pasteContent,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import { assertRichImage } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('mime', () => {
|
||||
test('should paste svg in text/plain mime', async ({ page }) => {
|
||||
expectConsoleMessage(page, 'Error: Image sourceId is missing!', 'warning');
|
||||
await commonSetup(page);
|
||||
const content = {
|
||||
'text/plain': `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
|
||||
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
|
||||
<script>alert("Malicious script executed!");</script>
|
||||
</svg>
|
||||
`,
|
||||
};
|
||||
|
||||
await pasteContent(page, content);
|
||||
|
||||
// wait for paste
|
||||
await page.waitForTimeout(200);
|
||||
await assertRichImage(page, 1);
|
||||
});
|
||||
|
||||
test('should not paste bad svg', async ({ page }) => {
|
||||
expectConsoleMessage(page, 'BlockSuiteError: val does not exist', 'error');
|
||||
expectConsoleMessage(page, 'Error: Image sourceId is missing!', 'warning');
|
||||
|
||||
await commonSetup(page);
|
||||
const contents = [
|
||||
{
|
||||
'text/plain': `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
|
||||
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
|
||||
<script>alert("Malicious script executed!");</script>
|
||||
`,
|
||||
},
|
||||
|
||||
{
|
||||
'text/plain': `<svg width="100" height="100">
|
||||
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
|
||||
<script>alert("Malicious script executed!");</script>
|
||||
</svg>
|
||||
`,
|
||||
},
|
||||
];
|
||||
for (const content of contents) {
|
||||
await pasteContent(page, content);
|
||||
}
|
||||
|
||||
await assertRichImage(page, 0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('frame clipboard', () => {
|
||||
test('copy and paste frame with shape elements inside', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [100, -100]);
|
||||
await page.mouse.click(10, 50);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addFrame');
|
||||
const originIds = await getAllSortedIds(page);
|
||||
expect(originIds.length).toBe(3);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(6);
|
||||
});
|
||||
|
||||
test('copy and paste frame with group elements inside', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [100, -100]);
|
||||
await page.mouse.click(10, 50);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await triggerComponentToolbarAction(page, 'createFrameOnMoreOption');
|
||||
const originIds = await getAllSortedIds(page);
|
||||
expect(originIds.length).toBe(5);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(10);
|
||||
});
|
||||
|
||||
test('copy and paste frame with frame inside', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [100, -100]);
|
||||
await page.mouse.click(10, 50);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addFrame');
|
||||
|
||||
await decreaseZoomLevel(page);
|
||||
await createShapeElement(page, [700, 0], [800, 100], Shape.Square);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addFrame');
|
||||
|
||||
const originIds = await getAllSortedIds(page);
|
||||
expect(originIds.length).toBe(5);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(10);
|
||||
});
|
||||
|
||||
test('cut frame with shape elements inside', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [100, -100]);
|
||||
await page.mouse.click(10, 50);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addFrame');
|
||||
const originIds = await getAllSortedIds(page);
|
||||
expect(originIds.length).toBe(3);
|
||||
|
||||
await cutByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('pasting URLs', () => {
|
||||
test('pasting github pr url', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await waitNextFrame(page);
|
||||
await pasteContent(page, {
|
||||
'text/plain': 'https://github.com/toeverything/blocksuite/pull/7217',
|
||||
});
|
||||
|
||||
await expect(
|
||||
page.locator('affine-embed-edgeless-github-block')
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('pasting internal link', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await waitNextFrame(page);
|
||||
await focusTitle(page);
|
||||
const docId = await getCurrentEditorDocId(page);
|
||||
|
||||
await type(page, 'doc title');
|
||||
|
||||
await switchEditorMode(page);
|
||||
await deleteAll(page);
|
||||
|
||||
await mockParseDocUrlService(page, {
|
||||
'http://workspace/doc-id': docId,
|
||||
});
|
||||
|
||||
await pasteContent(page, {
|
||||
'text/plain': 'http://workspace/doc-id',
|
||||
});
|
||||
|
||||
await expect(
|
||||
page.locator('affine-embed-edgeless-linked-doc-block')
|
||||
).toBeVisible();
|
||||
|
||||
await expect(
|
||||
page.locator('.affine-embed-linked-doc-content-title')
|
||||
).toHaveText('doc title');
|
||||
});
|
||||
|
||||
test('pasting external link', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await waitNextFrame(page);
|
||||
await focusTitle(page);
|
||||
|
||||
await type(page, 'doc title');
|
||||
|
||||
await switchEditorMode(page);
|
||||
await deleteAll(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await pasteContent(page, {
|
||||
'text/plain': 'https://affine.pro',
|
||||
});
|
||||
|
||||
await expect(page.locator('bookmark-card')).toBeVisible();
|
||||
});
|
||||
});
|
||||
368
blocksuite/tests-legacy/e2e/edgeless/color-picker.spec.ts
Normal file
368
blocksuite/tests-legacy/e2e/edgeless/color-picker.spec.ts
Normal file
@@ -0,0 +1,368 @@
|
||||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
|
||||
import { dragBetweenCoords } from '../utils/actions/drag.js';
|
||||
import {
|
||||
addBasicShapeElement,
|
||||
Shape,
|
||||
switchEditorMode,
|
||||
triggerComponentToolbarAction,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { parseStringToRgba } from '../utils/bs-alternative.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
async function setupWithColorPickerFunction(page: Page) {
|
||||
await enterPlaygroundRoom(page, { flags: { enable_color_picker: true } });
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
}
|
||||
|
||||
function getColorPickerButtonWithClass(page: Page, classes: string) {
|
||||
return page.locator(`edgeless-color-picker-button.${classes}`);
|
||||
}
|
||||
|
||||
function getCurrentColorUnitButton(locator: Locator) {
|
||||
return locator.locator('edgeless-color-button').locator('.color-unit').nth(0);
|
||||
}
|
||||
|
||||
function getCurrentColor(locator: Locator) {
|
||||
return locator.evaluate(ele =>
|
||||
getComputedStyle(ele.querySelector('svg')!).getPropertyValue('fill')
|
||||
);
|
||||
}
|
||||
|
||||
function getCustomButton(locator: Locator) {
|
||||
return locator.locator('edgeless-color-custom-button');
|
||||
}
|
||||
|
||||
function getColorPickerPanel(locator: Locator) {
|
||||
return locator.locator('edgeless-color-picker');
|
||||
}
|
||||
|
||||
function getPaletteControl(locator: Locator) {
|
||||
return locator.locator('.color-palette');
|
||||
}
|
||||
|
||||
function getHueControl(locator: Locator) {
|
||||
return locator.locator('.color-slider-wrapper.hue .color-slider');
|
||||
}
|
||||
|
||||
function getAlphaControl(locator: Locator) {
|
||||
return locator.locator('.color-slider-wrapper.alpha .color-slider');
|
||||
}
|
||||
|
||||
function getHexInput(locator: Locator) {
|
||||
return locator.locator('label.color input');
|
||||
}
|
||||
|
||||
function getAlphaInput(locator: Locator) {
|
||||
return locator.locator('label.alpha input');
|
||||
}
|
||||
|
||||
// Basic functions
|
||||
test.describe('basic functions', () => {
|
||||
test('custom color button should be displayed', async ({ page }) => {
|
||||
await setupWithColorPickerFunction(page);
|
||||
|
||||
const start0 = { x: 100, y: 100 };
|
||||
const end0 = { x: 150, y: 200 };
|
||||
await addBasicShapeElement(page, start0, end0, Shape.Square);
|
||||
|
||||
const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color');
|
||||
await expect(fillColorButton).toBeVisible();
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeShapeFillColor');
|
||||
|
||||
const customButton = getCustomButton(fillColorButton);
|
||||
await expect(customButton).toBeVisible();
|
||||
});
|
||||
|
||||
test('should open color-picker panel when clicking on custom color button', async ({
|
||||
page,
|
||||
}) => {
|
||||
await setupWithColorPickerFunction(page);
|
||||
|
||||
const start0 = { x: 100, y: 100 };
|
||||
const end0 = { x: 150, y: 200 };
|
||||
await addBasicShapeElement(page, start0, end0, Shape.Square);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeShapeFillColor');
|
||||
|
||||
const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color');
|
||||
const customButton = getCustomButton(fillColorButton);
|
||||
|
||||
await customButton.click();
|
||||
|
||||
const colorPickerPanel = getColorPickerPanel(fillColorButton);
|
||||
|
||||
await expect(colorPickerPanel).toBeVisible();
|
||||
});
|
||||
|
||||
test('should close color-picker panel when clicking on outside', async ({
|
||||
page,
|
||||
}) => {
|
||||
await setupWithColorPickerFunction(page);
|
||||
|
||||
const start0 = { x: 100, y: 100 };
|
||||
const end0 = { x: 150, y: 200 };
|
||||
await addBasicShapeElement(page, start0, end0, Shape.Square);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeShapeFillColor');
|
||||
|
||||
const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color');
|
||||
const currentColorUnit = getCurrentColorUnitButton(fillColorButton);
|
||||
|
||||
const value = await getCurrentColor(currentColorUnit);
|
||||
await expect(currentColorUnit.locator('svg')).toHaveCSS('fill', value);
|
||||
|
||||
const customButton = getCustomButton(fillColorButton);
|
||||
|
||||
await customButton.click();
|
||||
|
||||
const colorPickerPanel = getColorPickerPanel(fillColorButton);
|
||||
|
||||
await expect(colorPickerPanel).toBeVisible();
|
||||
|
||||
await colorPickerPanel.click({ position: { x: 0, y: 0 } });
|
||||
await expect(colorPickerPanel).toBeVisible();
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await expect(colorPickerPanel).toBeHidden();
|
||||
});
|
||||
|
||||
test('should return to the palette panel when re-clicking the color button', async ({
|
||||
page,
|
||||
}) => {
|
||||
await setupWithColorPickerFunction(page);
|
||||
|
||||
const start0 = { x: 100, y: 100 };
|
||||
const end0 = { x: 150, y: 200 };
|
||||
await addBasicShapeElement(page, start0, end0, Shape.Square);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeShapeFillColor');
|
||||
|
||||
const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color');
|
||||
const customButton = getCustomButton(fillColorButton);
|
||||
const colorPickerPanel = getColorPickerPanel(fillColorButton);
|
||||
|
||||
await customButton.click();
|
||||
|
||||
await expect(colorPickerPanel).toBeVisible();
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await expect(colorPickerPanel).toBeHidden();
|
||||
|
||||
await dragBetweenCoords(page, { x: 125, y: 75 }, { x: 175, y: 225 });
|
||||
|
||||
await fillColorButton.click();
|
||||
|
||||
await expect(customButton).toBeVisible();
|
||||
await expect(colorPickerPanel).toBeHidden();
|
||||
});
|
||||
|
||||
test('should pick a color when clicking on the palette canvas', async ({
|
||||
page,
|
||||
}) => {
|
||||
await setupWithColorPickerFunction(page);
|
||||
|
||||
const start0 = { x: 100, y: 100 };
|
||||
const end0 = { x: 150, y: 200 };
|
||||
await addBasicShapeElement(page, start0, end0, Shape.Square);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeShapeFillColor');
|
||||
|
||||
const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color');
|
||||
const customButton = getCustomButton(fillColorButton);
|
||||
const colorPickerPanel = getColorPickerPanel(fillColorButton);
|
||||
|
||||
await customButton.click();
|
||||
|
||||
const paletteControl = getPaletteControl(colorPickerPanel);
|
||||
const hexInput = getHexInput(colorPickerPanel);
|
||||
|
||||
const value = await hexInput.inputValue();
|
||||
|
||||
await paletteControl.click();
|
||||
|
||||
const newValue = await hexInput.inputValue();
|
||||
|
||||
expect(value).not.toEqual(newValue);
|
||||
});
|
||||
|
||||
test('should pick a color when clicking on the hue control', async ({
|
||||
page,
|
||||
}) => {
|
||||
await setupWithColorPickerFunction(page);
|
||||
|
||||
const start0 = { x: 100, y: 100 };
|
||||
const end0 = { x: 150, y: 200 };
|
||||
await addBasicShapeElement(page, start0, end0, Shape.Square);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeShapeFillColor');
|
||||
|
||||
const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color');
|
||||
const customButton = getCustomButton(fillColorButton);
|
||||
const colorPickerPanel = getColorPickerPanel(fillColorButton);
|
||||
|
||||
await customButton.click();
|
||||
|
||||
const hueControl = getHueControl(colorPickerPanel);
|
||||
const hexInput = getHexInput(colorPickerPanel);
|
||||
|
||||
const value = await hexInput.inputValue();
|
||||
|
||||
await hueControl.click();
|
||||
|
||||
const newValue = await hexInput.inputValue();
|
||||
|
||||
expect(value).not.toEqual(newValue);
|
||||
});
|
||||
|
||||
test('should update color when changing the hex input', async ({ page }) => {
|
||||
await setupWithColorPickerFunction(page);
|
||||
|
||||
const start0 = { x: 100, y: 100 };
|
||||
const end0 = { x: 150, y: 200 };
|
||||
await addBasicShapeElement(page, start0, end0, Shape.Square);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeShapeFillColor');
|
||||
|
||||
const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color');
|
||||
const customButton = getCustomButton(fillColorButton);
|
||||
const colorPickerPanel = getColorPickerPanel(fillColorButton);
|
||||
|
||||
await customButton.click();
|
||||
|
||||
const hexInput = getHexInput(colorPickerPanel);
|
||||
|
||||
await hexInput.fill('fff');
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(hexInput).toHaveValue('ffffff');
|
||||
|
||||
await hexInput.fill('000000');
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(hexInput).toHaveValue('000000');
|
||||
|
||||
await hexInput.fill('fff$');
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(hexInput).toHaveValue('ffffff');
|
||||
|
||||
await hexInput.fill('#f0f');
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(hexInput).toHaveValue('ff00ff');
|
||||
});
|
||||
|
||||
test('should adjust alpha when clicking on the alpha control', async ({
|
||||
page,
|
||||
}) => {
|
||||
await setupWithColorPickerFunction(page);
|
||||
|
||||
const start0 = { x: 100, y: 100 };
|
||||
const end0 = { x: 150, y: 200 };
|
||||
await addBasicShapeElement(page, start0, end0, Shape.Square);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeShapeFillColor');
|
||||
|
||||
const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color');
|
||||
const customButton = getCustomButton(fillColorButton);
|
||||
const colorPickerPanel = getColorPickerPanel(fillColorButton);
|
||||
|
||||
await customButton.click();
|
||||
|
||||
const alphaControl = getAlphaControl(colorPickerPanel);
|
||||
const alphaInput = getAlphaInput(colorPickerPanel);
|
||||
|
||||
const value = await alphaInput.inputValue();
|
||||
|
||||
await alphaControl.click();
|
||||
|
||||
const newValue = await alphaInput.inputValue();
|
||||
|
||||
expect(value).not.toEqual(newValue);
|
||||
});
|
||||
|
||||
test('should adjust alpha when changing the alpha input', async ({
|
||||
page,
|
||||
}) => {
|
||||
await setupWithColorPickerFunction(page);
|
||||
|
||||
const start0 = { x: 100, y: 100 };
|
||||
const end0 = { x: 150, y: 200 };
|
||||
await addBasicShapeElement(page, start0, end0, Shape.Square);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeShapeFillColor');
|
||||
|
||||
const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color');
|
||||
const customButton = getCustomButton(fillColorButton);
|
||||
const colorPickerPanel = getColorPickerPanel(fillColorButton);
|
||||
|
||||
await customButton.click();
|
||||
|
||||
const alphaInput = getAlphaInput(colorPickerPanel);
|
||||
|
||||
await alphaInput.fill('101');
|
||||
await expect(alphaInput).toHaveValue('100');
|
||||
|
||||
await alphaInput.fill('-1');
|
||||
await expect(alphaInput).toHaveValue('1');
|
||||
|
||||
await alphaInput.pressSequentially('--1');
|
||||
await expect(alphaInput).toHaveValue('1');
|
||||
|
||||
await alphaInput.pressSequentially('++1');
|
||||
await expect(alphaInput).toHaveValue('1');
|
||||
|
||||
await alphaInput.pressSequentially('-+1');
|
||||
await expect(alphaInput).toHaveValue('1');
|
||||
|
||||
await alphaInput.pressSequentially('+-1');
|
||||
await expect(alphaInput).toHaveValue('1');
|
||||
|
||||
await alphaInput.fill('23');
|
||||
await expect(alphaInput).toHaveValue('23');
|
||||
});
|
||||
|
||||
test('the computed style should be parsed correctly', async ({ page }) => {
|
||||
await setupWithColorPickerFunction(page);
|
||||
|
||||
const start0 = { x: 100, y: 100 };
|
||||
const end0 = { x: 150, y: 200 };
|
||||
await addBasicShapeElement(page, start0, end0, Shape.Square);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeShapeFillColor');
|
||||
|
||||
const fillColorButton = getColorPickerButtonWithClass(page, 'fill-color');
|
||||
const currentColorUnit = getCurrentColorUnitButton(fillColorButton);
|
||||
|
||||
const value = await getCurrentColor(currentColorUnit);
|
||||
let rgba = parseStringToRgba(value);
|
||||
|
||||
expect(rgba.a).toEqual(1);
|
||||
|
||||
rgba = parseStringToRgba('rgb(25.5,0,0)');
|
||||
expect(rgba.r).toBeCloseTo(0.1);
|
||||
|
||||
rgba = parseStringToRgba('rgba(233,233,233, .5)');
|
||||
expect(rgba.a).toEqual(0.5);
|
||||
|
||||
rgba = parseStringToRgba('transparent');
|
||||
expect(rgba).toEqual({ r: 1, g: 1, b: 1, a: 0 });
|
||||
|
||||
rgba = parseStringToRgba('--blocksuite-transparent');
|
||||
expect(rgba).toEqual({ r: 1, g: 1, b: 1, a: 0 });
|
||||
|
||||
rgba = parseStringToRgba('--affine-palette-transparent');
|
||||
expect(rgba).toEqual({ r: 1, g: 1, b: 1, a: 0 });
|
||||
|
||||
rgba = parseStringToRgba('#ff0');
|
||||
expect(rgba).toEqual({ r: 1, g: 1, b: 0, a: 1 });
|
||||
|
||||
rgba = parseStringToRgba('#ff09');
|
||||
expect(rgba).toEqual({ r: 1, g: 1, b: 0, a: 0.6 });
|
||||
});
|
||||
});
|
||||
142
blocksuite/tests-legacy/e2e/edgeless/connector/clipboard.spec.ts
Normal file
142
blocksuite/tests-legacy/e2e/edgeless/connector/clipboard.spec.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
copyByKeyboard,
|
||||
createConnectorElement,
|
||||
createNote,
|
||||
createShapeElement,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
getAllSortedIds,
|
||||
getTypeById,
|
||||
pasteByKeyboard,
|
||||
selectAllByKeyboard,
|
||||
Shape,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertConnectorPath } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test.describe('connector clipboard', () => {
|
||||
test('copy and paste connector whose both sides connect nothing', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createConnectorElement(page, [0, 0], [200, 100]);
|
||||
await waitNextFrame(page);
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [100, -50]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page);
|
||||
await assertConnectorPath(
|
||||
page,
|
||||
[
|
||||
[0, -100],
|
||||
[100, -100],
|
||||
[100, 0],
|
||||
[200, 0],
|
||||
],
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
test('copy and paste connector whose both sides connect elements', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [60, 50], [240, 50]);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [150, -50]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page);
|
||||
await assertConnectorPath(
|
||||
page,
|
||||
[
|
||||
[100, -50],
|
||||
[200, -50],
|
||||
],
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
test('copy and paste connector whose both sides connect elements, but only paste connector', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [70, 50], [230, 50]);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [150, -50]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page);
|
||||
await assertConnectorPath(
|
||||
page,
|
||||
[
|
||||
[100, -50],
|
||||
[200, -50],
|
||||
],
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
test('copy and paste connector whose one side connects elements', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [55, 50], [200, 50]);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [100, -50]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, false);
|
||||
await assertConnectorPath(
|
||||
page,
|
||||
[
|
||||
[100, -50],
|
||||
[200, -50],
|
||||
],
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
test('original relative index should keep same when copy and paste group with note and shape', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [100, 50]);
|
||||
await page.mouse.click(10, 50);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(6);
|
||||
expect(await getTypeById(page, sortedIds[0])).toBe(
|
||||
await getTypeById(page, sortedIds[3])
|
||||
);
|
||||
expect(await getTypeById(page, sortedIds[1])).toBe(
|
||||
await getTypeById(page, sortedIds[4])
|
||||
);
|
||||
expect(await getTypeById(page, sortedIds[2])).toBe(
|
||||
await getTypeById(page, sortedIds[5])
|
||||
);
|
||||
});
|
||||
});
|
||||
320
blocksuite/tests-legacy/e2e/edgeless/connector/connector.spec.ts
Normal file
320
blocksuite/tests-legacy/e2e/edgeless/connector/connector.spec.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addBasicConnectorElement,
|
||||
changeConnectorStrokeColor,
|
||||
changeConnectorStrokeStyle,
|
||||
changeConnectorStrokeWidth,
|
||||
createConnectorElement,
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
pickColorAtPoints,
|
||||
rotateElementByHandle,
|
||||
Shape,
|
||||
toModelCoord,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import { pressBackspace, waitNextFrame } from '../../utils/actions/index.js';
|
||||
import {
|
||||
assertConnectorPath,
|
||||
assertEdgelessNonSelectedRect,
|
||||
assertEdgelessSelectedRect,
|
||||
assertExists,
|
||||
} from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test('path #1, the upper line is parallel with the lower line of antoher, and anchor from top to bottom of another', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, -100], [300, 0], Shape.Square);
|
||||
await createConnectorElement(page, [50, 0], [250, 0]);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -20],
|
||||
[150, -20],
|
||||
[150, 20],
|
||||
[250, 20],
|
||||
[250, 0],
|
||||
]);
|
||||
});
|
||||
|
||||
test('path #2, the top-right point is overlapped with the bottom-left point of another, and anchor from top to bottom of another', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, -100], [200, 0], Shape.Square);
|
||||
await createConnectorElement(page, [50, 0], [150, 0]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -120],
|
||||
[220, -120],
|
||||
[220, 20],
|
||||
[150, 20],
|
||||
[150, 0],
|
||||
]);
|
||||
});
|
||||
|
||||
test('path #3, the two shape are parallel in x axis, the anchor from the right to right', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 50], [300, 50]);
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[150, 50],
|
||||
[150, 120],
|
||||
[320, 120],
|
||||
[320, 50],
|
||||
[300, 50],
|
||||
]);
|
||||
});
|
||||
|
||||
test('when element is removed, connector should be deleted too', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 50], [200, 0]);
|
||||
|
||||
//select
|
||||
await dragBetweenViewCoords(page, [10, -10], [20, 20]);
|
||||
await pressBackspace(page);
|
||||
await dragBetweenViewCoords(page, [100, 50], [0, 50]);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test('connector connects triangle shape', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Triangle);
|
||||
await createConnectorElement(page, [75, 50], [100, 50]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[75, 50],
|
||||
[100, 50],
|
||||
]);
|
||||
});
|
||||
|
||||
test('connector connects diamond shape', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond);
|
||||
await createConnectorElement(page, [100, 50], [200, 50]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[200, 50],
|
||||
]);
|
||||
});
|
||||
|
||||
test('connector connects rotated Square shape', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [50, 0], [50, -100]);
|
||||
await dragBetweenViewCoords(page, [-10, 50], [60, 60]);
|
||||
await rotateElementByHandle(page, 30, 'top-left');
|
||||
await assertConnectorPath(page, [
|
||||
[75, 6.7],
|
||||
[75, -46.65],
|
||||
[50, -46.65],
|
||||
[50, -100],
|
||||
]);
|
||||
await rotateElementByHandle(page, 30, 'top-left');
|
||||
await assertConnectorPath(page, [
|
||||
[93.3, 25],
|
||||
[138.3, 25],
|
||||
[138.3, -38.3],
|
||||
[50, -38.3],
|
||||
[50, -100],
|
||||
]);
|
||||
});
|
||||
|
||||
test('change connector line width', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
const start = { x: 100, y: 200 };
|
||||
const end = { x: 300, y: 300 };
|
||||
await addBasicConnectorElement(page, start, end);
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y);
|
||||
await triggerComponentToolbarAction(page, 'changeConnectorStrokeColor');
|
||||
await changeConnectorStrokeColor(page, 'MediumGrey');
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeConnectorStrokeStyles');
|
||||
await changeConnectorStrokeWidth(page, 5);
|
||||
|
||||
await waitNextFrame(page);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeConnectorStrokeStyles');
|
||||
|
||||
const pickedColor = await pickColorAtPoints(page, [
|
||||
[start.x + 5, start.y],
|
||||
[start.x + 10, start.y],
|
||||
]);
|
||||
expect(pickedColor[0]).toBe(pickedColor[1]);
|
||||
});
|
||||
|
||||
test('change connector stroke style', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
const start = { x: 100, y: 200 };
|
||||
const end = { x: 300, y: 300 };
|
||||
await addBasicConnectorElement(page, start, end);
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y);
|
||||
await triggerComponentToolbarAction(page, 'changeConnectorStrokeColor');
|
||||
await changeConnectorStrokeColor(page, 'MediumGrey');
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeConnectorStrokeStyles');
|
||||
await changeConnectorStrokeStyle(page, 'dash');
|
||||
await waitNextFrame(page);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeConnectorStrokeStyles');
|
||||
|
||||
const pickedColor = await pickColorAtPoints(page, [[start.x + 20, start.y]]);
|
||||
expect(pickedColor[0]).toBe('#000000');
|
||||
});
|
||||
|
||||
test.describe('quick connect', () => {
|
||||
test('should create a connector when clicking on button', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
const [x, y] = await toViewCoord(page, [50, 50]);
|
||||
await page.mouse.click(x, y);
|
||||
|
||||
const quickConnectBtn = page.getByRole('button', {
|
||||
name: 'Draw connector',
|
||||
});
|
||||
|
||||
await expect(quickConnectBtn).toBeVisible();
|
||||
await quickConnectBtn.click();
|
||||
await expect(quickConnectBtn).toBeHidden();
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[x, y],
|
||||
]);
|
||||
});
|
||||
|
||||
test('should be uncreated if the target is not found after clicking', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
const [x, y] = await toViewCoord(page, [50, 50]);
|
||||
await page.mouse.click(x, y);
|
||||
|
||||
const quickConnectBtn = page.getByRole('button', {
|
||||
name: 'Draw connector',
|
||||
});
|
||||
|
||||
const bounds = await quickConnectBtn.boundingBox();
|
||||
assertExists(bounds);
|
||||
|
||||
await quickConnectBtn.click();
|
||||
|
||||
await page.mouse.click(bounds.x, bounds.y);
|
||||
await assertEdgelessSelectedRect(page, [x - 50, y - 50, 100, 100]);
|
||||
});
|
||||
|
||||
test('should be uncreated if the target is not found after pressing ESC', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
|
||||
// select shape
|
||||
const [x, y] = await toViewCoord(page, [50, 50]);
|
||||
await page.mouse.click(x, y);
|
||||
|
||||
// click button
|
||||
await triggerComponentToolbarAction(page, 'quickConnect');
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test('should be connected if the target is found', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
|
||||
// select shape
|
||||
const [x, y] = await toViewCoord(page, [50, 50]);
|
||||
await page.mouse.click(x, y);
|
||||
|
||||
// click button
|
||||
await triggerComponentToolbarAction(page, 'quickConnect');
|
||||
|
||||
// click target
|
||||
const [tx, ty] = await toViewCoord(page, [200, 50]);
|
||||
await page.mouse.click(tx, ty);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[200, 50],
|
||||
]);
|
||||
});
|
||||
|
||||
test('should follow the mouse to automatically select the starting point', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
const shapeBounds = await toViewCoord(page, [0, 0]);
|
||||
|
||||
// select shape
|
||||
const [x, y] = await toViewCoord(page, [50, 50]);
|
||||
await page.mouse.click(x, y);
|
||||
|
||||
// click button
|
||||
const quickConnectBtn = page.getByRole('button', {
|
||||
name: 'Draw connector',
|
||||
});
|
||||
const bounds = await quickConnectBtn.boundingBox();
|
||||
assertExists(bounds);
|
||||
await quickConnectBtn.click();
|
||||
|
||||
// at right
|
||||
let point: [number, number] = [bounds.x, bounds.y];
|
||||
let endpoint = await toModelCoord(page, point);
|
||||
await assertConnectorPath(page, [[100, 50], endpoint]);
|
||||
|
||||
// at top
|
||||
point = [shapeBounds[0] + 50, shapeBounds[1] - 50];
|
||||
endpoint = await toModelCoord(page, point);
|
||||
await page.mouse.move(...point);
|
||||
await waitNextFrame(page);
|
||||
await assertConnectorPath(page, [[50, 0], endpoint]);
|
||||
|
||||
// at left
|
||||
point = [shapeBounds[0] - 50, shapeBounds[1] + 50];
|
||||
endpoint = await toModelCoord(page, point);
|
||||
await page.mouse.move(...point);
|
||||
await assertConnectorPath(page, [[0, 50], endpoint]);
|
||||
|
||||
// at bottom
|
||||
point = [shapeBounds[0] + 50, shapeBounds[1] + 100 + 50];
|
||||
endpoint = await toModelCoord(page, point);
|
||||
await page.mouse.move(...point);
|
||||
await assertConnectorPath(page, [[50, 100], endpoint]);
|
||||
});
|
||||
});
|
||||
206
blocksuite/tests-legacy/e2e/edgeless/connector/elbow.spec.ts
Normal file
206
blocksuite/tests-legacy/e2e/edgeless/connector/elbow.spec.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import {
|
||||
assertEdgelessConnectorToolMode,
|
||||
ConnectorMode,
|
||||
createConnectorElement,
|
||||
createShapeElement,
|
||||
deleteAllConnectors,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
redoByClick,
|
||||
setEdgelessTool,
|
||||
Shape,
|
||||
undoByClick,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertConnectorPath } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test('elbow connector without node and width greater than height', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await setEdgelessTool(page, 'connector');
|
||||
await assertEdgelessConnectorToolMode(page, ConnectorMode.Curve);
|
||||
await dragBetweenViewCoords(page, [0, 0], [200, 100]);
|
||||
await assertConnectorPath(page, [
|
||||
[0, 0],
|
||||
[100, 0],
|
||||
[100, 100],
|
||||
[200, 100],
|
||||
]);
|
||||
});
|
||||
|
||||
test('elbow connector without node and width less than height', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createConnectorElement(page, [0, 0], [100, 200]);
|
||||
await assertConnectorPath(page, [
|
||||
[0, 0],
|
||||
[0, 100],
|
||||
[100, 100],
|
||||
[100, 200],
|
||||
]);
|
||||
});
|
||||
|
||||
test('elbow connector one side attached element another side free', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [51, 50], [200, 0]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[150, 50],
|
||||
[150, 0],
|
||||
[200, 0],
|
||||
]);
|
||||
|
||||
await deleteAllConnectors(page);
|
||||
await createConnectorElement(page, [50, 50], [125, 0]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[125, 50],
|
||||
[125, 0],
|
||||
]);
|
||||
});
|
||||
|
||||
test('elbow connector both side attatched element', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [50, 50], [249, 50]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[200, 50],
|
||||
]);
|
||||
|
||||
// Could drag directly
|
||||
// because the default shape type change to general style with filled color
|
||||
await dragBetweenViewCoords(page, [250, 50], [250, 0]);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[150, 50],
|
||||
[150, 0],
|
||||
[200, 0],
|
||||
]);
|
||||
|
||||
await dragBetweenViewCoords(page, [250, 0], [150, -50]);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -50],
|
||||
[100, -50],
|
||||
]);
|
||||
|
||||
await dragBetweenViewCoords(page, [150, -50], [150, -150]);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -50],
|
||||
[150, -50],
|
||||
[150, -100],
|
||||
]);
|
||||
|
||||
await undoByClick(page);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -50],
|
||||
[100, -50],
|
||||
]);
|
||||
await undoByClick(page);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[150, 50],
|
||||
[150, 0],
|
||||
[200, 0],
|
||||
]);
|
||||
await undoByClick(page);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[200, 50],
|
||||
]);
|
||||
await redoByClick(page);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[150, 50],
|
||||
[150, 0],
|
||||
[200, 0],
|
||||
]);
|
||||
await redoByClick(page);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -50],
|
||||
[100, -50],
|
||||
]);
|
||||
await redoByClick(page);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -50],
|
||||
[150, -50],
|
||||
[150, -100],
|
||||
]);
|
||||
});
|
||||
|
||||
test('elbow connector both side attached element with one attach element and other is fixed', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [50, 0], [250, 50]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -20],
|
||||
[150, -20],
|
||||
[150, 50],
|
||||
[200, 50],
|
||||
]);
|
||||
|
||||
// select
|
||||
await dragBetweenViewCoords(page, [255, -10], [255, 55]);
|
||||
await dragBetweenViewCoords(page, [250, 50], [250, 0]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -20],
|
||||
[150, -20],
|
||||
[150, 0],
|
||||
[200, 0],
|
||||
]);
|
||||
|
||||
await dragBetweenViewCoords(page, [250, 0], [250, -20]);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -20],
|
||||
[200, -20],
|
||||
]);
|
||||
|
||||
await dragBetweenViewCoords(page, [250, -20], [150, -150]);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -50],
|
||||
[150, -50],
|
||||
[150, -100],
|
||||
]);
|
||||
});
|
||||
|
||||
test('elbow connector both side attached element with all fixed', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [50, 0], [300, 50]);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -20],
|
||||
[320, -20],
|
||||
[320, 50],
|
||||
[300, 50],
|
||||
]);
|
||||
});
|
||||
107
blocksuite/tests-legacy/e2e/edgeless/connector/group.spec.ts
Normal file
107
blocksuite/tests-legacy/e2e/edgeless/connector/group.spec.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
clickView,
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
moveView,
|
||||
selectAllByKeyboard,
|
||||
Shape,
|
||||
triggerComponentToolbarAction,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertConnectorPath } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test.describe('groups connections', () => {
|
||||
async function groupsSetup(page: Page) {
|
||||
await commonSetup(page);
|
||||
|
||||
// group 1
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
// group 2
|
||||
await createShapeElement(page, [500, 0], [600, 100], Shape.Square);
|
||||
await createShapeElement(page, [600, 100], [700, 200], Shape.Square);
|
||||
await dragBetweenViewCoords(page, [550, -50], [650, 250]);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
await waitNextFrame(page);
|
||||
}
|
||||
|
||||
test('should connect to other groups', async ({ page }) => {
|
||||
await groupsSetup(page);
|
||||
|
||||
// click button
|
||||
await triggerComponentToolbarAction(page, 'quickConnect');
|
||||
|
||||
// move to group 1
|
||||
await moveView(page, [200, 50]);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[500, 100],
|
||||
[200, 50],
|
||||
]);
|
||||
});
|
||||
|
||||
test('should connect to elements within other groups', async ({ page }) => {
|
||||
await groupsSetup(page);
|
||||
|
||||
// click button
|
||||
await triggerComponentToolbarAction(page, 'quickConnect');
|
||||
|
||||
// move to group 1
|
||||
await moveView(page, [200, 100]);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[500, 100],
|
||||
[200, 100],
|
||||
]);
|
||||
|
||||
// move to elements within group 1
|
||||
await moveView(page, [190, 150]);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[500, 100],
|
||||
[200, 150],
|
||||
]);
|
||||
});
|
||||
|
||||
test('elements within groups should connect to other groups', async ({
|
||||
page,
|
||||
}) => {
|
||||
await groupsSetup(page);
|
||||
|
||||
// click elements within group 1
|
||||
await clickView(page, [40, 40]);
|
||||
await clickView(page, [60, 60]);
|
||||
|
||||
// click button
|
||||
await triggerComponentToolbarAction(page, 'quickConnect');
|
||||
|
||||
// move to elements within group 2
|
||||
await moveView(page, [610, 50]);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[600, 50],
|
||||
]);
|
||||
|
||||
// move to group 2
|
||||
await moveView(page, [600, 100]);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[600, 100],
|
||||
]);
|
||||
});
|
||||
});
|
||||
334
blocksuite/tests-legacy/e2e/edgeless/connector/label.spec.ts
Normal file
334
blocksuite/tests-legacy/e2e/edgeless/connector/label.spec.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addBasicConnectorElement,
|
||||
createConnectorElement,
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
locatorComponentToolbar,
|
||||
setEdgelessTool,
|
||||
Shape,
|
||||
SHORT_KEY,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
type,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/index.js';
|
||||
import {
|
||||
assertConnectorPath,
|
||||
assertEdgelessCanvasText,
|
||||
assertPointAlmostEqual,
|
||||
} from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test.describe('connector label with straight shape', () => {
|
||||
async function getEditorCenter(page: Page) {
|
||||
const bounds = await page
|
||||
.locator('edgeless-connector-label-editor rich-text')
|
||||
.boundingBox();
|
||||
assertExists(bounds);
|
||||
const cx = bounds.x + bounds.width / 2;
|
||||
const cy = bounds.y + bounds.height / 2;
|
||||
return [cx, cy];
|
||||
}
|
||||
|
||||
function calcOffsetDistance(s: number[], e: number[], p: number[]) {
|
||||
const p1 = Math.hypot(s[1] - p[1], s[0] - p[0]);
|
||||
const f1 = Math.hypot(s[1] - e[1], s[0] - e[0]);
|
||||
return p1 / f1;
|
||||
}
|
||||
|
||||
test('should insert in the middle of the path when clicking on the button', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
const start = { x: 100, y: 200 };
|
||||
const end = { x: 300, y: 300 };
|
||||
await addBasicConnectorElement(page, start, end);
|
||||
await page.mouse.click(105, 200);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'addText');
|
||||
await type(page, ' a ');
|
||||
await assertEdgelessCanvasText(page, ' a ');
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.click(105, 200);
|
||||
|
||||
const addTextBtn = locatorComponentToolbar(page).getByRole('button', {
|
||||
name: 'Add text',
|
||||
});
|
||||
await expect(addTextBtn).toBeHidden();
|
||||
|
||||
await page.mouse.dblclick(200, 250);
|
||||
await assertEdgelessCanvasText(page, 'a');
|
||||
|
||||
await page.keyboard.press('Backspace');
|
||||
await assertEdgelessCanvasText(page, '');
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.click(200, 250);
|
||||
|
||||
await expect(addTextBtn).toBeVisible();
|
||||
});
|
||||
|
||||
test('should insert at the place when double clicking on the path', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await setEdgelessTool(page, 'connector');
|
||||
|
||||
await page.mouse.move(0, 0);
|
||||
|
||||
const menu = page.locator('edgeless-connector-menu');
|
||||
await expect(menu).toBeVisible();
|
||||
|
||||
const straightBtn = menu.locator('edgeless-tool-icon-button', {
|
||||
hasText: 'Straight',
|
||||
});
|
||||
await expect(straightBtn).toBeVisible();
|
||||
await straightBtn.click();
|
||||
|
||||
const start = { x: 250, y: 250 };
|
||||
const end = { x: 500, y: 250 };
|
||||
await addBasicConnectorElement(page, start, end);
|
||||
|
||||
await page.mouse.dblclick(300, 250);
|
||||
await type(page, 'a');
|
||||
await assertEdgelessCanvasText(page, 'a');
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.dblclick(300, 250);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await type(page, 'b');
|
||||
await assertEdgelessCanvasText(page, 'ab');
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.dblclick(300, 250);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await type(page, 'c');
|
||||
await assertEdgelessCanvasText(page, 'c');
|
||||
await waitNextFrame(page);
|
||||
|
||||
const [cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [300, 250]);
|
||||
expect((cx - 250) / (500 - 250)).toBeCloseTo(50 / 250);
|
||||
});
|
||||
|
||||
test('should move alone the path', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 50], [200, 50]);
|
||||
|
||||
await dragBetweenViewCoords(page, [140, 40], [160, 60]);
|
||||
await triggerComponentToolbarAction(page, 'changeConnectorShape');
|
||||
const straightBtn = locatorComponentToolbar(page).getByRole('button', {
|
||||
name: 'Straight',
|
||||
});
|
||||
await straightBtn.click();
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[200, 50],
|
||||
]);
|
||||
|
||||
const [x, y] = await toViewCoord(page, [150, 50]);
|
||||
await page.mouse.dblclick(x, y);
|
||||
await type(page, 'label');
|
||||
await assertEdgelessCanvasText(page, 'label');
|
||||
await waitNextFrame(page);
|
||||
|
||||
let [cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [x, y]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await dragBetweenViewCoords(page, [150, 50], [130, 30]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.dblclick(x - 20, y);
|
||||
await waitNextFrame(page);
|
||||
|
||||
[cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [x - 20, y]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await dragBetweenViewCoords(page, [130, 50], [170, 70]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.dblclick(x + 20, y);
|
||||
await waitNextFrame(page);
|
||||
|
||||
[cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [x + 20, y]);
|
||||
});
|
||||
|
||||
test('should only move within constraints', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 50], [200, 50]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[200, 50],
|
||||
]);
|
||||
|
||||
const [x, y] = await toViewCoord(page, [150, 50]);
|
||||
await page.mouse.dblclick(x, y);
|
||||
await type(page, 'label');
|
||||
await assertEdgelessCanvasText(page, 'label');
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await dragBetweenViewCoords(page, [150, 50], [300, 110]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.dblclick(x + 55, y);
|
||||
await waitNextFrame(page);
|
||||
|
||||
let [cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [x + 50, y]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await dragBetweenViewCoords(page, [200, 50], [0, 50]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.dblclick(x - 55, y);
|
||||
await waitNextFrame(page);
|
||||
|
||||
[cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [x - 50, y]);
|
||||
});
|
||||
|
||||
test('should automatically adjust position via offset distance', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 50], [200, 50]);
|
||||
|
||||
await dragBetweenViewCoords(page, [140, 40], [160, 60]);
|
||||
await triggerComponentToolbarAction(page, 'changeConnectorShape');
|
||||
const straightBtn = locatorComponentToolbar(page).getByRole('button', {
|
||||
name: 'Straight',
|
||||
});
|
||||
await straightBtn.click();
|
||||
|
||||
const point = [170, 50];
|
||||
const offsetDistance = calcOffsetDistance([100, 50], [200, 50], point);
|
||||
let [x, y] = await toViewCoord(page, point);
|
||||
await page.mouse.dblclick(x, y);
|
||||
await type(page, 'label');
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.dblclick(x, y);
|
||||
await waitNextFrame(page);
|
||||
|
||||
let [cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [x, y]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.click(50, 50);
|
||||
await waitNextFrame(page);
|
||||
await dragBetweenViewCoords(page, [50, 50], [-50, 50]);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.click(250, 50);
|
||||
await waitNextFrame(page);
|
||||
await dragBetweenViewCoords(page, [250, 50], [350, 50]);
|
||||
await waitNextFrame(page);
|
||||
|
||||
const start = [0, 50];
|
||||
const end = [300, 50];
|
||||
const mx = start[0] + offsetDistance * (end[0] - start[0]);
|
||||
const my = start[1] + offsetDistance * (end[1] - start[1]);
|
||||
[x, y] = await toViewCoord(page, [mx, my]);
|
||||
|
||||
await page.mouse.dblclick(x, y);
|
||||
await waitNextFrame(page);
|
||||
|
||||
[cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [x, y]);
|
||||
});
|
||||
|
||||
test('should enter the label editing state when pressing `Enter`', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
const start = { x: 100, y: 200 };
|
||||
const end = { x: 300, y: 300 };
|
||||
await addBasicConnectorElement(page, start, end);
|
||||
await page.mouse.click(105, 200);
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
await type(page, ' a ');
|
||||
await assertEdgelessCanvasText(page, ' a ');
|
||||
});
|
||||
|
||||
test('should exit the label editing state when pressing `Mod-Enter` or `Escape`', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
const start = { x: 100, y: 200 };
|
||||
const end = { x: 300, y: 300 };
|
||||
await addBasicConnectorElement(page, start, end);
|
||||
await page.mouse.click(105, 200);
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
await waitNextFrame(page);
|
||||
await type(page, ' a ');
|
||||
await assertEdgelessCanvasText(page, ' a ');
|
||||
|
||||
await page.keyboard.press(`${SHORT_KEY}+Enter`);
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'b');
|
||||
await assertEdgelessCanvasText(page, 'b');
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'c');
|
||||
await assertEdgelessCanvasText(page, 'c');
|
||||
});
|
||||
});
|
||||
598
blocksuite/tests-legacy/e2e/edgeless/edgeless-text.spec.ts
Normal file
598
blocksuite/tests-legacy/e2e/edgeless/edgeless-text.spec.ts
Normal file
@@ -0,0 +1,598 @@
|
||||
import type { EdgelessTextBlockComponent } from '@blocksuite/affine-block-edgeless-text';
|
||||
import { Bound } from '@blocksuite/global/utils';
|
||||
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`
|
||||
);
|
||||
});
|
||||
88
blocksuite/tests-legacy/e2e/edgeless/element-toolbar.spec.ts
Normal file
88
blocksuite/tests-legacy/e2e/edgeless/element-toolbar.spec.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addBasicRectShapeElement,
|
||||
locatorComponentToolbar,
|
||||
resizeElementByHandle,
|
||||
selectNoteInEdgeless,
|
||||
switchEditorMode,
|
||||
zoomResetByKeyboard,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('toolbar should appear when select note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
|
||||
const toolbar = locatorComponentToolbar(page);
|
||||
await expect(toolbar).toBeVisible();
|
||||
});
|
||||
|
||||
test('tooltip should be hidden after clicking on button', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
|
||||
const toolbar = locatorComponentToolbar(page);
|
||||
const modeBtn = toolbar.getByRole('button', { name: 'Mode' });
|
||||
|
||||
await modeBtn.hover();
|
||||
await expect(page.locator('.blocksuite-portal')).toBeVisible();
|
||||
|
||||
await modeBtn.click();
|
||||
await expect(page.locator('.blocksuite-portal')).toBeHidden();
|
||||
await expect(page.locator('note-display-mode-panel')).toBeVisible();
|
||||
|
||||
await modeBtn.click();
|
||||
await expect(page.locator('.blocksuite-portal')).toBeVisible();
|
||||
await expect(page.locator('note-display-mode-panel')).toBeHidden();
|
||||
|
||||
await modeBtn.click();
|
||||
await expect(page.locator('.blocksuite-portal')).toBeHidden();
|
||||
await expect(page.locator('note-display-mode-panel')).toBeVisible();
|
||||
|
||||
const colorBtn = toolbar.getByRole('button', {
|
||||
name: 'Background',
|
||||
});
|
||||
|
||||
await colorBtn.hover();
|
||||
await expect(page.locator('.blocksuite-portal')).toBeVisible();
|
||||
|
||||
await colorBtn.click();
|
||||
await expect(page.locator('.blocksuite-portal')).toBeHidden();
|
||||
await expect(page.locator('note-display-mode-panel')).toBeHidden();
|
||||
await expect(page.locator('edgeless-color-panel')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should be hidden when resizing element', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 210, y: 110 }, { x: 310, y: 210 });
|
||||
await page.mouse.click(220, 120);
|
||||
|
||||
const toolbar = locatorComponentToolbar(page);
|
||||
await expect(toolbar).toBeVisible();
|
||||
|
||||
await resizeElementByHandle(page, { x: 400, y: 300 }, 'top-left', 30);
|
||||
|
||||
await page.mouse.move(450, 300);
|
||||
await expect(toolbar).toBeEmpty();
|
||||
|
||||
await page.mouse.move(320, 220);
|
||||
await expect(toolbar).toBeEmpty();
|
||||
|
||||
await page.mouse.up();
|
||||
await expect(toolbar).toBeVisible();
|
||||
});
|
||||
49
blocksuite/tests-legacy/e2e/edgeless/eraser.spec.ts
Normal file
49
blocksuite/tests-legacy/e2e/edgeless/eraser.spec.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { click } from '../utils/actions/click.js';
|
||||
import { dragBetweenCoords } from '../utils/actions/drag.js';
|
||||
import {
|
||||
addBasicRectShapeElement,
|
||||
deleteAll,
|
||||
getNoteBoundBoxInEdgeless,
|
||||
setEdgelessTool,
|
||||
switchEditorMode,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
} from '../utils/actions/misc.js';
|
||||
import {
|
||||
assertBlockCount,
|
||||
assertEdgelessNonSelectedRect,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('erase shape', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await deleteAll(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 0, y: 0 }, { x: 100, y: 100 });
|
||||
await setEdgelessTool(page, 'eraser');
|
||||
|
||||
await dragBetweenCoords(page, { x: 50, y: 150 }, { x: 50, y: 50 });
|
||||
await click(page, { x: 50, y: 50 });
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test('erase note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await assertBlockCount(page, 'edgeless-note', 1);
|
||||
|
||||
await setEdgelessTool(page, 'eraser');
|
||||
const box = await getNoteBoundBoxInEdgeless(page, noteId);
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: 0, y: 0 },
|
||||
{ x: box.x + 10, y: box.y + 10 }
|
||||
);
|
||||
await assertBlockCount(page, 'edgeless-note', 0);
|
||||
});
|
||||
157
blocksuite/tests-legacy/e2e/edgeless/frame/clipboard.spec.ts
Normal file
157
blocksuite/tests-legacy/e2e/edgeless/frame/clipboard.spec.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
import { clickView, moveView } from '../../utils/actions/click.js';
|
||||
import {
|
||||
autoFit,
|
||||
createFrame as _createFrame,
|
||||
createShapeElement,
|
||||
deleteAll,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
getAllSortedIds,
|
||||
getFirstContainerId,
|
||||
getIds,
|
||||
Shape,
|
||||
shiftClickView,
|
||||
triggerComponentToolbarAction,
|
||||
zoomResetByKeyboard,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
copyByKeyboard,
|
||||
pasteByKeyboard,
|
||||
pressBackspace,
|
||||
pressEscape,
|
||||
} from '../../utils/actions/keyboard.js';
|
||||
import { assertContainerOfElements } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
const createFrame = async (
|
||||
page: Page,
|
||||
coord1: [number, number],
|
||||
coord2: [number, number]
|
||||
) => {
|
||||
await _createFrame(page, coord1, coord2);
|
||||
await autoFit(page);
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
});
|
||||
|
||||
test.describe('frame copy and paste', () => {
|
||||
test('copy of frame should keep relationship of child elements', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [450, 450]);
|
||||
await createShapeElement(page, [200, 200], [300, 300], Shape.Square);
|
||||
|
||||
const frameTitle = page.locator('affine-frame-title');
|
||||
|
||||
await pressEscape(page);
|
||||
await frameTitle.click();
|
||||
await copyByKeyboard(page);
|
||||
await deleteAll(page);
|
||||
await moveView(page, [500, 500]); // center copy
|
||||
await pasteByKeyboard(page);
|
||||
|
||||
const frameId = await getFirstContainerId(page);
|
||||
const shapeId = (await getAllSortedIds(page)).filter(id => id !== frameId);
|
||||
await assertContainerOfElements(page, shapeId, frameId);
|
||||
});
|
||||
|
||||
test('copy of frame by alt/option dragging should keep relationship of child elements', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [450, 450]);
|
||||
await createShapeElement(page, [200, 200], [300, 300], Shape.Square);
|
||||
await createShapeElement(page, [250, 250], [350, 350], Shape.Square);
|
||||
await createShapeElement(page, [300, 300], [400, 400], Shape.Square);
|
||||
await pressEscape(page);
|
||||
|
||||
const frameTitles = page.locator('affine-frame-title');
|
||||
|
||||
await shiftClickView(page, [260, 260]);
|
||||
await shiftClickView(page, [310, 310]);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await pressEscape(page);
|
||||
|
||||
await frameTitles.nth(0).click();
|
||||
await page.keyboard.down('Alt');
|
||||
await dragBetweenViewCoords(page, [60, 60], [460, 460]);
|
||||
await page.keyboard.up('Alt');
|
||||
await pressEscape(page);
|
||||
|
||||
await frameTitles.nth(0).click({ modifiers: ['Shift'] });
|
||||
await shiftClickView(page, [250, 250]);
|
||||
await shiftClickView(page, [350, 350]);
|
||||
await pressBackspace(page); // remove original elements
|
||||
|
||||
const frameId = await getFirstContainerId(page);
|
||||
const groupId = await getFirstContainerId(page, [frameId]);
|
||||
const shapeIds = (await getIds(page)).filter(
|
||||
id => ![frameId, groupId].includes(id)
|
||||
);
|
||||
|
||||
await assertContainerOfElements(page, [groupId], frameId);
|
||||
await assertContainerOfElements(page, [shapeIds[0]], frameId);
|
||||
await assertContainerOfElements(page, [shapeIds[1]], groupId);
|
||||
await assertContainerOfElements(page, [shapeIds[2]], groupId);
|
||||
});
|
||||
|
||||
test('duplicate element in frame', async ({ page }) => {
|
||||
await createFrame(page, [50, 50], [450, 450]);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await pressEscape(page);
|
||||
|
||||
const frameTitles = page.locator('affine-frame-title');
|
||||
|
||||
await frameTitles.nth(0).click();
|
||||
await page.locator('edgeless-more-button').click();
|
||||
await page.locator('editor-menu-action', { hasText: 'Duplicate' }).click();
|
||||
await pressEscape(page);
|
||||
|
||||
await frameTitles.nth(0).click();
|
||||
await shiftClickView(page, [150, 150]);
|
||||
await pressBackspace(page); // remove original elements
|
||||
|
||||
const frameId = await getFirstContainerId(page);
|
||||
const shapeIds = (await getIds(page)).filter(id => id !== frameId);
|
||||
await assertContainerOfElements(page, shapeIds, frameId);
|
||||
});
|
||||
|
||||
test('copy of element by alt/option dragging in frame should belong to frame', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [450, 450]);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await pressEscape(page);
|
||||
|
||||
await clickView(page, [150, 150]);
|
||||
await page.keyboard.down('Alt');
|
||||
await dragBetweenViewCoords(page, [150, 150], [250, 250]);
|
||||
await page.keyboard.up('Alt');
|
||||
|
||||
const frameId = await getFirstContainerId(page);
|
||||
const shapeIds = (await getIds(page)).filter(id => id !== frameId);
|
||||
await assertContainerOfElements(page, shapeIds, frameId);
|
||||
});
|
||||
|
||||
test('copy of element by alt/option dragging out of frame should not belong to frame', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [450, 450]);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await pressEscape(page);
|
||||
|
||||
await clickView(page, [150, 150]);
|
||||
await page.keyboard.down('Alt');
|
||||
await dragBetweenViewCoords(page, [150, 150], [550, 550]);
|
||||
await page.keyboard.up('Alt');
|
||||
|
||||
const frameId = await getFirstContainerId(page);
|
||||
const shapeIds = (await getIds(page)).filter(id => id !== frameId);
|
||||
await assertContainerOfElements(page, [shapeIds[0]], frameId);
|
||||
await assertContainerOfElements(page, [shapeIds[1]], null);
|
||||
});
|
||||
});
|
||||
224
blocksuite/tests-legacy/e2e/edgeless/frame/frame-mindmap.spec.ts
Normal file
224
blocksuite/tests-legacy/e2e/edgeless/frame/frame-mindmap.spec.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
import { clickView } from '../../utils/actions/click.js';
|
||||
import {
|
||||
createFrame,
|
||||
dragBetweenViewCoords as _dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
getFirstContainerId,
|
||||
getSelectedBound,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
zoomResetByKeyboard,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import { pressEscape } from '../../utils/actions/keyboard.js';
|
||||
import { waitNextFrame } from '../../utils/actions/misc.js';
|
||||
import { assertContainerOfElements } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
const dragBetweenViewCoords = async (
|
||||
page: Page,
|
||||
start: number[],
|
||||
end: number[]
|
||||
) => {
|
||||
// dragging slowly may drop frame if mindmap is existed, so for test we drag quickly
|
||||
await _dragBetweenViewCoords(page, start, end, { steps: 2 });
|
||||
await waitNextFrame(page);
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
});
|
||||
|
||||
test('drag root node of mindmap into frame partially, then drag root node of mindmap out.', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [550, 550]);
|
||||
await pressEscape(page);
|
||||
const frameId = await getFirstContainerId(page);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'addMindmap');
|
||||
const mindmapId = await getFirstContainerId(page, [frameId]);
|
||||
|
||||
// drag in
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
await clickView(page, [
|
||||
mindmapBound[0] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
]);
|
||||
await dragBetweenViewCoords(
|
||||
page,
|
||||
[mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]],
|
||||
[100, 100]
|
||||
);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], frameId);
|
||||
|
||||
// drag out
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
await clickView(page, [
|
||||
mindmapBound[0] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
]);
|
||||
await dragBetweenViewCoords(
|
||||
page,
|
||||
[mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]],
|
||||
[-100, -100]
|
||||
);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], null);
|
||||
});
|
||||
|
||||
test('drag root node of mindmap into frame fully, then drag root node of mindmap out.', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [550, 550]);
|
||||
const frameId = await getFirstContainerId(page);
|
||||
await pressEscape(page);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'addMindmap');
|
||||
const mindmapId = await getFirstContainerId(page, [frameId]);
|
||||
|
||||
// drag in
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
|
||||
await clickView(page, [
|
||||
mindmapBound[0] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
]);
|
||||
await dragBetweenViewCoords(
|
||||
page,
|
||||
[mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]],
|
||||
[100, 200]
|
||||
);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], frameId);
|
||||
|
||||
// drag out
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
await clickView(page, [
|
||||
mindmapBound[0] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
]);
|
||||
await dragBetweenViewCoords(
|
||||
page,
|
||||
[mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]],
|
||||
[-100, -100]
|
||||
);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], null);
|
||||
});
|
||||
|
||||
test('drag whole mindmap into frame, then drag root node of mindmap out.', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [550, 550]);
|
||||
const frameId = await getFirstContainerId(page);
|
||||
await pressEscape(page);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'addMindmap');
|
||||
const mindmapId = await getFirstContainerId(page, [frameId]);
|
||||
|
||||
// drag in
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
const rootNodePos = [
|
||||
mindmapBound[0] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
];
|
||||
await dragBetweenViewCoords(page, rootNodePos, [
|
||||
rootNodePos[0] - 20,
|
||||
rootNodePos[1] + 200,
|
||||
]);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], frameId);
|
||||
|
||||
// drag out
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
const rootNodePos = [
|
||||
mindmapBound[0] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
];
|
||||
|
||||
await dragBetweenViewCoords(page, rootNodePos, [-100, -100]);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], null);
|
||||
});
|
||||
|
||||
test('add mindmap into frame, then drag root node of mindmap out.', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [550, 550]);
|
||||
const frameId = await getFirstContainerId(page);
|
||||
await pressEscape(page);
|
||||
|
||||
const button = page.locator('edgeless-mindmap-tool-button');
|
||||
await button.click();
|
||||
await toViewCoord(page, [100, 200]);
|
||||
await clickView(page, [100, 200]);
|
||||
const mindmapId = await getFirstContainerId(page, [frameId]);
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], frameId);
|
||||
|
||||
// drag out
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
pressEscape(page);
|
||||
await clickView(page, [
|
||||
mindmapBound[0] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
]);
|
||||
await dragBetweenViewCoords(
|
||||
page,
|
||||
[mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]],
|
||||
[-20, -20]
|
||||
);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], null);
|
||||
});
|
||||
|
||||
test('add mindmap out of frame and add new node in frame then drag frame', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [500, 50], [1000, 550]);
|
||||
await pressEscape(page);
|
||||
|
||||
const button = page.locator('edgeless-mindmap-tool-button');
|
||||
await button.click();
|
||||
await toViewCoord(page, [20, 200]);
|
||||
await clickView(page, [20, 200]);
|
||||
await waitNextFrame(page, 100);
|
||||
const mindmapId = await getFirstContainerId(page);
|
||||
|
||||
// add new node
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 500);
|
||||
await clickView(page, [
|
||||
mindmapBound[2] - 50,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
]);
|
||||
await waitNextFrame(page, 500);
|
||||
await clickView(page, [
|
||||
mindmapBound[2] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
]);
|
||||
await pressEscape(page, 2);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], null);
|
||||
});
|
||||
158
blocksuite/tests-legacy/e2e/edgeless/frame/frame-title.spec.ts
Normal file
158
blocksuite/tests-legacy/e2e/edgeless/frame/frame-title.spec.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addNote,
|
||||
autoFit,
|
||||
createFrame as _createFrame,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
getFrameTitle,
|
||||
zoomOutByKeyboard,
|
||||
zoomResetByKeyboard,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
type,
|
||||
} from '../../utils/actions/keyboard.js';
|
||||
import { waitNextFrame } from '../../utils/actions/misc.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
const createFrame = async (
|
||||
page: Page,
|
||||
coord1: [number, number],
|
||||
coord2: [number, number]
|
||||
) => {
|
||||
const frame = await _createFrame(page, coord1, coord2);
|
||||
await autoFit(page);
|
||||
return frame;
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
});
|
||||
|
||||
const enterFrameTitleEditor = async (page: Page) => {
|
||||
const frameTitle = page.locator('affine-frame-title');
|
||||
await frameTitle.dblclick();
|
||||
|
||||
const frameTitleEditor = page.locator('edgeless-frame-title-editor');
|
||||
await frameTitleEditor.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
return frameTitleEditor;
|
||||
};
|
||||
|
||||
test.describe('frame title rendering', () => {
|
||||
test('frame title should be displayed', async ({ page }) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
await expect(frameTitle).toBeVisible();
|
||||
await expect(frameTitle).toHaveText('Frame 1');
|
||||
});
|
||||
|
||||
test('frame title should be rendered on the top', async ({ page }) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
await expect(frameTitle).toBeVisible();
|
||||
|
||||
const frameTitleBounding = await frameTitle.boundingBox();
|
||||
expect(frameTitleBounding).not.toBeNull();
|
||||
if (!frameTitleBounding) return;
|
||||
|
||||
const frameTitleCenter = [
|
||||
frameTitleBounding.x + frameTitleBounding.width / 2,
|
||||
frameTitleBounding.y + frameTitleBounding.height / 2,
|
||||
];
|
||||
|
||||
await addNote(page, '', frameTitleCenter[0], frameTitleCenter[1]);
|
||||
await pressEscape(page, 3);
|
||||
await waitNextFrame(page, 500);
|
||||
|
||||
try {
|
||||
// if the frame title is rendered on the top, it should be clickable
|
||||
await frameTitle.click();
|
||||
} catch {
|
||||
expect(true, 'frame title should be rendered on the top').toBeFalsy();
|
||||
}
|
||||
});
|
||||
|
||||
test('should not display frame title component when title is empty', async ({
|
||||
page,
|
||||
}) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
await enterFrameTitleEditor(page);
|
||||
|
||||
await pressBackspace(page);
|
||||
await pressEnter(page);
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
await expect(frameTitle).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('frame title editing', () => {
|
||||
test('edit frame title by db-click title', async ({ page }) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
|
||||
await enterFrameTitleEditor(page);
|
||||
|
||||
await type(page, 'ABC');
|
||||
await pressEnter(page);
|
||||
await expect(frameTitle).toHaveText('ABC');
|
||||
});
|
||||
|
||||
test('frame title can be edited repeatedly', async ({ page }) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
|
||||
await enterFrameTitleEditor(page);
|
||||
await type(page, 'ABC');
|
||||
await pressEnter(page);
|
||||
|
||||
await enterFrameTitleEditor(page);
|
||||
await type(page, 'DEF');
|
||||
await pressEnter(page);
|
||||
await expect(frameTitle).toHaveText('DEF');
|
||||
});
|
||||
|
||||
test('edit frame after zoom', async ({ page }) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
|
||||
await zoomOutByKeyboard(page);
|
||||
await enterFrameTitleEditor(page);
|
||||
await type(page, 'ABC');
|
||||
await pressEnter(page);
|
||||
await expect(frameTitle).toHaveText('ABC');
|
||||
});
|
||||
|
||||
test('edit frame title after drag', async ({ page }) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
await dragBetweenViewCoords(page, [50 + 10, 50 + 10], [50 + 20, 50 + 20]);
|
||||
|
||||
await enterFrameTitleEditor(page);
|
||||
await type(page, 'ABC');
|
||||
await pressEnter(page);
|
||||
await expect(frameTitle).toHaveText('ABC');
|
||||
});
|
||||
|
||||
test('blur unmount frame editor', async ({ page }) => {
|
||||
await createFrame(page, [50, 50], [150, 150]);
|
||||
const frameTitleEditor = await enterFrameTitleEditor(page);
|
||||
await page.mouse.click(10, 10);
|
||||
await expect(frameTitleEditor).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('enter unmount frame editor', async ({ page }) => {
|
||||
await createFrame(page, [50, 50], [150, 150]);
|
||||
const frameTitleEditor = await enterFrameTitleEditor(page);
|
||||
await pressEnter(page);
|
||||
await expect(frameTitleEditor).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
414
blocksuite/tests-legacy/e2e/edgeless/frame/frame.spec.ts
Normal file
414
blocksuite/tests-legacy/e2e/edgeless/frame/frame.spec.ts
Normal file
@@ -0,0 +1,414 @@
|
||||
import { Bound } from '@blocksuite/global/utils';
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import { clickView } from '../../utils/actions/click.js';
|
||||
import {
|
||||
addNote,
|
||||
autoFit,
|
||||
createFrame as _createFrame,
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
getFirstContainerId,
|
||||
getIds,
|
||||
getSelectedBound,
|
||||
getSelectedIds,
|
||||
pickColorAtPoints,
|
||||
setEdgelessTool,
|
||||
Shape,
|
||||
shiftClickView,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
zoomResetByKeyboard,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
pressBackspace,
|
||||
pressEscape,
|
||||
SHORT_KEY,
|
||||
} from '../../utils/actions/keyboard.js';
|
||||
import {
|
||||
assertCanvasElementsCount,
|
||||
assertContainerChildCount,
|
||||
assertEdgelessElementBound,
|
||||
assertSelectedBound,
|
||||
} from '../../utils/asserts.js';
|
||||
import {
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
} from '../../utils/bs-alternative.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
const createFrame = async (
|
||||
page: Page,
|
||||
coord1: [number, number],
|
||||
coord2: [number, number]
|
||||
) => {
|
||||
const frameId = await _createFrame(page, coord1, coord2);
|
||||
await autoFit(page);
|
||||
await pressEscape(page);
|
||||
return frameId;
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
});
|
||||
|
||||
test.describe('add a frame', () => {
|
||||
const createThreeShapesAndSelectTowShape = async (page: Page) => {
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 0], [200, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
|
||||
await clickView(page, [50, 50]);
|
||||
await shiftClickView(page, [150, 50]);
|
||||
};
|
||||
|
||||
test('multi select and add frame by shortcut F', async ({ page }) => {
|
||||
await createThreeShapesAndSelectTowShape(page);
|
||||
await page.keyboard.press('f');
|
||||
|
||||
await expect(page.locator('affine-frame')).toHaveCount(1);
|
||||
await assertSelectedBound(page, [-40, -40, 280, 180]);
|
||||
|
||||
const frameId = await getFirstContainerId(page);
|
||||
await assertContainerChildCount(page, frameId, 2);
|
||||
});
|
||||
|
||||
test('multi select and add frame by component toolbar', async ({ page }) => {
|
||||
await createThreeShapesAndSelectTowShape(page);
|
||||
await triggerComponentToolbarAction(page, 'addFrame');
|
||||
|
||||
await expect(page.locator('affine-frame')).toHaveCount(1);
|
||||
await assertSelectedBound(page, [-40, -40, 280, 180]);
|
||||
|
||||
const frameId = await getFirstContainerId(page);
|
||||
await assertContainerChildCount(page, frameId, 2);
|
||||
});
|
||||
|
||||
test('multi select and add frame by more option create frame', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createThreeShapesAndSelectTowShape(page);
|
||||
await triggerComponentToolbarAction(page, 'createFrameOnMoreOption');
|
||||
|
||||
await expect(page.locator('affine-frame')).toHaveCount(1);
|
||||
await assertSelectedBound(page, [-40, -40, 280, 180]);
|
||||
|
||||
const frameId = await getFirstContainerId(page);
|
||||
await assertContainerChildCount(page, frameId, 2);
|
||||
});
|
||||
|
||||
test('multi select add frame by edgeless toolbar', async ({ page }) => {
|
||||
await createThreeShapesAndSelectTowShape(page);
|
||||
await autoFit(page);
|
||||
await setEdgelessTool(page, 'frame');
|
||||
const frameMenu = page.locator('edgeless-frame-menu');
|
||||
await expect(frameMenu).toBeVisible();
|
||||
const button = page.locator('.frame-add-button[data-name="1:1"]');
|
||||
await button.click();
|
||||
await assertSelectedBound(page, [-450, -550, 1200, 1200]);
|
||||
|
||||
// the third should be inner frame because
|
||||
const frameId = await getFirstContainerId(page);
|
||||
await assertContainerChildCount(page, frameId, 3);
|
||||
});
|
||||
|
||||
test('add frame by dragging with shortcut F', async ({ page }) => {
|
||||
await createThreeShapesAndSelectTowShape(page);
|
||||
await pressEscape(page); // unselect
|
||||
|
||||
await page.keyboard.press('f');
|
||||
await dragBetweenViewCoords(page, [-10, -10], [210, 110]);
|
||||
|
||||
await expect(page.locator('affine-frame')).toHaveCount(1);
|
||||
await assertSelectedBound(page, [-10, -10, 220, 120]);
|
||||
|
||||
const frameId = await getFirstContainerId(page);
|
||||
await assertContainerChildCount(page, frameId, 2);
|
||||
});
|
||||
|
||||
test('add inner frame', async ({ page }) => {
|
||||
await createFrame(page, [50, 50], [450, 450]);
|
||||
await createShapeElement(page, [200, 200], [300, 300], Shape.Square);
|
||||
await pressEscape(page);
|
||||
|
||||
await shiftClickView(page, [250, 250]);
|
||||
await page.keyboard.press('f');
|
||||
const innerFrameBound = await getSelectedBound(page);
|
||||
expect(
|
||||
new Bound(50, 50, 400, 400).contains(Bound.fromXYWH(innerFrameBound))
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('add element to frame and then move frame', () => {
|
||||
test.describe('add single element', () => {
|
||||
test('element should be moved since it is created in frame', async ({
|
||||
page,
|
||||
}) => {
|
||||
const frameId = await createFrame(page, [50, 50], [550, 550]);
|
||||
const shapeId = await createShapeElement(
|
||||
page,
|
||||
[100, 100],
|
||||
[200, 200],
|
||||
Shape.Square
|
||||
);
|
||||
|
||||
const noteCoord = await toViewCoord(page, [200, 200]);
|
||||
const noteId = await addNote(page, '', noteCoord[0], noteCoord[1]);
|
||||
|
||||
const frameTitle = page.locator('affine-frame-title');
|
||||
|
||||
await pressEscape(page);
|
||||
|
||||
await frameTitle.click();
|
||||
await dragBetweenViewCoords(page, [60, 60], [110, 110]);
|
||||
|
||||
await assertEdgelessElementBound(page, shapeId, [150, 150, 100, 100]);
|
||||
await assertEdgelessElementBound(page, frameId, [100, 100, 500, 500]);
|
||||
await assertEdgelessElementBound(page, noteId, [
|
||||
220,
|
||||
210,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
]);
|
||||
});
|
||||
|
||||
test('element should be not moved since it is created not in frame', async ({
|
||||
page,
|
||||
}) => {
|
||||
const frameId = await createFrame(page, [50, 50], [550, 550]);
|
||||
const shapeId = await createShapeElement(
|
||||
page,
|
||||
[600, 600],
|
||||
[500, 500],
|
||||
Shape.Square
|
||||
);
|
||||
await pressEscape(page);
|
||||
|
||||
const frameTitle = page.locator('affine-frame-title');
|
||||
|
||||
await frameTitle.click();
|
||||
await dragBetweenViewCoords(page, [60, 60], [110, 110]);
|
||||
|
||||
await assertEdgelessElementBound(page, shapeId, [500, 500, 100, 100]);
|
||||
await assertEdgelessElementBound(page, frameId, [100, 100, 500, 500]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('add group', () => {
|
||||
// Group
|
||||
// |<150px>|
|
||||
// ┌────┐ ─
|
||||
// │ ┌─┼──┐ 150 px
|
||||
// └──┼─┘ │ |
|
||||
// └────┘ ─
|
||||
|
||||
test('group should be moved since it is fully contained in frame', async ({
|
||||
page,
|
||||
}) => {
|
||||
const [frameId, ...shapeIds] = [
|
||||
await createFrame(page, [50, 50], [550, 550]),
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square),
|
||||
await createShapeElement(page, [150, 150], [250, 250], Shape.Square),
|
||||
];
|
||||
await pressEscape(page);
|
||||
|
||||
const frameTitle = page.locator('affine-frame-title');
|
||||
|
||||
await shiftClickView(page, [110, 110]);
|
||||
await shiftClickView(page, [160, 160]);
|
||||
await page.keyboard.press(`${SHORT_KEY}+g`);
|
||||
const groupId = (await getSelectedIds(page))[0];
|
||||
await pressEscape(page);
|
||||
|
||||
await frameTitle.click();
|
||||
await dragBetweenViewCoords(page, [60, 60], [110, 110]);
|
||||
|
||||
await assertEdgelessElementBound(page, shapeIds[0], [150, 150, 100, 100]);
|
||||
await assertEdgelessElementBound(page, shapeIds[1], [200, 200, 100, 100]);
|
||||
await assertEdgelessElementBound(page, groupId, [150, 150, 150, 150]);
|
||||
await assertEdgelessElementBound(page, frameId, [100, 100, 500, 500]);
|
||||
});
|
||||
|
||||
test('group should be moved since its center is in frame', async ({
|
||||
page,
|
||||
}) => {
|
||||
const [frameId, ...shapeIds] = [
|
||||
await createFrame(page, [50, 50], [550, 550]),
|
||||
await createShapeElement(page, [450, 450], [550, 550], Shape.Square),
|
||||
await createShapeElement(page, [500, 500], [600, 600], Shape.Square),
|
||||
];
|
||||
await pressEscape(page);
|
||||
|
||||
const frameTitle = page.locator('affine-frame-title');
|
||||
|
||||
await shiftClickView(page, [460, 460]);
|
||||
await shiftClickView(page, [510, 510]);
|
||||
await page.keyboard.press(`${SHORT_KEY}+g`);
|
||||
const groupId = (await getSelectedIds(page))[0];
|
||||
await pressEscape(page);
|
||||
|
||||
await frameTitle.click();
|
||||
await dragBetweenViewCoords(page, [60, 60], [110, 110]);
|
||||
|
||||
await assertEdgelessElementBound(page, shapeIds[0], [500, 500, 100, 100]);
|
||||
await assertEdgelessElementBound(page, shapeIds[1], [550, 550, 100, 100]);
|
||||
await assertEdgelessElementBound(page, groupId, [500, 500, 150, 150]);
|
||||
await assertEdgelessElementBound(page, frameId, [100, 100, 500, 500]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('add inner frame', () => {
|
||||
test('the inner frame and its children should be moved since it is fully contained in frame', async ({
|
||||
page,
|
||||
}) => {
|
||||
const [frameId, innerId, shapeId] = [
|
||||
await createFrame(page, [50, 50], [550, 550]),
|
||||
await createFrame(page, [100, 100], [300, 300]),
|
||||
await createShapeElement(page, [150, 150], [250, 250], Shape.Square),
|
||||
];
|
||||
await pressEscape(page);
|
||||
|
||||
const frameTitles = page.locator('affine-frame-title');
|
||||
|
||||
await frameTitles.nth(0).click();
|
||||
await dragBetweenViewCoords(page, [60, 60], [110, 110]);
|
||||
|
||||
await assertEdgelessElementBound(page, shapeId, [200, 200, 100, 100]);
|
||||
await assertEdgelessElementBound(page, innerId, [150, 150, 200, 200]);
|
||||
await assertEdgelessElementBound(page, frameId, [100, 100, 500, 500]);
|
||||
});
|
||||
|
||||
test('the inner frame and its children should be moved since its center is in frame', async ({
|
||||
page,
|
||||
}) => {
|
||||
const [frameId, innerId, shapeId] = [
|
||||
await createFrame(page, [50, 50], [550, 550]),
|
||||
await createFrame(page, [400, 400], [600, 600]),
|
||||
await createShapeElement(page, [550, 550], [600, 600], Shape.Square),
|
||||
];
|
||||
await pressEscape(page);
|
||||
|
||||
const frameTitles = page.locator('affine-frame-title');
|
||||
|
||||
await frameTitles.nth(0).click();
|
||||
await dragBetweenViewCoords(page, [60, 60], [110, 110]);
|
||||
|
||||
await assertEdgelessElementBound(page, shapeId, [600, 600, 50, 50]);
|
||||
await assertEdgelessElementBound(page, innerId, [450, 450, 200, 200]);
|
||||
await assertEdgelessElementBound(page, frameId, [100, 100, 500, 500]);
|
||||
});
|
||||
|
||||
test('the inner frame and its children should also be moved even though its center is not in frame', async ({
|
||||
page,
|
||||
}) => {
|
||||
const [frameId, innerId, shapeId] = [
|
||||
await createFrame(page, [50, 50], [550, 550]),
|
||||
await createFrame(page, [500, 500], [600, 600]),
|
||||
await createShapeElement(page, [550, 550], [600, 600], Shape.Square),
|
||||
];
|
||||
|
||||
const frameTitles = page.locator('affine-frame-title');
|
||||
|
||||
await frameTitles.nth(0).click();
|
||||
await dragBetweenViewCoords(page, [60, 60], [110, 110]);
|
||||
|
||||
await assertEdgelessElementBound(page, shapeId, [600, 600, 50, 50]);
|
||||
await assertEdgelessElementBound(page, innerId, [550, 550, 100, 100]);
|
||||
await assertEdgelessElementBound(page, frameId, [100, 100, 500, 500]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('resize frame then move ', () => {
|
||||
test('resize frame to warp shape', async ({ page }) => {
|
||||
const [frameId, shapeId] = [
|
||||
await createFrame(page, [50, 50], [150, 150]),
|
||||
await createShapeElement(page, [200, 200], [300, 300], Shape.Square),
|
||||
];
|
||||
await pressEscape(page);
|
||||
|
||||
const frameTitle = page.locator('affine-frame-title');
|
||||
|
||||
await frameTitle.click();
|
||||
await dragBetweenViewCoords(page, [150, 150], [450, 450]);
|
||||
|
||||
await dragBetweenViewCoords(page, [60, 60], [110, 110]);
|
||||
|
||||
await assertEdgelessElementBound(page, shapeId, [250, 250, 100, 100]);
|
||||
await assertEdgelessElementBound(page, frameId, [100, 100, 400, 400]);
|
||||
});
|
||||
|
||||
test('resize frame to unwrap shape', async ({ page }) => {
|
||||
const [frameId, shapeId] = [
|
||||
await createFrame(page, [50, 50], [450, 450]),
|
||||
await createShapeElement(page, [200, 200], [300, 300], Shape.Square),
|
||||
];
|
||||
await pressEscape(page);
|
||||
|
||||
const frameTitle = page.locator('affine-frame-title');
|
||||
|
||||
await frameTitle.click();
|
||||
await dragBetweenViewCoords(page, [450, 450], [150, 150]);
|
||||
|
||||
await dragBetweenViewCoords(page, [60, 60], [110, 110]);
|
||||
|
||||
await assertEdgelessElementBound(page, shapeId, [200, 200, 100, 100]);
|
||||
await assertEdgelessElementBound(page, frameId, [100, 100, 100, 100]);
|
||||
});
|
||||
});
|
||||
|
||||
test('delete frame should also delete its children', async ({ page }) => {
|
||||
await createFrame(page, [50, 50], [450, 450]);
|
||||
await createShapeElement(page, [200, 200], [300, 300], Shape.Square);
|
||||
await pressEscape(page);
|
||||
|
||||
const frameTitle = page.locator('affine-frame-title');
|
||||
|
||||
await frameTitle.click();
|
||||
await pressBackspace(page);
|
||||
await expect(page.locator('affine-frame')).toHaveCount(0);
|
||||
|
||||
await assertCanvasElementsCount(page, 0);
|
||||
});
|
||||
|
||||
test('delete frame by click ungroup should not delete its children', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [450, 450]);
|
||||
const shapeId = await createShapeElement(
|
||||
page,
|
||||
[200, 200],
|
||||
[300, 300],
|
||||
Shape.Square
|
||||
);
|
||||
await pressEscape(page);
|
||||
|
||||
const frameTitle = page.locator('affine-frame-title');
|
||||
await frameTitle.click();
|
||||
const elementToolbar = page.locator('edgeless-element-toolbar-widget');
|
||||
const ungroupButton = elementToolbar.getByLabel('Ungroup');
|
||||
await ungroupButton.click();
|
||||
|
||||
await assertCanvasElementsCount(page, 1);
|
||||
expect(await getIds(page)).toEqual([shapeId]);
|
||||
});
|
||||
|
||||
test('outline should keep updated during a new frame created by frame-tool dragging', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.keyboard.press('f');
|
||||
|
||||
const start = await toViewCoord(page, [0, 0]);
|
||||
const end = await toViewCoord(page, [100, 100]);
|
||||
await page.mouse.move(start[0], start[1]);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(end[0], end[1], { steps: 10 });
|
||||
await page.waitForTimeout(50);
|
||||
|
||||
expect(
|
||||
await pickColorAtPoints(page, [start, [end[0] - 1, end[1] - 1]])
|
||||
).toEqual(['#1e96eb', '#1e96eb']);
|
||||
});
|
||||
67
blocksuite/tests-legacy/e2e/edgeless/frame/layer.spec.ts
Normal file
67
blocksuite/tests-legacy/e2e/edgeless/frame/layer.spec.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
createFrame,
|
||||
createNote,
|
||||
createShapeElement,
|
||||
edgelessCommonSetup,
|
||||
getAllSortedIds,
|
||||
getEdgelessSelectedRectModel,
|
||||
Shape,
|
||||
zoomResetByKeyboard,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
pressEscape,
|
||||
selectAllByKeyboard,
|
||||
} from '../../utils/actions/keyboard.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
});
|
||||
|
||||
test.describe('layer logic of frame block', () => {
|
||||
test('a new frame should be on the bottom layer', async ({ page }) => {
|
||||
const shapeId = await createShapeElement(
|
||||
page,
|
||||
[100, 100],
|
||||
[200, 200],
|
||||
Shape.Square
|
||||
);
|
||||
const noteId = await createNote(page, [200, 200]);
|
||||
await pressEscape(page, 3);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
const [x, y, w, h] = await getEdgelessSelectedRectModel(page);
|
||||
await pressEscape(page);
|
||||
const frameAId = await createFrame(
|
||||
page,
|
||||
[x - 10, y - 10],
|
||||
[x + w + 10, y + h + 10]
|
||||
);
|
||||
|
||||
let sortedIds = await getAllSortedIds(page);
|
||||
expect(
|
||||
sortedIds[0],
|
||||
'a new frame created by frame-tool should be on the bottom layer'
|
||||
).toBe(frameAId);
|
||||
expect(sortedIds[1]).toBe(shapeId);
|
||||
expect(sortedIds[2]).toBe(noteId);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await page.keyboard.press('f');
|
||||
|
||||
sortedIds = await getAllSortedIds(page);
|
||||
const frameBId = sortedIds.find(
|
||||
id => ![frameAId, noteId, shapeId].includes(id)
|
||||
);
|
||||
expect(
|
||||
sortedIds[0],
|
||||
'a new frame created by short-cut should also be on the bottom layer'
|
||||
).toBe(frameBId);
|
||||
expect(sortedIds[1]).toBe(frameAId);
|
||||
expect(sortedIds[2]).toBe(shapeId);
|
||||
expect(sortedIds[3]).toBe(noteId);
|
||||
});
|
||||
});
|
||||
158
blocksuite/tests-legacy/e2e/edgeless/frame/selection.spec.ts
Normal file
158
blocksuite/tests-legacy/e2e/edgeless/frame/selection.spec.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import { click, clickView, dblclickView } from '../../utils/actions/click.js';
|
||||
import {
|
||||
addNote,
|
||||
autoFit,
|
||||
createFrame as _createFrame,
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
getFrameTitle,
|
||||
getSelectedBoundCount,
|
||||
getSelectedIds,
|
||||
Shape,
|
||||
toViewCoord,
|
||||
zoomResetByKeyboard,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
} from '../../utils/actions/keyboard.js';
|
||||
import { waitNextFrame } from '../../utils/actions/misc.js';
|
||||
import {
|
||||
assertEdgelessCanvasText,
|
||||
assertRichTexts,
|
||||
assertSelectedBound,
|
||||
} from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
const createFrame = async (
|
||||
page: Page,
|
||||
coord1: [number, number],
|
||||
coord2: [number, number]
|
||||
) => {
|
||||
const frame = await _createFrame(page, coord1, coord2);
|
||||
await autoFit(page);
|
||||
return frame;
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
});
|
||||
|
||||
test.describe('frame selection', () => {
|
||||
test('frame can not be selected by click blank area of frame if it has title', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [150, 150]);
|
||||
await pressEscape(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
|
||||
await clickView(page, [100, 100]);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
});
|
||||
|
||||
test('frame can selected by click blank area of frame if it has not title', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [150, 150]);
|
||||
await pressEscape(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
|
||||
await page.locator('affine-frame-title').dblclick();
|
||||
await pressBackspace(page);
|
||||
await pressEnter(page);
|
||||
|
||||
await clickView(page, [100, 100]);
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
});
|
||||
|
||||
test('frame can be selected by click frame title', async ({ page }) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
await pressEscape(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
await frameTitle.click();
|
||||
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
await assertSelectedBound(page, [50, 50, 100, 100]);
|
||||
});
|
||||
|
||||
test('frame can be selected by click frame title when a note overlap on it', async ({
|
||||
page,
|
||||
}) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
await pressEscape(page);
|
||||
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
const frameTitleBox = await frameTitle.boundingBox();
|
||||
expect(frameTitleBox).not.toBeNull();
|
||||
if (frameTitleBox === null) return;
|
||||
|
||||
const frameTitleCenter = {
|
||||
x: frameTitleBox.x + frameTitleBox.width / 2,
|
||||
y: frameTitleBox.y + frameTitleBox.height / 2,
|
||||
};
|
||||
|
||||
await addNote(page, '', frameTitleCenter.x - 10, frameTitleCenter.y);
|
||||
await pressEscape(page, 3);
|
||||
await waitNextFrame(page, 500);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
|
||||
await click(page, frameTitleCenter);
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
const selectedIds = await getSelectedIds(page);
|
||||
expect(selectedIds.length).toBe(1);
|
||||
expect(selectedIds[0]).toBe(frame);
|
||||
});
|
||||
|
||||
test('shape inside frame can be selected and edited', async ({ page }) => {
|
||||
await createFrame(page, [50, 50], [150, 150]);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await pressEscape(page);
|
||||
|
||||
await clickView(page, [150, 150]);
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
await assertSelectedBound(page, [100, 100, 100, 100]);
|
||||
|
||||
await dblclickView(page, [150, 150]);
|
||||
await type(page, 'hello');
|
||||
await assertEdgelessCanvasText(page, 'hello');
|
||||
});
|
||||
|
||||
test('dom inside frame can be selected and edited', async ({ page }) => {
|
||||
await createFrame(page, [50, 50], [150, 150]);
|
||||
const noteCoord = await toViewCoord(page, [100, 100]);
|
||||
await addNote(page, '', noteCoord[0], noteCoord[1]);
|
||||
await page.mouse.click(noteCoord[0] - 80, noteCoord[1]);
|
||||
|
||||
await dblclickView(page, [150, 150]);
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['hello']);
|
||||
});
|
||||
|
||||
test('element in frame should not be selected when frame is selected by drag or Cmd/Ctrl + A', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [200, 200]);
|
||||
await createShapeElement(page, [100, 100], [150, 150], Shape.Square);
|
||||
await pressEscape(page);
|
||||
|
||||
await dragBetweenViewCoords(page, [0, 0], [250, 250]);
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
await assertSelectedBound(page, [50, 50, 150, 150]);
|
||||
|
||||
await pressEscape(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
await assertSelectedBound(page, [50, 50, 150, 150]);
|
||||
});
|
||||
});
|
||||
152
blocksuite/tests-legacy/e2e/edgeless/group/clipboard.spec.ts
Normal file
152
blocksuite/tests-legacy/e2e/edgeless/group/clipboard.spec.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
copyByKeyboard,
|
||||
createConnectorElement,
|
||||
createNote,
|
||||
createShapeElement,
|
||||
decreaseZoomLevel,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
edgelessCommonSetup,
|
||||
getAllSortedIds,
|
||||
getFirstContainerId,
|
||||
pasteByKeyboard,
|
||||
selectAllByKeyboard,
|
||||
Shape,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/index.js';
|
||||
import {
|
||||
assertContainerChildCount,
|
||||
assertContainerIds,
|
||||
} from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test.describe('clipboard', () => {
|
||||
test('copy and paste group', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 0], [200, 100], Shape.Square);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const originGroupId = await getFirstContainerId(page);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
await waitNextFrame(page, 100);
|
||||
const move = await toViewCoord(page, [100, -50]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await waitNextFrame(page, 1000);
|
||||
await pasteByKeyboard(page, false);
|
||||
const copyedGroupId = await getFirstContainerId(page, [originGroupId]);
|
||||
|
||||
await assertContainerIds(page, {
|
||||
[originGroupId]: 2,
|
||||
[copyedGroupId]: 2,
|
||||
null: 2,
|
||||
});
|
||||
await assertContainerChildCount(page, originGroupId, 2);
|
||||
await assertContainerChildCount(page, copyedGroupId, 2);
|
||||
});
|
||||
|
||||
test('copy and paste group with connector', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 0], [200, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 50], [200, 50]);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const originGroupId = await getFirstContainerId(page);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
await waitNextFrame(page, 100);
|
||||
const move = await toViewCoord(page, [100, -50]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await waitNextFrame(page, 1000);
|
||||
await pasteByKeyboard(page, false);
|
||||
const copyedGroupId = await getFirstContainerId(page, [originGroupId]);
|
||||
|
||||
await assertContainerIds(page, {
|
||||
[originGroupId]: 3,
|
||||
[copyedGroupId]: 3,
|
||||
null: 2,
|
||||
});
|
||||
await assertContainerChildCount(page, originGroupId, 3);
|
||||
await assertContainerChildCount(page, copyedGroupId, 3);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('group clipboard', () => {
|
||||
test('copy and paste group with shape and note inside', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [100, -100]);
|
||||
await page.mouse.click(10, 50);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
const originIds = await getAllSortedIds(page);
|
||||
expect(originIds.length).toBe(3);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(6);
|
||||
});
|
||||
|
||||
test('copy and paste group with group inside', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
await createNote(page, [100, -100]);
|
||||
await page.mouse.click(10, 50);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'createGroupOnMoreOption');
|
||||
|
||||
const originIds = await getAllSortedIds(page);
|
||||
expect(originIds.length).toBe(5);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(10);
|
||||
});
|
||||
|
||||
test('copy and paste group with frame inside', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [100, -100]);
|
||||
await page.mouse.click(10, 50);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addFrame');
|
||||
|
||||
await decreaseZoomLevel(page);
|
||||
await createShapeElement(page, [700, 0], [800, 100], Shape.Square);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
const originIds = await getAllSortedIds(page);
|
||||
expect(originIds.length).toBe(5);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(10);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
captureHistory,
|
||||
clickView,
|
||||
createShapeElement,
|
||||
edgelessCommonSetup,
|
||||
getFirstContainerId,
|
||||
getIds,
|
||||
redoByKeyboard,
|
||||
selectAllByKeyboard,
|
||||
Shape,
|
||||
shiftClickView,
|
||||
toIdCountMap,
|
||||
triggerComponentToolbarAction,
|
||||
undoByKeyboard,
|
||||
} from '../../utils/actions/index.js';
|
||||
import {
|
||||
assertContainerChildCount,
|
||||
assertContainerChildIds,
|
||||
assertContainerIds,
|
||||
assertSelectedBound,
|
||||
} from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
let initShapes: string[] = [];
|
||||
async function init(page: Page) {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 0], [200, 100], Shape.Square);
|
||||
initShapes = await getIds(page);
|
||||
}
|
||||
|
||||
test.describe('group and ungroup in group', () => {
|
||||
let outterGroupId: string;
|
||||
let newAddedShape: string;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
newAddedShape = (await getIds(page)).filter(
|
||||
id => !initShapes.includes(id)
|
||||
)[0];
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
outterGroupId = await getFirstContainerId(page);
|
||||
});
|
||||
|
||||
test('group in group', async ({ page }) => {
|
||||
await clickView(page, [50, 50]);
|
||||
await shiftClickView(page, [150, 50]);
|
||||
await captureHistory(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const groupId = await getFirstContainerId(page, [outterGroupId]);
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
[outterGroupId]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
await assertContainerChildCount(page, outterGroupId, 2);
|
||||
|
||||
// undo the creation
|
||||
await undoByKeyboard(page);
|
||||
await assertContainerIds(page, {
|
||||
[outterGroupId]: 3,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, outterGroupId, 3);
|
||||
|
||||
// redo the creation
|
||||
await redoByKeyboard(page);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
[outterGroupId]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
await assertContainerChildCount(page, outterGroupId, 2);
|
||||
});
|
||||
|
||||
test('ungroup in group', async ({ page }) => {
|
||||
await clickView(page, [50, 50]);
|
||||
await shiftClickView(page, [150, 50]);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await captureHistory(page);
|
||||
const groupId = await getFirstContainerId(page, [outterGroupId]);
|
||||
await triggerComponentToolbarAction(page, 'ungroup');
|
||||
await assertContainerIds(page, { [outterGroupId]: 3, null: 1 });
|
||||
await assertContainerChildIds(
|
||||
page,
|
||||
toIdCountMap(await getIds(page, true)),
|
||||
outterGroupId
|
||||
);
|
||||
|
||||
// undo, group should in group again
|
||||
await undoByKeyboard(page);
|
||||
await assertContainerIds(page, {
|
||||
[outterGroupId]: 2,
|
||||
[groupId]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildIds(page, toIdCountMap(initShapes), groupId);
|
||||
await assertContainerChildIds(
|
||||
page,
|
||||
{
|
||||
[groupId]: 1,
|
||||
[newAddedShape]: 1,
|
||||
},
|
||||
outterGroupId
|
||||
);
|
||||
|
||||
// redo, group should be ungroup again
|
||||
await redoByKeyboard(page);
|
||||
await assertContainerIds(page, { [outterGroupId]: 3, null: 1 });
|
||||
await assertContainerChildIds(
|
||||
page,
|
||||
toIdCountMap(await getIds(page, true)),
|
||||
outterGroupId
|
||||
);
|
||||
});
|
||||
});
|
||||
260
blocksuite/tests-legacy/e2e/edgeless/group/group.spec.ts
Normal file
260
blocksuite/tests-legacy/e2e/edgeless/group/group.spec.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import { clickView } from '../../utils/actions/click.js';
|
||||
import {
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
getFirstContainerId,
|
||||
Shape,
|
||||
shiftClickView,
|
||||
triggerComponentToolbarAction,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
pressBackspace,
|
||||
redoByKeyboard,
|
||||
selectAllByKeyboard,
|
||||
SHORT_KEY,
|
||||
undoByKeyboard,
|
||||
} from '../../utils/actions/keyboard.js';
|
||||
import { captureHistory } from '../../utils/actions/misc.js';
|
||||
import {
|
||||
assertCanvasElementsCount,
|
||||
assertContainerChildCount,
|
||||
assertContainerIds,
|
||||
assertEdgelessNonSelectedRect,
|
||||
assertSelectedBound,
|
||||
} from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
export const GROUP_ROOT_ID = 'GROUP_ROOT';
|
||||
|
||||
test.describe('group', () => {
|
||||
async function init(page: Page) {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 0], [200, 100], Shape.Square);
|
||||
}
|
||||
|
||||
test.describe('group create', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
});
|
||||
|
||||
test('create group button not show when single select', async ({
|
||||
page,
|
||||
}) => {
|
||||
await clickView(page, [50, 50]);
|
||||
await expect(
|
||||
page.locator('edgeless-element-toolbar-widget')
|
||||
).toBeVisible();
|
||||
await expect(page.locator('edgeless-add-group-button')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('create button show up when multi select', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await expect(page.locator('edgeless-add-group-button')).toBeVisible();
|
||||
});
|
||||
|
||||
test('create group by component toolbar', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
});
|
||||
|
||||
test('create group by shortcut mod + G', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+g`);
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
});
|
||||
|
||||
test('create group and undo, redo', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await captureHistory(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+g`);
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
await undoByKeyboard(page);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
await redoByKeyboard(page);
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('ungroup', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
});
|
||||
|
||||
test('ungroup by component toolbar', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
await triggerComponentToolbarAction(page, 'ungroup');
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test('ungroup by shortcut mod + shift + G', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
await page.keyboard.press(`${SHORT_KEY}+Shift+g`);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test('ungroup and undo, redo', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
await captureHistory(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+Shift+g`);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
await undoByKeyboard(page);
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
await redoByKeyboard(page);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('drag group', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
});
|
||||
|
||||
test('drag group to move', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await dragBetweenViewCoords(page, [100, 50], [110, 50]);
|
||||
await assertSelectedBound(page, [10, 0, 200, 100]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('select', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
});
|
||||
|
||||
test('select group by click', async ({ page }) => {
|
||||
await clickView(page, [300, -100]);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
await clickView(page, [50, 50]);
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
});
|
||||
|
||||
test('select sub-element by first select group', async ({ page }) => {
|
||||
await clickView(page, [50, 50]);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
});
|
||||
|
||||
test('select element when enter gorup', async ({ page }) => {
|
||||
await clickView(page, [50, 50]);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
await clickView(page, [150, 50]);
|
||||
await assertSelectedBound(page, [100, 0, 100, 100]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('delete', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
});
|
||||
|
||||
test('delete root group', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const groupId = await getFirstContainerId(page);
|
||||
await captureHistory(page);
|
||||
await pressBackspace(page);
|
||||
await assertCanvasElementsCount(page, 0);
|
||||
|
||||
// undo the delete
|
||||
await undoByKeyboard(page);
|
||||
await assertCanvasElementsCount(page, 3);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
|
||||
// redo the delete
|
||||
await redoByKeyboard(page);
|
||||
await assertCanvasElementsCount(page, 0);
|
||||
});
|
||||
|
||||
test('delete sub-element in group', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const groupId = await getFirstContainerId(page);
|
||||
await captureHistory(page);
|
||||
await clickView(page, [50, 50]);
|
||||
await pressBackspace(page);
|
||||
|
||||
await assertCanvasElementsCount(page, 2);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 1,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 1);
|
||||
|
||||
// undo the delete
|
||||
await undoByKeyboard(page);
|
||||
await assertCanvasElementsCount(page, 3);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
|
||||
// redo the delete
|
||||
await redoByKeyboard(page);
|
||||
await assertCanvasElementsCount(page, 2);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 1,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 1);
|
||||
});
|
||||
|
||||
test('delete group in group', async ({ page }) => {
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const firstGroup = await getFirstContainerId(page);
|
||||
await clickView(page, [50, 50]);
|
||||
await shiftClickView(page, [150, 50]);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const secondGroup = await getFirstContainerId(page, [firstGroup]);
|
||||
await captureHistory(page);
|
||||
|
||||
// delete group in group
|
||||
await pressBackspace(page);
|
||||
await assertCanvasElementsCount(page, 2);
|
||||
await assertContainerIds(page, {
|
||||
[firstGroup]: 1,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, firstGroup, 1);
|
||||
|
||||
// undo the delete
|
||||
await undoByKeyboard(page);
|
||||
await assertCanvasElementsCount(page, 5);
|
||||
await assertContainerIds(page, {
|
||||
[firstGroup]: 2,
|
||||
[secondGroup]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, firstGroup, 2);
|
||||
await assertContainerChildCount(page, secondGroup, 2);
|
||||
|
||||
// redo the delete
|
||||
await redoByKeyboard(page);
|
||||
await assertCanvasElementsCount(page, 2);
|
||||
await assertContainerIds(page, {
|
||||
[firstGroup]: 1,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, firstGroup, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
116
blocksuite/tests-legacy/e2e/edgeless/group/release.spec.ts
Normal file
116
blocksuite/tests-legacy/e2e/edgeless/group/release.spec.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
captureHistory,
|
||||
clickView,
|
||||
createShapeElement,
|
||||
edgelessCommonSetup,
|
||||
getFirstContainerId,
|
||||
redoByKeyboard,
|
||||
selectAllByKeyboard,
|
||||
Shape,
|
||||
shiftClickView,
|
||||
triggerComponentToolbarAction,
|
||||
undoByKeyboard,
|
||||
} from '../../utils/actions/index.js';
|
||||
import {
|
||||
assertContainerChildCount,
|
||||
assertContainerIds,
|
||||
assertSelectedBound,
|
||||
} from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
async function init(page: Page) {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 0], [200, 100], Shape.Square);
|
||||
}
|
||||
|
||||
test.describe('release from group', () => {
|
||||
let outterGroupId: string;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
outterGroupId = await getFirstContainerId(page);
|
||||
});
|
||||
|
||||
test('release element from group', async ({ page }) => {
|
||||
await clickView(page, [50, 50]);
|
||||
await captureHistory(page);
|
||||
await triggerComponentToolbarAction(page, 'releaseFromGroup');
|
||||
await assertContainerIds(page, {
|
||||
[outterGroupId]: 2,
|
||||
null: 2,
|
||||
});
|
||||
await assertContainerChildCount(page, outterGroupId, 2);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
|
||||
// undo the release
|
||||
await undoByKeyboard(page);
|
||||
await assertContainerIds(page, {
|
||||
[outterGroupId]: 3,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, outterGroupId, 3);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
|
||||
// redo the release
|
||||
await redoByKeyboard(page);
|
||||
await assertContainerIds(page, {
|
||||
[outterGroupId]: 2,
|
||||
null: 2,
|
||||
});
|
||||
await assertContainerChildCount(page, outterGroupId, 2);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
});
|
||||
|
||||
test('release group from group', async ({ page }) => {
|
||||
await clickView(page, [50, 50]);
|
||||
await shiftClickView(page, [150, 50]);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await captureHistory(page);
|
||||
const groupId = await getFirstContainerId(page, [outterGroupId]);
|
||||
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
[outterGroupId]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
await assertContainerChildCount(page, outterGroupId, 2);
|
||||
|
||||
// release group from group
|
||||
await triggerComponentToolbarAction(page, 'releaseFromGroup');
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
[outterGroupId]: 1,
|
||||
null: 2,
|
||||
});
|
||||
await assertContainerChildCount(page, outterGroupId, 1);
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
|
||||
// undo the release
|
||||
await undoByKeyboard(page);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
[outterGroupId]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
await assertContainerChildCount(page, outterGroupId, 2);
|
||||
|
||||
// redo the release
|
||||
await redoByKeyboard(page);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
[outterGroupId]: 1,
|
||||
null: 2,
|
||||
});
|
||||
await assertContainerChildCount(page, outterGroupId, 1);
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
});
|
||||
});
|
||||
72
blocksuite/tests-legacy/e2e/edgeless/group/title.spec.ts
Normal file
72
blocksuite/tests-legacy/e2e/edgeless/group/title.spec.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
createShapeElement,
|
||||
dblclickView,
|
||||
edgelessCommonSetup,
|
||||
getSelectedBound,
|
||||
pressEnter,
|
||||
selectAllByKeyboard,
|
||||
Shape,
|
||||
triggerComponentToolbarAction,
|
||||
type,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertEdgelessCanvasText } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
async function init(page: Page) {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 0], [200, 100], Shape.Square);
|
||||
}
|
||||
|
||||
test.describe('group title', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
});
|
||||
|
||||
test('edit group title by component toolbar', async ({ page }) => {
|
||||
expect(await page.locator('edgeless-group-title-editor').count()).toBe(0);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'renameGroup');
|
||||
await page.locator('edgeless-group-title-editor').waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
});
|
||||
|
||||
test('edit group title by dbclick', async ({ page }) => {
|
||||
expect(await page.locator('edgeless-group-title-editor').count()).toBe(0);
|
||||
|
||||
const bound = await getSelectedBound(page);
|
||||
await dblclickView(page, [bound[0] + 10, bound[1] - 10]);
|
||||
await page.locator('edgeless-group-title-editor').waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
await type(page, 'ABC');
|
||||
await assertEdgelessCanvasText(page, 'ABC');
|
||||
});
|
||||
|
||||
test('blur unmount group editor', async ({ page }) => {
|
||||
const bound = await getSelectedBound(page);
|
||||
await dblclickView(page, [bound[0] + 10, bound[1] - 10]);
|
||||
|
||||
await page.locator('edgeless-group-title-editor').waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
await page.mouse.click(10, 10);
|
||||
expect(await page.locator('edgeless-group-title-editor').count()).toBe(0);
|
||||
});
|
||||
|
||||
test('enter unmount group editor', async ({ page }) => {
|
||||
const bound = await getSelectedBound(page);
|
||||
await dblclickView(page, [bound[0] + 10, bound[1] - 10]);
|
||||
|
||||
await page.locator('edgeless-group-title-editor').waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
await pressEnter(page);
|
||||
expect(await page.locator('edgeless-group-title-editor').count()).toBe(0);
|
||||
});
|
||||
});
|
||||
262
blocksuite/tests-legacy/e2e/edgeless/lasso.spec.ts
Normal file
262
blocksuite/tests-legacy/e2e/edgeless/lasso.spec.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
import { sleep } from '@blocksuite/global/utils';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addBasicRectShapeElement,
|
||||
assertEdgelessTool,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
setEdgelessTool,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
dragBetweenCoords,
|
||||
selectAllByKeyboard,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessNonSelectedRect,
|
||||
assertEdgelessSelectedRect,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.skip('lasso tool should deselect when dragging in an empty area', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await addBasicRectShapeElement(page, start, end);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await assertEdgelessTool(page, 'lasso');
|
||||
|
||||
await dragBetweenCoords(page, { x: 10, y: 10 }, { x: 15, y: 15 });
|
||||
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test.skip('freehand lasso basic test', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 });
|
||||
|
||||
await page.mouse.click(10, 10); // deselect
|
||||
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await assertEdgelessTool(page, 'lasso');
|
||||
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
|
||||
// simulate a basic lasso selection to select both the rects
|
||||
const points: [number, number][] = [
|
||||
[500, 100],
|
||||
[500, 500],
|
||||
[90, 500],
|
||||
];
|
||||
await page.mouse.move(90, 90);
|
||||
await page.mouse.down();
|
||||
for (const point of points) await page.mouse.move(...point);
|
||||
await page.mouse.up();
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 200, 200]);
|
||||
});
|
||||
|
||||
test.skip('freehand lasso add to selection', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 });
|
||||
|
||||
await page.mouse.click(10, 10); // deselect
|
||||
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await assertEdgelessTool(page, 'lasso');
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
|
||||
// some random selection covering the rectangle
|
||||
let points: [number, number][] = [
|
||||
[250, 90],
|
||||
[250, 300],
|
||||
[10, 300],
|
||||
];
|
||||
await page.mouse.move(90, 90);
|
||||
await page.mouse.down();
|
||||
for (const point of points) await page.mouse.move(...point);
|
||||
await page.mouse.up();
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
points = [
|
||||
[400, 250],
|
||||
[400, 450],
|
||||
[250, 450],
|
||||
];
|
||||
|
||||
await page.keyboard.down('Shift'); // addition selection
|
||||
await page.mouse.move(250, 250);
|
||||
await page.mouse.down();
|
||||
for (const point of points) await page.mouse.move(...point);
|
||||
await page.mouse.up();
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 200, 200]);
|
||||
});
|
||||
|
||||
test.skip('freehand lasso subtract from selection', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 });
|
||||
await setEdgelessTool(page, 'default');
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
|
||||
const points: [number, number][] = [
|
||||
[410, 290],
|
||||
[410, 410],
|
||||
[290, 410],
|
||||
];
|
||||
|
||||
await page.keyboard.down('Alt');
|
||||
|
||||
await page.mouse.move(290, 290);
|
||||
await page.mouse.down();
|
||||
for (const point of points) await page.mouse.move(...point);
|
||||
await page.mouse.up();
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); // only the first rectangle should be selected
|
||||
});
|
||||
|
||||
test.skip('polygonal lasso basic test', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 });
|
||||
await page.mouse.click(10, 10); // deselect
|
||||
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await setEdgelessTool(page, 'lasso'); // switch to polygonal lasso
|
||||
await sleep(100);
|
||||
|
||||
const points: [number, number][] = [
|
||||
[90, 90],
|
||||
[500, 90],
|
||||
[500, 500],
|
||||
[90, 500],
|
||||
[90, 90],
|
||||
];
|
||||
|
||||
for (const point of points) {
|
||||
await page.mouse.click(...point);
|
||||
}
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 200, 200]);
|
||||
});
|
||||
|
||||
test.skip('polygonal lasso add to selection by holding Shift Key', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 });
|
||||
|
||||
await page.mouse.click(10, 10); // deselect
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await sleep(100);
|
||||
|
||||
let points: [number, number][] = [
|
||||
[90, 90],
|
||||
[150, 90],
|
||||
[150, 150],
|
||||
[90, 150],
|
||||
[90, 90],
|
||||
];
|
||||
|
||||
// select the first rectangle
|
||||
for (const point of points) await page.mouse.click(...point);
|
||||
|
||||
points = [
|
||||
[290, 290],
|
||||
[350, 290],
|
||||
[350, 350],
|
||||
[290, 350],
|
||||
[290, 290],
|
||||
];
|
||||
|
||||
await page.keyboard.down('Shift'); // add to selection
|
||||
// selects the second rectangle
|
||||
for (const point of points) await page.mouse.click(...point);
|
||||
|
||||
// by the end both of the rects should be selected
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 200, 200]);
|
||||
});
|
||||
|
||||
test.skip('polygonal lasso subtract from selection by holding Alt', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 });
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
|
||||
const points: [number, number][] = [
|
||||
[290, 290],
|
||||
[350, 290],
|
||||
[350, 350],
|
||||
[290, 350],
|
||||
[290, 290],
|
||||
];
|
||||
|
||||
// switch to polygonal lasso tool
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await sleep(100);
|
||||
|
||||
await page.keyboard.down('Alt'); // subtract from selection
|
||||
for (const point of points) await page.mouse.click(...point);
|
||||
|
||||
// By the end the second rectangle must be deselected leaving the first rect selection
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
});
|
||||
|
||||
test.skip('polygonal lasso should complete selection when clicking the last point', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
// switch to polygonal lasso
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await sleep(100);
|
||||
|
||||
const lassoPoints: [number, number][] = [
|
||||
[100, 100],
|
||||
[200, 200],
|
||||
[250, 150],
|
||||
[100, 100],
|
||||
];
|
||||
|
||||
for (const point of lassoPoints) await page.mouse.click(...point);
|
||||
|
||||
const isSelecting = await page.evaluate(() => {
|
||||
const edgeless = document.querySelector('affine-edgeless-root');
|
||||
if (!edgeless) throw new Error('Missing edgless root block');
|
||||
|
||||
const curController = edgeless.gfx.tool.currentTool$.peek();
|
||||
if (curController?.toolName !== 'lasso')
|
||||
throw new Error('expected lasso tool controller');
|
||||
|
||||
return (curController as any)['_isSelecting'];
|
||||
});
|
||||
|
||||
expect(isSelecting).toBe(false);
|
||||
});
|
||||
354
blocksuite/tests-legacy/e2e/edgeless/linked-doc.spec.ts
Normal file
354
blocksuite/tests-legacy/e2e/edgeless/linked-doc.spec.ts
Normal file
@@ -0,0 +1,354 @@
|
||||
import { assertNotExists } from '@blocksuite/global/utils';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
activeNoteInEdgeless,
|
||||
createConnectorElement,
|
||||
createNote,
|
||||
createShapeElement,
|
||||
edgelessCommonSetup,
|
||||
getConnectorPath,
|
||||
locatorComponentToolbarMoreButton,
|
||||
selectNoteInEdgeless,
|
||||
Shape,
|
||||
triggerComponentToolbarAction,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
addBasicBrushElement,
|
||||
pressEnter,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import { assertConnectorPath, assertExists } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('note to linked doc', () => {
|
||||
test('select a note and turn it into a linked doc', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const noteId = await createNote(page, [100, 0], '');
|
||||
await activeNoteInEdgeless(page, noteId);
|
||||
await waitNextFrame(page, 200);
|
||||
await type(page, 'Hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'World');
|
||||
|
||||
await page.mouse.click(10, 50);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'turnIntoLinkedDoc');
|
||||
|
||||
await waitNextFrame(page, 200);
|
||||
const embedSyncedBlock = page.locator('affine-embed-synced-doc-block');
|
||||
assertExists(embedSyncedBlock);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'openLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const noteBlock = page.locator('affine-edgeless-note');
|
||||
assertExists(noteBlock);
|
||||
const noteContent = await noteBlock.innerText();
|
||||
expect(noteContent).toBe('Hello\nWorld');
|
||||
});
|
||||
|
||||
test('turn note into a linked doc, connector keeps', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const noteId = await createNote(page, [100, 0]);
|
||||
await createShapeElement(page, [100, 100], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 150], [100, 10]);
|
||||
const connectorPath = await getConnectorPath(page);
|
||||
|
||||
await page.mouse.click(10, 50);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'turnIntoLinkedDoc');
|
||||
|
||||
await waitNextFrame(page, 200);
|
||||
const embedSyncedBlock = page.locator('affine-embed-synced-doc-block');
|
||||
assertExists(embedSyncedBlock);
|
||||
|
||||
await assertConnectorPath(page, [connectorPath[0], connectorPath[1]], 0);
|
||||
});
|
||||
|
||||
// TODO FIX ME
|
||||
test.skip('embed-synced-doc card can not turn into linked doc', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const noteId = await createNote(page, [100, 0]);
|
||||
await activeNoteInEdgeless(page, noteId);
|
||||
await waitNextFrame(page, 200);
|
||||
await type(page, 'Hello World');
|
||||
|
||||
await page.mouse.click(10, 50);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'turnIntoLinkedDoc');
|
||||
|
||||
const moreButton = locatorComponentToolbarMoreButton(page);
|
||||
await moreButton.click();
|
||||
const turnButton = page.locator('.turn-into-linked-doc');
|
||||
assertNotExists(turnButton);
|
||||
});
|
||||
|
||||
// TODO FIX ME
|
||||
test.skip('embed-linked-doc card can not turn into linked doc', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const noteId = await createNote(page, [100, 0]);
|
||||
await activeNoteInEdgeless(page, noteId);
|
||||
await waitNextFrame(page, 200);
|
||||
await type(page, 'Hello World');
|
||||
|
||||
await page.mouse.click(10, 50);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'turnIntoLinkedDoc');
|
||||
|
||||
await triggerComponentToolbarAction(page, 'toCardView');
|
||||
const moreButton = locatorComponentToolbarMoreButton(page);
|
||||
await moreButton.click();
|
||||
const turnButton = page.locator('.turn-into-linked-doc');
|
||||
assertNotExists(turnButton);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('single edgeless element to linked doc', () => {
|
||||
test('select a shape, turn into a linked doc', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [100, 100], [100, 100], Shape.Square);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'createLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block');
|
||||
assertExists(linkedSyncedBlock);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'openLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
|
||||
const shapes = await page.evaluate(() => {
|
||||
const container = document.querySelector('affine-edgeless-root');
|
||||
return container!.service.crud
|
||||
.getElementsByType('shape')
|
||||
.map(s => ({ type: s.type, xywh: s.xywh }));
|
||||
});
|
||||
expect(shapes.length).toBe(1);
|
||||
expect(shapes[0]).toEqual({ type: 'shape', xywh: '[100,100,100,100]' });
|
||||
});
|
||||
|
||||
test('select a connector, turn into a linked doc', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createConnectorElement(page, [100, 150], [100, 10]);
|
||||
const connectorPath = await getConnectorPath(page);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'createLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block');
|
||||
assertExists(linkedSyncedBlock);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'openLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertConnectorPath(page, [connectorPath[0], connectorPath[1]], 0);
|
||||
});
|
||||
|
||||
test('select a brush, turn into a linked doc', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const start = { x: 400, y: 400 };
|
||||
const end = { x: 500, y: 500 };
|
||||
await addBasicBrushElement(page, start, end);
|
||||
await page.mouse.click(start.x + 5, start.y + 5);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'createLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block');
|
||||
assertExists(linkedSyncedBlock);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'openLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const brushes = await page.evaluate(() => {
|
||||
const container = document.querySelector('affine-edgeless-root');
|
||||
return container!.service.crud
|
||||
.getElementsByType('brush')
|
||||
.map(s => ({ type: s.type, xywh: s.xywh }));
|
||||
});
|
||||
expect(brushes.length).toBe(1);
|
||||
});
|
||||
|
||||
test('select a group, turn into a linked doc', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createNote(page, [100, 0]);
|
||||
await createShapeElement(page, [100, 100], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 150], [100, 10]);
|
||||
const start = { x: 400, y: 400 };
|
||||
const end = { x: 500, y: 500 };
|
||||
await addBasicBrushElement(page, start, end);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
await triggerComponentToolbarAction(page, 'createLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block');
|
||||
assertExists(linkedSyncedBlock);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'openLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const groups = await page.evaluate(() => {
|
||||
const container = document.querySelector('affine-edgeless-root');
|
||||
return container!.service.crud.getElementsByType('group').map(s => ({
|
||||
type: s.type,
|
||||
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
children: s.childElements.map((c: any) => c.type || c.flavour),
|
||||
}));
|
||||
});
|
||||
expect(groups.length).toBe(1);
|
||||
expect(groups[0].children).toContain('affine:note');
|
||||
expect(groups[0].children).toContain('shape');
|
||||
expect(groups[0].children).toContain('connector');
|
||||
expect(groups[0].children).toContain('brush');
|
||||
});
|
||||
|
||||
test('select a frame, turn into a linked doc', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createNote(page, [100, 0]);
|
||||
await createShapeElement(page, [100, 100], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 150], [100, 10]);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
const start = { x: 400, y: 400 };
|
||||
const end = { x: 500, y: 500 };
|
||||
await addBasicBrushElement(page, start, end);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addFrame');
|
||||
|
||||
await triggerComponentToolbarAction(page, 'createLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block');
|
||||
assertExists(linkedSyncedBlock);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'openLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const nodes = await page.evaluate(() => {
|
||||
const container = document.querySelector('affine-edgeless-root');
|
||||
const elements = container!.service.elements.map(s => s.type);
|
||||
const blocks = container!.service.blocks.map(b => b.flavour);
|
||||
|
||||
blocks.sort();
|
||||
elements.sort();
|
||||
|
||||
return { blocks, elements };
|
||||
});
|
||||
|
||||
expect(nodes).toEqual({
|
||||
blocks: ['affine:note', 'affine:frame'].sort(),
|
||||
elements: ['group', 'shape', 'connector', 'brush'].sort(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('multiple edgeless elements to linked doc', () => {
|
||||
test('multi-select note, frame, shape, connector, brush and group, turn it into a linked doc', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createNote(page, [100, 0], 'Hello World');
|
||||
await page.mouse.click(10, 50);
|
||||
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
await createShapeElement(page, [200, 200], [300, 300], Shape.Square);
|
||||
await createConnectorElement(page, [250, 300], [100, 70]);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addFrame');
|
||||
|
||||
const start = { x: 400, y: 400 };
|
||||
const end = { x: 500, y: 500 };
|
||||
await addBasicBrushElement(page, start, end);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'createLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block');
|
||||
assertExists(linkedSyncedBlock);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'openLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const nodes = await page.evaluate(() => {
|
||||
const container = document.querySelector('affine-edgeless-root');
|
||||
const elements = container!.service.elements.map(s => s.type);
|
||||
const blocks = container!.service.blocks.map(b => b.flavour);
|
||||
|
||||
blocks.sort();
|
||||
elements.sort();
|
||||
|
||||
return { blocks, elements };
|
||||
});
|
||||
expect(nodes).toEqual({
|
||||
blocks: ['affine:frame', 'affine:note'].sort(),
|
||||
elements: ['shape', 'shape', 'group', 'connector', 'brush'].sort(),
|
||||
});
|
||||
});
|
||||
|
||||
test('multi-select with embed doc card inside, turn it into a linked doc', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const noteId = await createNote(page, [100, 0], 'Hello World');
|
||||
await page.mouse.click(10, 50);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'turnIntoLinkedDoc');
|
||||
|
||||
await createShapeElement(page, [100, 100], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 150], [100, 10]);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'createLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block');
|
||||
assertExists(linkedSyncedBlock);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'openLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const nodes = await page.evaluate(() => {
|
||||
const container = document.querySelector('affine-edgeless-root');
|
||||
const elements = container!.service.elements.map(s => s.type);
|
||||
const blocks = container!.service.blocks.map(b => b.flavour);
|
||||
return { blocks, elements };
|
||||
});
|
||||
|
||||
expect(nodes.blocks).toHaveLength(1);
|
||||
expect(nodes.blocks).toContain('affine:embed-synced-doc');
|
||||
|
||||
expect(nodes.elements).toHaveLength(2);
|
||||
expect(nodes.elements).toContain('shape');
|
||||
expect(nodes.elements).toContain('connector');
|
||||
});
|
||||
|
||||
test('multi-select with mindmap, turn it into a linked doc', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await triggerComponentToolbarAction(page, 'addMindmap');
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'createLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block');
|
||||
assertExists(linkedSyncedBlock);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'openLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const nodes = await page.evaluate(() => {
|
||||
const container = document.querySelector('affine-edgeless-root');
|
||||
const elements = container!.service.elements.map(s => s.type);
|
||||
const blocks = container!.service.blocks.map(b => b.flavour);
|
||||
return { blocks, elements };
|
||||
});
|
||||
|
||||
expect(nodes.blocks).toHaveLength(0);
|
||||
|
||||
expect(nodes.elements).toHaveLength(5);
|
||||
expect(nodes.elements).toContain('mindmap');
|
||||
expect(nodes.elements.filter(el => el === 'shape')).toHaveLength(4);
|
||||
});
|
||||
});
|
||||
558
blocksuite/tests-legacy/e2e/edgeless/lock.spec.ts
Normal file
558
blocksuite/tests-legacy/e2e/edgeless/lock.spec.ts
Normal file
@@ -0,0 +1,558 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import { clickView, dblclickView, moveView } from '../utils/actions/click.js';
|
||||
import {
|
||||
createBrushElement,
|
||||
createConnectorElement,
|
||||
createEdgelessText,
|
||||
createFrame,
|
||||
createMindmap,
|
||||
createNote as _createNote,
|
||||
createShapeElement,
|
||||
deleteAll,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
getContainerChildIds,
|
||||
getSelectedBound,
|
||||
getSelectedIds,
|
||||
getTypeById,
|
||||
setEdgelessTool,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
copyByKeyboard,
|
||||
pasteByKeyboard,
|
||||
pressArrowDown,
|
||||
pressBackspace,
|
||||
pressEscape,
|
||||
pressForwardDelete,
|
||||
pressTab,
|
||||
selectAllByKeyboard,
|
||||
SHORT_KEY,
|
||||
type,
|
||||
undoByKeyboard,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import { waitNextFrame } from '../utils/actions/misc.js';
|
||||
import {
|
||||
assertCanvasElementsCount,
|
||||
assertEdgelessElementBound,
|
||||
assertEdgelessSelectedModelRect,
|
||||
assertRichTexts,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('lock', () => {
|
||||
const getButtons = (page: Page) => {
|
||||
const elementToolbar = page.locator('edgeless-element-toolbar-widget');
|
||||
return {
|
||||
lock: elementToolbar.locator('edgeless-lock-button[data-locked="false"]'),
|
||||
unlock: elementToolbar.locator(
|
||||
'edgeless-lock-button[data-locked="true"]'
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
async function createNote(page: Page, coord1: number[], content?: string) {
|
||||
await _createNote(page, coord1, content);
|
||||
await pressEscape(page, 3);
|
||||
}
|
||||
|
||||
test('edgeless element can be locked and unlocked', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
|
||||
const wrapTest = async <F extends (...args: any) => any>(
|
||||
elementCreateFn: F,
|
||||
...args: Parameters<F>
|
||||
) => {
|
||||
await elementCreateFn(...args);
|
||||
await waitNextFrame(page);
|
||||
await pressEscape(page);
|
||||
await selectAllByKeyboard(page);
|
||||
|
||||
const ids = await getSelectedIds(page);
|
||||
expect(ids).toHaveLength(1);
|
||||
const type = await getTypeById(page, ids[0]);
|
||||
const message = `element(${type}) should be able to be (un)locked`;
|
||||
|
||||
const { lock, unlock } = getButtons(page);
|
||||
|
||||
await expect(lock, message).toBeVisible();
|
||||
await expect(unlock, message).toBeHidden();
|
||||
|
||||
await lock.click();
|
||||
await expect(lock, message).toBeHidden();
|
||||
await expect(unlock, message).toBeVisible();
|
||||
|
||||
await unlock.click();
|
||||
await expect(lock, message).toBeVisible();
|
||||
await expect(unlock, message).toBeHidden();
|
||||
await deleteAll(page);
|
||||
await waitNextFrame(page);
|
||||
};
|
||||
|
||||
await wrapTest(createBrushElement, page, [100, 100], [150, 150]);
|
||||
await wrapTest(createConnectorElement, page, [100, 100], [150, 150]);
|
||||
await wrapTest(createShapeElement, page, [100, 100], [150, 150]);
|
||||
await wrapTest(createEdgelessText, page, [100, 100]);
|
||||
await wrapTest(createMindmap, page, [100, 100]);
|
||||
await wrapTest(createFrame, page, [100, 100], [150, 150]);
|
||||
await wrapTest(createNote, page, [100, 100]);
|
||||
|
||||
await wrapTest(async () => {
|
||||
await createShapeElement(page, [100, 100], [150, 150]);
|
||||
await createShapeElement(page, [150, 150], [200, 200]);
|
||||
await selectAllByKeyboard(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+g`);
|
||||
});
|
||||
});
|
||||
|
||||
test('locked element should be selectable by clicking or short-cut', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [100, 100], [150, 150]);
|
||||
await selectAllByKeyboard(page);
|
||||
|
||||
await getButtons(page).lock.click();
|
||||
expect(await getSelectedIds(page)).toHaveLength(1);
|
||||
await pressEscape(page);
|
||||
expect(await getSelectedIds(page)).toHaveLength(0);
|
||||
await selectAllByKeyboard(page);
|
||||
expect(await getSelectedIds(page)).toHaveLength(1);
|
||||
|
||||
await pressEscape(page);
|
||||
await clickView(page, [125, 125]);
|
||||
expect(await getSelectedIds(page)).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('locked element should not be selectable by dragging default tool or lasso tool. unlocking will recover', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [100, 100], [150, 150]);
|
||||
await selectAllByKeyboard(page);
|
||||
|
||||
const { lock, unlock } = getButtons(page);
|
||||
|
||||
await lock.click();
|
||||
await pressEscape(page);
|
||||
await dragBetweenViewCoords(page, [90, 90], [160, 160]);
|
||||
expect(await getSelectedIds(page)).toHaveLength(0);
|
||||
|
||||
await clickView(page, [125, 125]);
|
||||
await unlock.click();
|
||||
await dragBetweenViewCoords(page, [90, 90], [160, 160]);
|
||||
expect(await getSelectedIds(page)).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('descendant of locked element should not be selectable. unlocking will recover', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const shapeId = await createShapeElement(page, [100, 100], [150, 150]);
|
||||
await createShapeElement(page, [150, 150], [200, 200]);
|
||||
await selectAllByKeyboard(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+g`);
|
||||
const groupId = (await getSelectedIds(page))[0];
|
||||
|
||||
const { lock, unlock } = getButtons(page);
|
||||
|
||||
await lock.click();
|
||||
await pressEscape(page);
|
||||
await clickView(page, [125, 125]);
|
||||
expect(await getSelectedIds(page)).toEqual([groupId]);
|
||||
await clickView(page, [125, 125]);
|
||||
expect(await getSelectedIds(page)).toEqual([groupId]);
|
||||
|
||||
await unlock.click();
|
||||
await clickView(page, [125, 125]);
|
||||
expect(await getSelectedIds(page)).toEqual([shapeId]);
|
||||
await pressEscape(page);
|
||||
|
||||
const frameId = await createFrame(page, [50, 50], [250, 250]);
|
||||
await selectAllByKeyboard(page);
|
||||
await lock.click();
|
||||
await pressEscape(page);
|
||||
await clickView(page, [125, 125]);
|
||||
expect(await getSelectedIds(page)).toEqual([frameId]);
|
||||
await unlock.click();
|
||||
await clickView(page, [125, 125]);
|
||||
expect(await getSelectedIds(page)).toEqual([shapeId]);
|
||||
});
|
||||
|
||||
test('the selected rect of locked element should contain descendant. unlocking will recover', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
// frame
|
||||
await createFrame(page, [0, 0], [100, 100]);
|
||||
await createShapeElement(page, [100, 100], [150, 150]);
|
||||
await dragBetweenViewCoords(page, [125, 125], [95, 95]); // add shape to frame, and partial area out of frame
|
||||
await selectAllByKeyboard(page);
|
||||
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 100, 100]); // only frame outline
|
||||
|
||||
const { lock, unlock } = getButtons(page);
|
||||
|
||||
await lock.click();
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 120, 120]); // frame outline and shape
|
||||
await pressEscape(page);
|
||||
await clickView(page, [100, 100]);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 120, 120]);
|
||||
|
||||
await unlock.click();
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 100, 100]);
|
||||
await pressEscape(page);
|
||||
await clickView(page, [100, 100]);
|
||||
await assertEdgelessSelectedModelRect(page, [70, 70, 50, 50]);
|
||||
|
||||
await deleteAll(page);
|
||||
|
||||
// mindmap
|
||||
await createMindmap(page, [100, 100]);
|
||||
const bound = await getSelectedBound(page);
|
||||
const rootNodePos: [number, number] = [
|
||||
bound[0] + 10,
|
||||
bound[1] + 0.5 * bound[3],
|
||||
];
|
||||
|
||||
await clickView(page, rootNodePos);
|
||||
const rootNodeBound = await getSelectedBound(page);
|
||||
|
||||
await lock.click();
|
||||
await assertEdgelessSelectedModelRect(page, bound);
|
||||
await clickView(page, rootNodePos);
|
||||
await assertEdgelessSelectedModelRect(page, bound);
|
||||
|
||||
await unlock.click();
|
||||
await assertEdgelessSelectedModelRect(page, bound);
|
||||
await clickView(page, rootNodePos);
|
||||
await assertEdgelessSelectedModelRect(page, rootNodeBound);
|
||||
});
|
||||
|
||||
test('locked element should be copyable, and the copy is unlocked', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [100, 100], [150, 150]);
|
||||
await selectAllByKeyboard(page);
|
||||
|
||||
await getButtons(page).lock.click();
|
||||
await pressEscape(page);
|
||||
await clickView(page, [125, 125]);
|
||||
await copyByKeyboard(page);
|
||||
await moveView(page, [200, 200]);
|
||||
await pasteByKeyboard(page);
|
||||
await clickView(page, [200, 200]);
|
||||
await expect(getButtons(page).lock).toBeVisible();
|
||||
});
|
||||
|
||||
test('locked element and descendant should not be draggable and moved by arrow key. unlocking will recover', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const frame = await createFrame(page, [50, 50], [250, 250]);
|
||||
const shape1 = await createShapeElement(page, [100, 100], [150, 150]);
|
||||
const shape2 = await createShapeElement(page, [150, 150], [200, 200]);
|
||||
await selectAllByKeyboard(page);
|
||||
|
||||
await getButtons(page).lock.click();
|
||||
await pressEscape(page);
|
||||
|
||||
await dragBetweenViewCoords(page, [100, 100], [150, 150]);
|
||||
await assertEdgelessElementBound(page, frame, [50, 50, 200, 200]);
|
||||
await assertEdgelessElementBound(page, shape1, [100, 100, 50, 50]);
|
||||
await assertEdgelessElementBound(page, shape2, [150, 150, 50, 50]);
|
||||
|
||||
await pressArrowDown(page, 3);
|
||||
await assertEdgelessElementBound(page, frame, [50, 50, 200, 200]);
|
||||
await assertEdgelessElementBound(page, shape1, [100, 100, 50, 50]);
|
||||
await assertEdgelessElementBound(page, shape2, [150, 150, 50, 50]);
|
||||
|
||||
await getButtons(page).unlock.click();
|
||||
await dragBetweenViewCoords(page, [100, 100], [150, 150]);
|
||||
await assertEdgelessElementBound(page, frame, [100, 100, 200, 200]);
|
||||
await assertEdgelessElementBound(page, shape1, [150, 150, 50, 50]);
|
||||
await assertEdgelessElementBound(page, shape2, [200, 200, 50, 50]);
|
||||
|
||||
await pressArrowDown(page, 3);
|
||||
await assertEdgelessElementBound(page, frame, [100, 103, 200, 200]);
|
||||
await assertEdgelessElementBound(page, shape1, [150, 153, 50, 50]);
|
||||
await assertEdgelessElementBound(page, shape2, [200, 203, 50, 50]);
|
||||
});
|
||||
|
||||
test('locked element should be moved if parent is moved', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const frame = await createFrame(page, [50, 50], [250, 250]);
|
||||
const shape = await createShapeElement(page, [100, 100], [150, 150]);
|
||||
await clickView(page, [125, 125]);
|
||||
await getButtons(page).lock.click();
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await dragBetweenViewCoords(page, [100, 100], [150, 150]);
|
||||
|
||||
assertEdgelessElementBound(page, frame, [100, 100, 200, 200]);
|
||||
assertEdgelessElementBound(page, shape, [150, 150, 50, 50]);
|
||||
});
|
||||
|
||||
test('locked element should not be scalable and rotatable. unlocking will recover', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [100, 100], [150, 150]);
|
||||
await selectAllByKeyboard(page);
|
||||
|
||||
const rect = page.locator('edgeless-selected-rect');
|
||||
const { lock, unlock } = getButtons(page);
|
||||
await expect(rect.locator('.resize')).toHaveCount(8);
|
||||
await expect(rect.locator('.rotate')).toHaveCount(4);
|
||||
|
||||
await lock.click();
|
||||
await expect(rect.locator('.resize')).toHaveCount(0);
|
||||
await expect(rect.locator('.rotate')).toHaveCount(0);
|
||||
|
||||
await unlock.click();
|
||||
await expect(rect.locator('.resize')).toHaveCount(8);
|
||||
await expect(rect.locator('.rotate')).toHaveCount(4);
|
||||
});
|
||||
|
||||
test('locked element should not be editable. unlocking will recover', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const { lock, unlock } = getButtons(page);
|
||||
|
||||
// Shape
|
||||
{
|
||||
await createShapeElement(page, [100, 100], [150, 150]);
|
||||
await selectAllByKeyboard(page);
|
||||
await lock.click();
|
||||
await dblclickView(page, [125, 125]);
|
||||
await expect(page.locator('edgeless-shape-text-editor')).toHaveCount(0);
|
||||
await unlock.click();
|
||||
await dblclickView(page, [125, 125]);
|
||||
await expect(page.locator('edgeless-shape-text-editor')).toHaveCount(1);
|
||||
await deleteAll(page);
|
||||
}
|
||||
|
||||
// Connector
|
||||
{
|
||||
await createConnectorElement(page, [100, 100], [150, 150]);
|
||||
await selectAllByKeyboard(page);
|
||||
await lock.click();
|
||||
await dblclickView(page, [125, 125]);
|
||||
await expect(page.locator('edgeless-connector-label-editor')).toHaveCount(
|
||||
0
|
||||
);
|
||||
await unlock.click();
|
||||
await dblclickView(page, [125, 125]);
|
||||
await expect(page.locator('edgeless-connector-label-editor')).toHaveCount(
|
||||
1
|
||||
);
|
||||
await deleteAll(page);
|
||||
}
|
||||
|
||||
// Mindmap
|
||||
{
|
||||
await createMindmap(page, [100, 100]);
|
||||
const bound = await getSelectedBound(page);
|
||||
const rootPos: [number, number] = [
|
||||
bound[0] + 10,
|
||||
bound[1] + 0.5 * bound[3],
|
||||
];
|
||||
await lock.click();
|
||||
await dblclickView(page, rootPos);
|
||||
await expect(page.locator('edgeless-shape-text-editor')).toHaveCount(0);
|
||||
await unlock.click();
|
||||
await dblclickView(page, rootPos);
|
||||
await expect(page.locator('edgeless-shape-text-editor')).toHaveCount(1);
|
||||
await deleteAll(page);
|
||||
}
|
||||
|
||||
// Edgeless Text
|
||||
{
|
||||
await createEdgelessText(page, [100, 100], 'text');
|
||||
await selectAllByKeyboard(page);
|
||||
await lock.click();
|
||||
const text = page.locator('affine-edgeless-text');
|
||||
await text.dblclick();
|
||||
await type(page, '111');
|
||||
await expect(text).toHaveText('text');
|
||||
await unlock.click();
|
||||
await text.dblclick();
|
||||
await type(page, '111');
|
||||
await expect(text).toHaveText('111');
|
||||
await deleteAll(page);
|
||||
}
|
||||
|
||||
// Note
|
||||
{
|
||||
await createNote(page, [100, 100], 'note');
|
||||
await selectAllByKeyboard(page);
|
||||
await lock.click();
|
||||
const note = page.locator('affine-edgeless-note');
|
||||
await note.dblclick();
|
||||
await page.keyboard.press('End');
|
||||
await type(page, '111');
|
||||
await assertRichTexts(page, ['note']);
|
||||
await unlock.click();
|
||||
await note.dblclick();
|
||||
await page.keyboard.press('End');
|
||||
await type(page, '111');
|
||||
await assertRichTexts(page, ['note111']);
|
||||
await pressEscape(page, 3);
|
||||
await deleteAll(page);
|
||||
}
|
||||
});
|
||||
|
||||
test('locked element should not be deletable. unlocking will recover', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [100, 100], [150, 150]);
|
||||
await selectAllByKeyboard(page);
|
||||
const { lock, unlock } = getButtons(page);
|
||||
|
||||
await lock.click();
|
||||
await clickView(page, [125, 125]);
|
||||
await pressBackspace(page);
|
||||
await assertCanvasElementsCount(page, 1);
|
||||
await page.keyboard.press('Delete');
|
||||
await assertCanvasElementsCount(page, 1);
|
||||
await pressForwardDelete(page);
|
||||
await assertCanvasElementsCount(page, 1);
|
||||
await setEdgelessTool(page, 'eraser');
|
||||
await dragBetweenViewCoords(page, [90, 90], [160, 160], { steps: 2 });
|
||||
await assertCanvasElementsCount(page, 1);
|
||||
await setEdgelessTool(page, 'default');
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await unlock.click();
|
||||
await page.evaluate(() => {
|
||||
window.doc.captureSync();
|
||||
});
|
||||
await pressBackspace(page);
|
||||
await assertCanvasElementsCount(page, 0);
|
||||
await undoByKeyboard(page);
|
||||
await assertCanvasElementsCount(page, 1);
|
||||
await page.keyboard.press('Delete');
|
||||
await assertCanvasElementsCount(page, 0);
|
||||
await undoByKeyboard(page);
|
||||
await assertCanvasElementsCount(page, 1);
|
||||
await pressForwardDelete(page);
|
||||
await assertCanvasElementsCount(page, 0);
|
||||
await undoByKeyboard(page);
|
||||
await assertCanvasElementsCount(page, 1);
|
||||
await setEdgelessTool(page, 'eraser');
|
||||
await dragBetweenViewCoords(page, [90, 90], [160, 160], { steps: 2 });
|
||||
await assertCanvasElementsCount(page, 0);
|
||||
});
|
||||
|
||||
test('locked frame should not add new child element. unlocking will recover', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const frame = await createFrame(page, [50, 50], [250, 250]);
|
||||
await selectAllByKeyboard(page);
|
||||
const frameTitle = page.locator('affine-frame-title');
|
||||
const { lock, unlock } = getButtons(page);
|
||||
|
||||
await lock.click();
|
||||
const shape = await createShapeElement(page, [100, 100], [150, 150]);
|
||||
|
||||
expect(await getContainerChildIds(page, frame)).toHaveLength(0);
|
||||
|
||||
await frameTitle.click();
|
||||
await unlock.click();
|
||||
expect(await getContainerChildIds(page, frame)).toHaveLength(0);
|
||||
await clickView(page, [125, 125]);
|
||||
await dragBetweenViewCoords(page, [125, 125], [130, 130]); // move shape into frame
|
||||
expect(await getContainerChildIds(page, frame)).toEqual([shape]);
|
||||
});
|
||||
|
||||
test('locked mindmap can not create new node by pressing Tab. unlocking will recover', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createMindmap(page, [100, 100]);
|
||||
await selectAllByKeyboard(page);
|
||||
const bound = await getSelectedBound(page);
|
||||
const rootPos: [number, number] = [
|
||||
bound[0] + 10,
|
||||
bound[1] + 0.5 * bound[3],
|
||||
];
|
||||
const nodeEditor = page.locator('edgeless-shape-text-editor');
|
||||
const { lock, unlock } = getButtons(page);
|
||||
await lock.click();
|
||||
await clickView(page, rootPos);
|
||||
await pressTab(page);
|
||||
await expect(nodeEditor).toHaveCount(0);
|
||||
await unlock.click();
|
||||
await clickView(page, rootPos);
|
||||
await pressTab(page);
|
||||
await expect(nodeEditor).toHaveCount(1);
|
||||
await expect(nodeEditor).toHaveText('New node');
|
||||
});
|
||||
|
||||
test('endpoint of locked connector should not be changeable. unlocking will recover', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createConnectorElement(page, [100, 100], [150, 150]);
|
||||
const handles = page.locator('edgeless-connector-handle');
|
||||
await expect(handles).toHaveCount(1);
|
||||
const { lock, unlock } = getButtons(page);
|
||||
await lock.click();
|
||||
await expect(handles).toHaveCount(0);
|
||||
await unlock.click();
|
||||
await expect(handles).toHaveCount(1);
|
||||
});
|
||||
|
||||
test('locking multiple elements will create locked group. unlocking a group will release elements', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const shape1 = await createShapeElement(page, [100, 100], [150, 150]);
|
||||
const shape2 = await createShapeElement(page, [150, 150], [200, 200]);
|
||||
await selectAllByKeyboard(page);
|
||||
const { lock, unlock } = getButtons(page);
|
||||
await lock.click();
|
||||
const group = (await getSelectedIds(page))[0];
|
||||
expect(group).not.toBeUndefined();
|
||||
expect(await getTypeById(page, group)).toBe('group');
|
||||
|
||||
await unlock.click();
|
||||
expect(await getSelectedIds(page)).toEqual([shape1, shape2]);
|
||||
});
|
||||
|
||||
test('locking a group should not create a new group', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [100, 100], [150, 150]);
|
||||
await createShapeElement(page, [150, 150], [200, 200]);
|
||||
await selectAllByKeyboard(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+g`);
|
||||
const group = (await getSelectedIds(page))[0];
|
||||
await getButtons(page).lock.click();
|
||||
expect(await getSelectedIds(page)).toEqual([group]);
|
||||
});
|
||||
|
||||
test('unlocking an element should not unlock its locked descendant', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createFrame(page, [50, 50], [250, 250]);
|
||||
await createShapeElement(page, [150, 150], [200, 200]);
|
||||
|
||||
const { lock, unlock } = getButtons(page);
|
||||
|
||||
await clickView(page, [175, 175]);
|
||||
await lock.click();
|
||||
await page.locator('affine-frame-title').click();
|
||||
await lock.click();
|
||||
await unlock.click();
|
||||
await clickView(page, [175, 175]);
|
||||
await expect(lock).toBeHidden();
|
||||
await expect(unlock).toBeVisible();
|
||||
});
|
||||
});
|
||||
371
blocksuite/tests-legacy/e2e/edgeless/mindmap.spec.ts
Normal file
371
blocksuite/tests-legacy/e2e/edgeless/mindmap.spec.ts
Normal file
@@ -0,0 +1,371 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { clickView } from '../utils/actions/click.js';
|
||||
import { dragBetweenCoords } from '../utils/actions/drag.js';
|
||||
import {
|
||||
addBasicRectShapeElement,
|
||||
autoFit,
|
||||
edgelessCommonSetup,
|
||||
getSelectedBound,
|
||||
getSelectedBoundCount,
|
||||
selectElementInEdgeless,
|
||||
waitFontsLoaded,
|
||||
zoomResetByKeyboard,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressTab,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
undoByKeyboard,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import { waitNextFrame } from '../utils/actions/misc.js';
|
||||
import {
|
||||
assertEdgelessSelectedRect,
|
||||
assertSelectedBound,
|
||||
} from '../utils/asserts.js';
|
||||
import {
|
||||
addMindmapNodes,
|
||||
createMindMap,
|
||||
getMindMapNode,
|
||||
} from '../utils/mindmap.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('elements should be selectable after open mindmap menu', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await addBasicRectShapeElement(page, start, end);
|
||||
|
||||
await page.locator('.basket-wrapper').click({ position: { x: 0, y: 0 } });
|
||||
await expect(page.locator('edgeless-mindmap-menu')).toBeVisible();
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y + 5);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
});
|
||||
|
||||
test('undo deletion of mindmap should restore the deleted element', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await page.keyboard.press('m');
|
||||
await clickView(page, [0, 0]);
|
||||
await autoFit(page);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
|
||||
await pressBackspace(page);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await assertSelectedBound(page, mindmapBound);
|
||||
});
|
||||
|
||||
test('drag mind map node to reorder the node', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
const mindmapId = await createMindMap(page, [0, 0]);
|
||||
const { id: nodeId, rect: nodeRect } = await getMindMapNode(
|
||||
page,
|
||||
mindmapId,
|
||||
[0, 0]
|
||||
);
|
||||
const { rect: targetRect } = await getMindMapNode(page, mindmapId, [0, 1]);
|
||||
const { rect: lastRect } = await getMindMapNode(page, mindmapId, [0, 2]);
|
||||
|
||||
await selectElementInEdgeless(page, [nodeId]);
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: nodeRect.x + nodeRect.w / 2, y: nodeRect.y + nodeRect.h / 2 },
|
||||
{ x: targetRect.x + targetRect.w / 2, y: targetRect.y + targetRect.h + 40 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
expect((await getMindMapNode(page, mindmapId, [0, 1])).id).toEqual(nodeId);
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: targetRect.x + targetRect.w / 2, y: targetRect.y + targetRect.h / 2 },
|
||||
{ x: nodeRect.x - 20, y: nodeRect.y - 40 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
expect((await getMindMapNode(page, mindmapId, [0, 0])).id).toEqual(nodeId);
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: nodeRect.x + nodeRect.w / 2, y: nodeRect.y + nodeRect.h / 2 },
|
||||
{ x: lastRect.x - 20, y: lastRect.y + lastRect.h + 40 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
expect((await getMindMapNode(page, mindmapId, [0, 2])).id).toEqual(nodeId);
|
||||
});
|
||||
|
||||
test('drag mind map node to make it a child node', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
const mindmapId = await createMindMap(page, [0, 0]);
|
||||
|
||||
{
|
||||
const { id: nodeId, rect: nodeRect } = await getMindMapNode(
|
||||
page,
|
||||
mindmapId,
|
||||
[0, 0]
|
||||
);
|
||||
const { rect: targetRect } = await getMindMapNode(page, mindmapId, [0, 1]);
|
||||
|
||||
await selectElementInEdgeless(page, [nodeId]);
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: nodeRect.x + nodeRect.w / 2, y: nodeRect.y + nodeRect.h / 2 },
|
||||
{
|
||||
x: targetRect.x + targetRect.w / 2,
|
||||
y: targetRect.y + targetRect.h / 2,
|
||||
},
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
expect((await getMindMapNode(page, mindmapId, [0, 0, 0])).id).toEqual(
|
||||
nodeId
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const { id: childId } = await getMindMapNode(page, mindmapId, [0, 0, 0]);
|
||||
const { rect: firstRect } = await getMindMapNode(page, mindmapId, [0, 0]);
|
||||
const { rect: secondRect } = await getMindMapNode(page, mindmapId, [0, 1]);
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: firstRect.x + firstRect.w / 2, y: firstRect.y + firstRect.h / 2 },
|
||||
{
|
||||
x: secondRect.x + secondRect.w + 10,
|
||||
y: secondRect.y + secondRect.h / 2,
|
||||
},
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
expect((await getMindMapNode(page, mindmapId, [0, 0, 0, 0])).id).toEqual(
|
||||
childId
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('cannot drag mind map node to itself or its descendants', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
const mindmapId = await createMindMap(page, [0, 1]);
|
||||
await addMindmapNodes(page, mindmapId, [0, 1], {
|
||||
text: 'child node 1',
|
||||
children: [
|
||||
{
|
||||
text: 'child node 2',
|
||||
},
|
||||
{
|
||||
text: 'child node 3',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { id: node, rect } = await getMindMapNode(page, mindmapId, [0, 1]);
|
||||
const { id: childNode3, rect: childRect3 } = await getMindMapNode(
|
||||
page,
|
||||
mindmapId,
|
||||
[0, 1, 0, 1]
|
||||
);
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: rect.x + rect.w / 2, y: rect.y + rect.h / 2 },
|
||||
{ x: childRect3.x + childRect3.w + 10, y: childRect3.y + childRect3.h / 2 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
expect((await getMindMapNode(page, mindmapId, [0, 1])).id).toEqual(node);
|
||||
expect((await getMindMapNode(page, mindmapId, [0, 1, 0, 1])).id).toEqual(
|
||||
childNode3
|
||||
);
|
||||
});
|
||||
|
||||
test('drag root node should layout in real time', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
// wait for the font to be loaded
|
||||
await waitFontsLoaded(page);
|
||||
|
||||
const mindmapId = await createMindMap(page, [0, 0]);
|
||||
const { rect: rootRect } = await getMindMapNode(page, mindmapId, [0]);
|
||||
const { rect: firstRect } = await getMindMapNode(page, mindmapId, [0, 0]);
|
||||
const { rect: secondRect } = await getMindMapNode(page, mindmapId, [0, 1]);
|
||||
const { rect: thirdRect } = await getMindMapNode(page, mindmapId, [0, 2]);
|
||||
|
||||
const assertMindMapNodesPosition = async (deltaX: number, deltaY: number) => {
|
||||
expect((await getMindMapNode(page, mindmapId, [0, 0])).rect).toEqual({
|
||||
...firstRect,
|
||||
x: firstRect.x + deltaX,
|
||||
y: firstRect.y + deltaY,
|
||||
});
|
||||
expect((await getMindMapNode(page, mindmapId, [0, 1])).rect).toEqual({
|
||||
...secondRect,
|
||||
x: secondRect.x + deltaX,
|
||||
y: secondRect.y + deltaY,
|
||||
});
|
||||
expect((await getMindMapNode(page, mindmapId, [0, 2])).rect).toEqual({
|
||||
...thirdRect,
|
||||
x: thirdRect.x + deltaX,
|
||||
y: thirdRect.y + deltaY,
|
||||
});
|
||||
};
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: rootRect.x + rootRect.w / 2, y: rootRect.y + rootRect.h / 2 },
|
||||
{
|
||||
x: rootRect.x + rootRect.w / 2 + 10,
|
||||
y: rootRect.y + rootRect.h / 2 + 10,
|
||||
},
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
await assertMindMapNodesPosition(10, 10);
|
||||
|
||||
await page.mouse.move(
|
||||
rootRect.x + rootRect.w / 2 + 10,
|
||||
rootRect.y + rootRect.h / 2 + 10
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(
|
||||
rootRect.x + rootRect.w / 2 + 10 + 4,
|
||||
rootRect.y + rootRect.h / 2 + 10 + 4
|
||||
);
|
||||
await page.mouse.move(
|
||||
rootRect.x + rootRect.w / 2 + 10 + 44,
|
||||
rootRect.y + rootRect.h / 2 + 10 + 44,
|
||||
{ steps: 10 }
|
||||
);
|
||||
|
||||
// assert when dragging is in progress
|
||||
await waitNextFrame(page, 500);
|
||||
await assertMindMapNodesPosition(50, 50);
|
||||
|
||||
await page.mouse.up();
|
||||
});
|
||||
|
||||
test('drag node out of mind map should detach the node and create a new mind map', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
const mindmapId = await createMindMap(page, [0, 1]);
|
||||
await addMindmapNodes(page, mindmapId, [0, 1], {
|
||||
text: 'child node 1',
|
||||
children: [
|
||||
{
|
||||
text: 'child node 2',
|
||||
},
|
||||
{
|
||||
text: 'child node 3',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { rect } = await getMindMapNode(page, mindmapId, [0, 1]);
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: rect.x + rect.w / 2,
|
||||
y: rect.y + rect.h / 2,
|
||||
},
|
||||
{
|
||||
x: rect.x + rect.w / 2,
|
||||
y: rect.y + rect.h / 2 + 300,
|
||||
},
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const { count, mindmap: lastMindmapId } = await page.evaluate(() => {
|
||||
const edgelessBlock = document.querySelector('affine-edgeless-root');
|
||||
if (!edgelessBlock) {
|
||||
throw new Error('edgeless block not found');
|
||||
}
|
||||
const mindmaps = edgelessBlock.gfx.gfxElements.filter(
|
||||
el => 'type' in el && el.type === 'mindmap'
|
||||
);
|
||||
|
||||
return {
|
||||
count: mindmaps.length,
|
||||
mindmap: mindmaps[mindmaps.length - 1].id,
|
||||
};
|
||||
});
|
||||
|
||||
expect(count).toBe(2);
|
||||
expect((await getMindMapNode(page, lastMindmapId, [0, 0])).text).toBe(
|
||||
'child node 1'
|
||||
);
|
||||
expect((await getMindMapNode(page, lastMindmapId, [0, 0, 0])).text).toBe(
|
||||
'child node 2'
|
||||
);
|
||||
expect((await getMindMapNode(page, lastMindmapId, [0, 0, 1])).text).toBe(
|
||||
'child node 3'
|
||||
);
|
||||
});
|
||||
|
||||
test('allow to type content directly when node has been selected', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
const mindmapId = await createMindMap(page, [0, 0]);
|
||||
const { id: nodeId } = await getMindMapNode(page, mindmapId, [0, 1]);
|
||||
|
||||
await clickView(page, [0, 0]);
|
||||
await selectElementInEdgeless(page, [nodeId]);
|
||||
await type(page, 'parent node');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'child node 1');
|
||||
await pressEnter(page);
|
||||
await pressEnter(page);
|
||||
await type(page, 'child node 2');
|
||||
await pressEnter(page);
|
||||
|
||||
expect((await getMindMapNode(page, mindmapId, [0, 1])).text).toBe(
|
||||
'parent node'
|
||||
);
|
||||
expect((await getMindMapNode(page, mindmapId, [0, 1, 0])).text).toBe(
|
||||
'child node 1'
|
||||
);
|
||||
expect((await getMindMapNode(page, mindmapId, [0, 1, 1])).text).toBe(
|
||||
'child node 2'
|
||||
);
|
||||
});
|
||||
155
blocksuite/tests-legacy/e2e/edgeless/note/drag-handle.spec.ts
Normal file
155
blocksuite/tests-legacy/e2e/edgeless/note/drag-handle.spec.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addNote,
|
||||
dragHandleFromBlockToBlockBottomById,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyEdgelessState,
|
||||
initThreeParagraphs,
|
||||
setEdgelessTool,
|
||||
switchEditorMode,
|
||||
type,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertRectExist, assertRichTexts } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
const CENTER_X = 450;
|
||||
const CENTER_Y = 450;
|
||||
|
||||
test('drag handle should be shown when a note is activated in default mode or hidden in other modes', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
const noteBox = await page.locator('affine-edgeless-note').boundingBox();
|
||||
if (!noteBox) {
|
||||
throw new Error('Missing edgeless affine-note');
|
||||
}
|
||||
|
||||
const [x, y] = [noteBox.x + 26, noteBox.y + noteBox.height / 2];
|
||||
|
||||
await page.mouse.move(x, y);
|
||||
await expect(page.locator('.affine-drag-handle-container')).toBeHidden();
|
||||
await page.mouse.dblclick(x, y);
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.move(x, y);
|
||||
|
||||
await expect(page.locator('.affine-drag-handle-container')).toBeVisible();
|
||||
|
||||
await page.mouse.move(0, 0);
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await page.mouse.move(x, y);
|
||||
await expect(page.locator('.affine-drag-handle-container')).toBeHidden();
|
||||
|
||||
await page.mouse.move(0, 0);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.move(x, y);
|
||||
await expect(page.locator('.affine-drag-handle-container')).toBeVisible();
|
||||
});
|
||||
|
||||
test('drag handle can drag note into another note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
const noteRect = await page
|
||||
.locator(`[data-block-id="${noteId}"]`)
|
||||
.boundingBox();
|
||||
assertRectExist(noteRect);
|
||||
|
||||
const secondNoteId = await addNote(page, 'hello world', 100, 100);
|
||||
await waitNextFrame(page);
|
||||
const secondNoteRect = await page
|
||||
.locator(`[data-block-id="${secondNoteId}"]`)
|
||||
.boundingBox();
|
||||
assertRectExist(secondNoteRect);
|
||||
|
||||
{
|
||||
const [x, y] = [
|
||||
noteRect.x + noteRect.width / 2,
|
||||
noteRect.y + noteRect.height / 2,
|
||||
];
|
||||
await page.mouse.click(noteRect.x, noteRect.y + noteRect.height + 100);
|
||||
await page.mouse.move(x, y);
|
||||
await page.mouse.click(x, y);
|
||||
|
||||
const handlerRect = await page
|
||||
.locator('.affine-drag-handle-container')
|
||||
.boundingBox();
|
||||
assertRectExist(handlerRect);
|
||||
|
||||
await page.mouse.move(
|
||||
handlerRect.x + handlerRect.width / 2,
|
||||
handlerRect.y + handlerRect.height / 2
|
||||
);
|
||||
await page.mouse.down();
|
||||
|
||||
const [targetX, targetY] = [
|
||||
secondNoteRect.x + 10,
|
||||
secondNoteRect.y + secondNoteRect.height / 2,
|
||||
];
|
||||
await page.mouse.move(targetX, targetY);
|
||||
await page.mouse.up();
|
||||
|
||||
await waitNextFrame(page);
|
||||
}
|
||||
});
|
||||
|
||||
test('drag handle should work inside one note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await initThreeParagraphs(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
await page.mouse.dblclick(CENTER_X, CENTER_Y);
|
||||
await dragHandleFromBlockToBlockBottomById(page, '3', '5');
|
||||
await waitNextFrame(page);
|
||||
await expect(page.locator('affine-drag-handle-container')).toBeHidden();
|
||||
await assertRichTexts(page, ['456', '789', '123']);
|
||||
});
|
||||
|
||||
test.fixme(
|
||||
'drag handle should work across multiple notes',
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
await setEdgelessTool(page, 'note');
|
||||
|
||||
await page.mouse.click(200, 200);
|
||||
await focusRichText(page, 3);
|
||||
await waitNextFrame(page);
|
||||
|
||||
// block id 7
|
||||
await type(page, '000');
|
||||
|
||||
await page.mouse.dblclick(CENTER_X, CENTER_Y - 20);
|
||||
await dragHandleFromBlockToBlockBottomById(page, '3', '7');
|
||||
await expect(page.locator('.affine-drag-handle-container')).toBeHidden();
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['456', '789', '000', '123']);
|
||||
|
||||
// await page.mouse.dblclick(305, 305);
|
||||
await dragHandleFromBlockToBlockBottomById(page, '3', '4');
|
||||
await waitNextFrame(page);
|
||||
await expect(page.locator('.affine-drag-handle-container')).toBeHidden();
|
||||
await assertRichTexts(page, ['456', '123', '789', '000']);
|
||||
|
||||
await expect(page.locator('selected > *')).toHaveCount(0);
|
||||
}
|
||||
);
|
||||
79
blocksuite/tests-legacy/e2e/edgeless/note/mode.spec.ts
Normal file
79
blocksuite/tests-legacy/e2e/edgeless/note/mode.spec.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
addNote,
|
||||
changeNoteDisplayModeWithId,
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
switchEditorMode,
|
||||
zoomResetByKeyboard,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertBlockCount } from '../../utils/asserts.js';
|
||||
import { NoteDisplayMode } from '../../utils/bs-alternative.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test('Note added on doc mode should display on both modes by default', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
// there should be 1 note in doc page
|
||||
await assertBlockCount(page, 'note', 1);
|
||||
|
||||
await switchEditorMode(page);
|
||||
// there should be 1 note in edgeless page as well
|
||||
await assertBlockCount(page, 'edgeless-note', 1);
|
||||
});
|
||||
|
||||
test('Note added on edgeless mode should display on edgeless only by default', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await addNote(page, 'note2', 100, 100);
|
||||
|
||||
// assert add note success, there should be 2 notes in edgeless page
|
||||
await assertBlockCount(page, 'edgeless-note', 2);
|
||||
|
||||
await switchEditorMode(page);
|
||||
// switch to doc mode, the note added on edgeless mode should not render on doc mode
|
||||
// there should be only 1 note in doc page
|
||||
await assertBlockCount(page, 'note', 1);
|
||||
});
|
||||
|
||||
test('Note can be changed to display on doc and edgeless mode', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
const noteId = await addNote(page, 'note2', 100, 200);
|
||||
await page.mouse.click(200, 150);
|
||||
// assert add note success, there should be 2 notes in edgeless page
|
||||
await assertBlockCount(page, 'edgeless-note', 2);
|
||||
|
||||
// switch to doc mode
|
||||
await switchEditorMode(page);
|
||||
// there should be 1 notes in doc page
|
||||
await assertBlockCount(page, 'note', 1);
|
||||
|
||||
// switch back to edgeless mode
|
||||
await switchEditorMode(page);
|
||||
// change note display mode to doc only
|
||||
await changeNoteDisplayModeWithId(
|
||||
page,
|
||||
noteId,
|
||||
NoteDisplayMode.DocAndEdgeless
|
||||
);
|
||||
// there should still be 2 notes in edgeless page
|
||||
await assertBlockCount(page, 'edgeless-note', 2);
|
||||
|
||||
// switch to doc mode
|
||||
await switchEditorMode(page);
|
||||
// change successfully, there should be 2 notes in doc page
|
||||
await assertBlockCount(page, 'note', 2);
|
||||
});
|
||||
534
blocksuite/tests-legacy/e2e/edgeless/note/note.spec.ts
Normal file
534
blocksuite/tests-legacy/e2e/edgeless/note/note.spec.ts
Normal file
@@ -0,0 +1,534 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { lightThemeV2 } from '@toeverything/theme/v2';
|
||||
|
||||
import {
|
||||
activeNoteInEdgeless,
|
||||
addNote,
|
||||
assertEdgelessTool,
|
||||
changeEdgelessNoteBackground,
|
||||
changeNoteDisplayMode,
|
||||
locatorComponentToolbar,
|
||||
locatorEdgelessZoomToolButton,
|
||||
selectNoteInEdgeless,
|
||||
setEdgelessTool,
|
||||
switchEditorMode,
|
||||
triggerComponentToolbarAction,
|
||||
zoomOutByKeyboard,
|
||||
zoomResetByKeyboard,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
click,
|
||||
clickBlockById,
|
||||
dragBetweenCoords,
|
||||
dragBetweenIndices,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
focusRichTextEnd,
|
||||
initEmptyEdgelessState,
|
||||
initThreeParagraphs,
|
||||
pressArrowDown,
|
||||
pressArrowUp,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressTab,
|
||||
type,
|
||||
undoByKeyboard,
|
||||
waitForInlineEditorStateUpdated,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockChildrenIds,
|
||||
assertBlockCount,
|
||||
assertEdgelessNonSelectedRect,
|
||||
assertEdgelessNoteBackground,
|
||||
assertEdgelessSelectedRect,
|
||||
assertExists,
|
||||
assertNoteSequence,
|
||||
assertNoteXYWH,
|
||||
assertRichTextInlineRange,
|
||||
assertRichTexts,
|
||||
assertTextSelection,
|
||||
} from '../../utils/asserts.js';
|
||||
import {
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
NoteDisplayMode,
|
||||
} from '../../utils/bs-alternative.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
const CENTER_X = 450;
|
||||
const CENTER_Y = 450;
|
||||
|
||||
test('can drag selected non-active note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]);
|
||||
|
||||
// selected, non-active
|
||||
await page.mouse.click(CENTER_X, CENTER_Y);
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: CENTER_X, y: CENTER_Y },
|
||||
{ x: CENTER_X, y: CENTER_Y + 100 }
|
||||
);
|
||||
await assertNoteXYWH(page, [0, 100, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]);
|
||||
});
|
||||
|
||||
test('add Note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await addNote(page, 'hello', 300, 300);
|
||||
|
||||
await assertEdgelessTool(page, 'default');
|
||||
await assertRichTexts(page, ['', 'hello']);
|
||||
await page.mouse.click(300, 200);
|
||||
await page.mouse.click(350, 320);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
270,
|
||||
260,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
]);
|
||||
});
|
||||
|
||||
test('add empty Note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await setEdgelessTool(page, 'note');
|
||||
// add note at 300,300
|
||||
await page.mouse.click(300, 300);
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
// should wait for inline editor update and resizeObserver callback
|
||||
await waitNextFrame(page);
|
||||
|
||||
// assert add note success
|
||||
await assertBlockCount(page, 'edgeless-note', 2);
|
||||
|
||||
// click out of note
|
||||
await page.mouse.click(250, 200);
|
||||
|
||||
// assert empty note is note removed
|
||||
await page.mouse.move(320, 320);
|
||||
await assertBlockCount(page, 'edgeless-note', 2);
|
||||
});
|
||||
|
||||
test('always keep at least 1 note block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await setEdgelessTool(page, 'default');
|
||||
|
||||
// clicking in default mode will try to remove empty note block
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
const notes = await page.locator('affine-edgeless-note').all();
|
||||
expect(notes.length).toEqual(1);
|
||||
});
|
||||
|
||||
test('edgeless arrow up/down', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { paragraphId, noteId } = await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await activeNoteInEdgeless(page, noteId);
|
||||
await waitNextFrame(page, 400);
|
||||
|
||||
await type(page, 'aaaaa');
|
||||
await pressEnter(page);
|
||||
await type(page, 'aaaaa');
|
||||
await pressEnter(page);
|
||||
await type(page, 'aaa');
|
||||
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
// 0 for page, 1 for surface, 2 for note, 3 for paragraph
|
||||
expect(paragraphId).toBe('3');
|
||||
await clickBlockById(page, paragraphId);
|
||||
await assertRichTextInlineRange(page, 0, 5, 0);
|
||||
|
||||
await pressArrowDown(page);
|
||||
await waitNextFrame(page);
|
||||
await assertRichTextInlineRange(page, 1, 5, 0);
|
||||
|
||||
await pressArrowUp(page);
|
||||
await waitNextFrame(page);
|
||||
await assertRichTextInlineRange(page, 0, 5, 0);
|
||||
|
||||
await pressArrowUp(page);
|
||||
await waitNextFrame(page);
|
||||
await assertRichTextInlineRange(page, 0, 0, 0);
|
||||
});
|
||||
|
||||
test('dragging un-selected note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
const noteBox = await page.locator('affine-edgeless-note').boundingBox();
|
||||
if (!noteBox) {
|
||||
throw new Error('Missing edgeless affine-note');
|
||||
}
|
||||
await page.mouse.click(noteBox.x + 5, noteBox.y + 5);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
noteBox.x,
|
||||
noteBox.y,
|
||||
noteBox.width,
|
||||
noteBox.height,
|
||||
]);
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: noteBox.x + 10, y: noteBox.y + 15 },
|
||||
{ x: noteBox.x + 10, y: noteBox.y + 35 },
|
||||
{ steps: 10 }
|
||||
);
|
||||
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
noteBox.x,
|
||||
noteBox.y + 20,
|
||||
noteBox.width,
|
||||
noteBox.height,
|
||||
]);
|
||||
});
|
||||
|
||||
test('format quick bar should show up when double-clicking on text', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await page.mouse.dblclick(CENTER_X, CENTER_Y);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page
|
||||
.locator('rich-text')
|
||||
.nth(1)
|
||||
.dblclick({
|
||||
position: { x: 10, y: 10 },
|
||||
delay: 20,
|
||||
});
|
||||
await page.waitForTimeout(200);
|
||||
const formatBar = page.locator('.affine-format-bar-widget');
|
||||
await expect(formatBar).toBeVisible();
|
||||
});
|
||||
|
||||
test('when editing text in edgeless, should hide component toolbar', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
|
||||
const toolbar = locatorComponentToolbar(page);
|
||||
await expect(toolbar).toBeVisible();
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await activeNoteInEdgeless(page, noteId);
|
||||
await expect(toolbar).toBeHidden();
|
||||
});
|
||||
|
||||
test('duplicate note should work correctly', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'duplicate');
|
||||
await waitNextFrame(page, 200); // wait viewport fit animation
|
||||
const moreActionsContainer = page.locator('.more-actions-container');
|
||||
await expect(moreActionsContainer).toBeHidden();
|
||||
|
||||
const noteLocator = page.locator('affine-edgeless-note');
|
||||
await expect(noteLocator).toHaveCount(2);
|
||||
const [firstNote, secondNote] = await noteLocator.all();
|
||||
|
||||
// content should be same
|
||||
expect(await firstNote.innerText()).toEqual(await secondNote.innerText());
|
||||
|
||||
// size should be same
|
||||
const firstNoteBox = await firstNote.boundingBox();
|
||||
const secondNoteBox = await secondNote.boundingBox();
|
||||
expect(firstNoteBox!.width).toBeCloseTo(secondNoteBox!.width);
|
||||
expect(firstNoteBox!.height).toBeCloseTo(secondNoteBox!.height);
|
||||
});
|
||||
|
||||
test('double click toolbar zoom button, should not add text', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const zoomOutButton = await locatorEdgelessZoomToolButton(
|
||||
page,
|
||||
'zoomOut',
|
||||
false
|
||||
);
|
||||
await zoomOutButton.dblclick();
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test('change note color', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await assertEdgelessNoteBackground(
|
||||
page,
|
||||
noteId,
|
||||
lightThemeV2['edgeless/note/white']
|
||||
);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'changeNoteColor');
|
||||
await changeEdgelessNoteBackground(page, 'Green');
|
||||
await assertEdgelessNoteBackground(
|
||||
page,
|
||||
noteId,
|
||||
lightThemeV2['edgeless/note/green']
|
||||
);
|
||||
});
|
||||
|
||||
test('cursor for active and inactive state', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await pressEnter(page);
|
||||
await assertRichTexts(page, ['hello', '', '']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
await page.mouse.click(CENTER_X, CENTER_Y);
|
||||
await waitNextFrame(page);
|
||||
await assertTextSelection(page);
|
||||
await page.mouse.dblclick(CENTER_X, CENTER_Y);
|
||||
await waitNextFrame(page);
|
||||
await assertTextSelection(page, {
|
||||
blockId: '3',
|
||||
index: 5,
|
||||
length: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test('when no visible note block, clicking in page mode will auto add a new note block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await assertBlockCount(page, 'edgeless-note', 1);
|
||||
// select note
|
||||
await selectNoteInEdgeless(page, '2');
|
||||
await assertNoteSequence(page, '1');
|
||||
await assertBlockCount(page, 'edgeless-note', 1);
|
||||
// hide note
|
||||
await triggerComponentToolbarAction(page, 'changeNoteDisplayMode');
|
||||
await waitNextFrame(page);
|
||||
await changeNoteDisplayMode(page, NoteDisplayMode.EdgelessOnly);
|
||||
|
||||
await switchEditorMode(page);
|
||||
let note = await page.evaluate(() => {
|
||||
return document.querySelector('affine-note');
|
||||
});
|
||||
expect(note).toBeNull();
|
||||
await click(page, { x: 200, y: 280 });
|
||||
|
||||
note = await page.evaluate(() => {
|
||||
return document.querySelector('affine-note');
|
||||
});
|
||||
expect(note).not.toBeNull();
|
||||
});
|
||||
|
||||
test.fixme(
|
||||
'Click at empty note should add a paragraph block',
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '123');
|
||||
await assertRichTexts(page, ['123']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
// Drag paragraph out of note block
|
||||
const paragraphBlock = await page
|
||||
.locator(`[data-block-id="3"]`)
|
||||
.boundingBox();
|
||||
assertExists(paragraphBlock);
|
||||
await page.mouse.dblclick(paragraphBlock.x, paragraphBlock.y);
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.move(
|
||||
paragraphBlock.x + paragraphBlock.width / 2,
|
||||
paragraphBlock.y + paragraphBlock.height / 2
|
||||
);
|
||||
await waitNextFrame(page);
|
||||
const handle = await page
|
||||
.locator('.affine-drag-handle-container')
|
||||
.boundingBox();
|
||||
assertExists(handle);
|
||||
await page.mouse.move(
|
||||
handle.x + handle.width / 2,
|
||||
handle.y + handle.height / 2,
|
||||
{ steps: 10 }
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(100, 200, { steps: 30 });
|
||||
await page.mouse.up();
|
||||
|
||||
// There should be two note blocks and one paragraph block
|
||||
await assertRichTexts(page, ['123']);
|
||||
await assertBlockCount(page, 'edgeless-note', 2);
|
||||
await assertBlockCount(page, 'paragraph', 1);
|
||||
|
||||
// Click at empty note block to add a paragraph block
|
||||
const emptyNote = await page.locator(`[data-block-id="2"]`).boundingBox();
|
||||
assertExists(emptyNote);
|
||||
await page.mouse.click(
|
||||
emptyNote.x + emptyNote.width / 2,
|
||||
emptyNote.y + emptyNote.height / 2
|
||||
);
|
||||
await waitNextFrame(page, 300);
|
||||
await type(page, '456');
|
||||
await waitNextFrame(page, 400);
|
||||
|
||||
await page.mouse.click(100, 100);
|
||||
await waitNextFrame(page, 400);
|
||||
await assertBlockCount(page, 'paragraph', 2);
|
||||
}
|
||||
);
|
||||
|
||||
test('Should focus at closest text block when note collapse', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
// Make sure there is no rich text content
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
// Select the note
|
||||
await zoomOutByKeyboard(page);
|
||||
const notePortalBox = await page
|
||||
.locator('affine-edgeless-note')
|
||||
.boundingBox();
|
||||
assertExists(notePortalBox);
|
||||
await page.mouse.click(notePortalBox.x + 10, notePortalBox.y + 10);
|
||||
await waitNextFrame(page, 200);
|
||||
const selectedRect = page
|
||||
.locator('edgeless-selected-rect')
|
||||
.locator('.affine-edgeless-selected-rect');
|
||||
await expect(selectedRect).toBeVisible();
|
||||
|
||||
// Collapse the note
|
||||
const selectedBox = await selectedRect.boundingBox();
|
||||
assertExists(selectedBox);
|
||||
await page.mouse.move(
|
||||
selectedBox.x + selectedBox.width / 2,
|
||||
selectedBox.y + selectedBox.height
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(
|
||||
selectedBox.x + selectedBox.width / 2,
|
||||
selectedBox.y + selectedBox.height + 200,
|
||||
{ steps: 10 }
|
||||
);
|
||||
await page.mouse.up();
|
||||
await expect(selectedRect).toBeVisible();
|
||||
|
||||
// Click at the bottom of note to focus at the closest text block
|
||||
await page.mouse.click(
|
||||
selectedBox.x + selectedBox.width / 2,
|
||||
selectedBox.y + selectedBox.height - 20
|
||||
);
|
||||
await waitNextFrame(page, 200);
|
||||
|
||||
// Should be enter edit mode and there are no selected rect
|
||||
await expect(selectedRect).toBeHidden();
|
||||
|
||||
// Focus at the closest text block and make sure can type
|
||||
await type(page, 'hello');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertRichTexts(page, ['hello']);
|
||||
});
|
||||
|
||||
test('delete first block in edgeless note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await assertNoteXYWH(page, [0, 0, DEFAULT_NOTE_WIDTH, DEFAULT_NOTE_HEIGHT]);
|
||||
await page.mouse.dblclick(CENTER_X, CENTER_Y);
|
||||
|
||||
// first block without children, nothing should happen
|
||||
await assertRichTexts(page, ['']);
|
||||
await assertBlockChildrenIds(page, '3', []);
|
||||
await pressBackspace(page);
|
||||
|
||||
await type(page, 'aaa');
|
||||
await pressEnter(page);
|
||||
await type(page, 'bbb');
|
||||
await pressTab(page);
|
||||
await assertRichTexts(page, ['aaa', 'bbb']);
|
||||
await assertBlockChildrenIds(page, '3', ['4']);
|
||||
|
||||
// first block with children, need to bring children to parent
|
||||
await focusRichTextEnd(page);
|
||||
await pressBackspace(page, 3);
|
||||
await assertRichTexts(page, ['', 'bbb']);
|
||||
await pressBackspace(page);
|
||||
await assertRichTexts(page, ['bbb']);
|
||||
await assertBlockChildrenIds(page, '4', []);
|
||||
});
|
||||
|
||||
test('select text cross blocks in edgeless note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await activeNoteInEdgeless(page, noteId);
|
||||
await waitNextFrame(page, 400);
|
||||
|
||||
await type(page, 'aaa');
|
||||
await pressEnter(page);
|
||||
await type(page, 'bbb');
|
||||
await pressEnter(page);
|
||||
await type(page, 'ccc');
|
||||
await assertRichTexts(page, ['aaa', 'bbb', 'ccc']);
|
||||
|
||||
await dragBetweenIndices(page, [0, 1], [2, 2]);
|
||||
await pressBackspace(page);
|
||||
await assertRichTexts(page, ['ac']);
|
||||
});
|
||||
223
blocksuite/tests-legacy/e2e/edgeless/note/resize.spec.ts
Normal file
223
blocksuite/tests-legacy/e2e/edgeless/note/resize.spec.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
activeNoteInEdgeless,
|
||||
enterPlaygroundRoom,
|
||||
getNoteRect,
|
||||
initEmptyEdgelessState,
|
||||
redoByClick,
|
||||
resizeElementByHandle,
|
||||
selectNoteInEdgeless,
|
||||
setEdgelessTool,
|
||||
switchEditorMode,
|
||||
triggerComponentToolbarAction,
|
||||
type,
|
||||
undoByClick,
|
||||
waitForInlineEditorStateUpdated,
|
||||
waitNextFrame,
|
||||
zoomResetByKeyboard,
|
||||
} from '../../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockCount,
|
||||
assertEdgelessSelectedRect,
|
||||
assertNoteRectEqual,
|
||||
assertRectEqual,
|
||||
assertRichTexts,
|
||||
} from '../../utils/asserts.js';
|
||||
import { NOTE_MIN_HEIGHT, NOTE_MIN_WIDTH } from '../../utils/bs-alternative.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test('resize note in edgeless mode', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await activeNoteInEdgeless(page, noteId);
|
||||
await waitNextFrame(page, 400);
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
// unselect note
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
expect(noteId).toBe('2'); // 0 for page, 1 for surface
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
|
||||
const initRect = await getNoteRect(page, noteId);
|
||||
await resizeElementByHandle(page, { x: -100, y: 0 }, 'bottom-left');
|
||||
|
||||
const draggedRect = await getNoteRect(page, noteId);
|
||||
assertRectEqual(draggedRect, {
|
||||
x: initRect.x - 100,
|
||||
y: initRect.y,
|
||||
w: initRect.w + 100,
|
||||
h: initRect.h,
|
||||
});
|
||||
|
||||
await switchEditorMode(page);
|
||||
await switchEditorMode(page);
|
||||
const newRect = await getNoteRect(page, noteId);
|
||||
assertRectEqual(newRect, draggedRect);
|
||||
});
|
||||
|
||||
test('resize note then collapse note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await activeNoteInEdgeless(page, noteId);
|
||||
await waitNextFrame(page, 400);
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
// unselect note
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
expect(noteId).toBe('2'); // 0 for page, 1 for surface
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
|
||||
const initRect = await getNoteRect(page, noteId);
|
||||
|
||||
await resizeElementByHandle(page, { x: 0, y: 100 }, 'bottom-right');
|
||||
let noteRect = await getNoteRect(page, noteId);
|
||||
await expect(page.getByTestId('edgeless-note-collapse-button')).toBeVisible();
|
||||
assertRectEqual(noteRect, {
|
||||
x: initRect.x,
|
||||
y: initRect.y,
|
||||
w: initRect.w,
|
||||
h: initRect.h + 100,
|
||||
});
|
||||
|
||||
await page.getByTestId('edgeless-note-collapse-button')!.click();
|
||||
let domRect = await page.locator('affine-edgeless-note').boundingBox();
|
||||
expect(domRect!.height).toBeCloseTo(NOTE_MIN_HEIGHT);
|
||||
|
||||
await page.getByTestId('edgeless-note-collapse-button')!.click();
|
||||
domRect = await page.locator('affine-edgeless-note').boundingBox();
|
||||
expect(domRect!.height).toBeCloseTo(initRect.h + 100);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await resizeElementByHandle(page, { x: 0, y: -150 }, 'bottom-right');
|
||||
|
||||
noteRect = await getNoteRect(page, noteId);
|
||||
assertRectEqual(noteRect, {
|
||||
x: initRect.x,
|
||||
y: initRect.y,
|
||||
w: initRect.w,
|
||||
h: NOTE_MIN_HEIGHT,
|
||||
});
|
||||
|
||||
await switchEditorMode(page);
|
||||
await switchEditorMode(page);
|
||||
const newRect = await getNoteRect(page, noteId);
|
||||
assertRectEqual(newRect, noteRect);
|
||||
});
|
||||
|
||||
test('resize note then auto size and custom size', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await activeNoteInEdgeless(page, noteId);
|
||||
await waitNextFrame(page, 400);
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['hello']);
|
||||
// unselect note
|
||||
await page.mouse.click(50, 50);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
|
||||
const initRect = await getNoteRect(page, noteId);
|
||||
|
||||
await resizeElementByHandle(page, { x: 0, y: 100 }, 'bottom-right');
|
||||
|
||||
const draggedRect = await getNoteRect(page, noteId);
|
||||
assertRectEqual(draggedRect, {
|
||||
x: initRect.x,
|
||||
y: initRect.y,
|
||||
w: initRect.w,
|
||||
h: initRect.h + 100,
|
||||
});
|
||||
|
||||
await triggerComponentToolbarAction(page, 'autoSize');
|
||||
await waitNextFrame(page, 200);
|
||||
const autoSizeRect = await getNoteRect(page, noteId);
|
||||
assertRectEqual(autoSizeRect, initRect);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'autoSize');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertNoteRectEqual(page, noteId, draggedRect);
|
||||
|
||||
await undoByClick(page);
|
||||
await page.mouse.click(50, 50);
|
||||
await waitNextFrame(page, 200);
|
||||
await assertNoteRectEqual(page, noteId, initRect);
|
||||
|
||||
await redoByClick(page);
|
||||
await waitNextFrame(page, 200);
|
||||
await assertNoteRectEqual(page, noteId, draggedRect);
|
||||
});
|
||||
|
||||
test('drag to add customized size note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await setEdgelessTool(page, 'note');
|
||||
// add note at 300,300
|
||||
await page.mouse.move(300, 300);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(900, 600, { steps: 10 });
|
||||
await page.mouse.up();
|
||||
// should wait for inline editor update and resizeObserver callback
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
|
||||
// assert add note success
|
||||
await assertBlockCount(page, 'edgeless-note', 2);
|
||||
|
||||
// click out of note
|
||||
await page.mouse.click(250, 200);
|
||||
// click on note to select it
|
||||
await page.mouse.click(600, 500);
|
||||
// assert selected note
|
||||
// note add on edgeless mode will have a offsetX of 30 and offsetY of 40
|
||||
await assertEdgelessSelectedRect(page, [270, 260, 600, 300]);
|
||||
});
|
||||
|
||||
test('drag to add customized size note: should clamp to min width and min height', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await setEdgelessTool(page, 'note');
|
||||
|
||||
// add note at 300,300
|
||||
await page.mouse.move(300, 300);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(400, 360, { steps: 10 });
|
||||
await page.mouse.up();
|
||||
await waitNextFrame(page);
|
||||
|
||||
await waitNextFrame(page);
|
||||
|
||||
// should wait for inline editor update and resizeObserver callback
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
// assert add note success
|
||||
await assertBlockCount(page, 'edgeless-note', 2);
|
||||
|
||||
// click out of note
|
||||
await page.mouse.click(250, 200);
|
||||
// click on note to select it
|
||||
await page.mouse.click(320, 300);
|
||||
// assert selected note
|
||||
// note add on edgeless mode will have a offsetX of 30 and offsetY of 40
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
270,
|
||||
260,
|
||||
NOTE_MIN_WIDTH,
|
||||
NOTE_MIN_HEIGHT,
|
||||
]);
|
||||
});
|
||||
147
blocksuite/tests-legacy/e2e/edgeless/note/scale.spec.ts
Normal file
147
blocksuite/tests-legacy/e2e/edgeless/note/scale.spec.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addNote,
|
||||
locatorScalePanelButton,
|
||||
selectNoteInEdgeless,
|
||||
switchEditorMode,
|
||||
triggerComponentToolbarAction,
|
||||
zoomResetByKeyboard,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
copyByKeyboard,
|
||||
pasteByKeyboard,
|
||||
selectAllByKeyboard,
|
||||
} from '../../utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/misc.js';
|
||||
import { assertRectExist } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
async function setupAndAddNote(page: Page) {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
const noteId = await addNote(page, 'hello world', 100, 200);
|
||||
await page.mouse.click(0, 0);
|
||||
return noteId;
|
||||
}
|
||||
|
||||
async function openScalePanel(page: Page, noteId: string) {
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'changeNoteScale');
|
||||
await waitNextFrame(page);
|
||||
const scalePanel = page.locator('edgeless-scale-panel');
|
||||
await expect(scalePanel).toBeVisible();
|
||||
return scalePanel;
|
||||
}
|
||||
|
||||
async function checkNoteScale(
|
||||
page: Page,
|
||||
noteId: string,
|
||||
expectedScale: number,
|
||||
expectedType: 'equal' | 'greater' | 'less' = 'equal'
|
||||
) {
|
||||
const edgelessNote = page.locator(
|
||||
`affine-edgeless-note[data-block-id="${noteId}"]`
|
||||
);
|
||||
const noteContainer = edgelessNote.getByTestId('edgeless-note-container');
|
||||
const style = await noteContainer.getAttribute('style');
|
||||
|
||||
if (!style) {
|
||||
throw new Error('Style attribute not found');
|
||||
}
|
||||
|
||||
const scaleMatch = style.match(/transform:\s*scale\(([\d.]+)\)/);
|
||||
if (!scaleMatch) {
|
||||
throw new Error('Scale transform not found in style');
|
||||
}
|
||||
|
||||
const actualScale = parseFloat(scaleMatch[1]);
|
||||
|
||||
switch (expectedType) {
|
||||
case 'equal':
|
||||
expect(actualScale).toBeCloseTo(expectedScale, 2);
|
||||
break;
|
||||
case 'greater':
|
||||
expect(actualScale).toBeGreaterThan(expectedScale);
|
||||
break;
|
||||
case 'less':
|
||||
expect(actualScale).toBeLessThan(expectedScale);
|
||||
}
|
||||
}
|
||||
|
||||
test.describe('note scale', () => {
|
||||
test('Note scale can be changed by scale panel button', async ({ page }) => {
|
||||
const noteId = await setupAndAddNote(page);
|
||||
await openScalePanel(page, noteId);
|
||||
|
||||
const scale150 = locatorScalePanelButton(page, 50);
|
||||
await scale150.click();
|
||||
|
||||
await checkNoteScale(page, noteId, 0.5);
|
||||
});
|
||||
|
||||
test('Note scale can be changed by scale panel input', async ({ page }) => {
|
||||
const noteId = await setupAndAddNote(page);
|
||||
const scalePanel = await openScalePanel(page, noteId);
|
||||
|
||||
const scaleInput = scalePanel.locator('.scale-input');
|
||||
await scaleInput.click();
|
||||
await page.keyboard.type('50');
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
await checkNoteScale(page, noteId, 0.5);
|
||||
});
|
||||
|
||||
test('Note scale input support copy paste', async ({ page }) => {
|
||||
const noteId = await setupAndAddNote(page);
|
||||
const scalePanel = await openScalePanel(page, noteId);
|
||||
|
||||
const scaleInput = scalePanel.locator('.scale-input');
|
||||
await scaleInput.click();
|
||||
await page.keyboard.type('50');
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'changeNoteScale');
|
||||
await waitNextFrame(page);
|
||||
|
||||
await scaleInput.click();
|
||||
await pasteByKeyboard(page);
|
||||
await page.keyboard.press('Enter');
|
||||
|
||||
await checkNoteScale(page, noteId, 0.5);
|
||||
});
|
||||
|
||||
test('Note scale can be changed by shift drag', async ({ page }) => {
|
||||
const noteId = await setupAndAddNote(page);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
|
||||
const edgelessNote = page.locator(
|
||||
`affine-edgeless-note[data-block-id="${noteId}"]`
|
||||
);
|
||||
const noteRect = await edgelessNote.boundingBox();
|
||||
assertRectExist(noteRect);
|
||||
await page.mouse.move(
|
||||
noteRect.x + noteRect.width,
|
||||
noteRect.y + noteRect.height
|
||||
);
|
||||
await page.keyboard.down('Shift');
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(
|
||||
noteRect.x + noteRect.width * 2,
|
||||
noteRect.y + noteRect.height * 2
|
||||
);
|
||||
await page.mouse.up();
|
||||
|
||||
// expect style scale to be greater than 1
|
||||
await checkNoteScale(page, noteId, 1, 'greater');
|
||||
});
|
||||
});
|
||||
156
blocksuite/tests-legacy/e2e/edgeless/note/slicer.spec.ts
Normal file
156
blocksuite/tests-legacy/e2e/edgeless/note/slicer.spec.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
initSixParagraphs,
|
||||
initThreeParagraphs,
|
||||
selectNoteInEdgeless,
|
||||
switchEditorMode,
|
||||
triggerComponentToolbarAction,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertRectExist, assertRichTexts } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test.describe('note slicer', () => {
|
||||
test('could enable and disenable note slicer', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await initSixParagraphs(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
// note slicer button should not be visible when note slicer setting is disenabled
|
||||
await expect(page.locator('.note-slicer-button')).toBeHidden();
|
||||
await expect(page.locator('.note-slicer-dividing-line')).toHaveCount(0);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting');
|
||||
// note slicer button should be visible when note slicer setting is enabled
|
||||
await expect(page.locator('.note-slicer-button')).toBeVisible();
|
||||
await expect(page.locator('.note-slicer-dividing-line')).toHaveCount(5);
|
||||
});
|
||||
|
||||
test('note slicer will add new note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await initSixParagraphs(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await expect(page.locator('affine-edgeless-note')).toHaveCount(1);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting');
|
||||
await expect(page.locator('.note-slicer-button')).toBeVisible();
|
||||
|
||||
await page.locator('.note-slicer-button').click();
|
||||
|
||||
await expect(page.locator('affine-edgeless-note')).toHaveCount(2);
|
||||
});
|
||||
|
||||
test('note slicer button should appears at right position', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting');
|
||||
|
||||
const blocks = await page
|
||||
.locator(`[data-block-id="${noteId}"] [data-block-id]`)
|
||||
.all();
|
||||
expect(blocks.length).toBe(3);
|
||||
|
||||
const firstBlockRect = await blocks[0].boundingBox();
|
||||
assertRectExist(firstBlockRect);
|
||||
const secondBlockRect = await blocks[1].boundingBox();
|
||||
assertRectExist(secondBlockRect);
|
||||
await page.mouse.move(
|
||||
secondBlockRect.x + 1,
|
||||
secondBlockRect.y + secondBlockRect.height / 2
|
||||
);
|
||||
|
||||
let slicerButtonRect = await page
|
||||
.locator('.note-slicer-button')
|
||||
.boundingBox();
|
||||
assertRectExist(slicerButtonRect);
|
||||
|
||||
let buttonRectMiddle = slicerButtonRect.y + slicerButtonRect.height / 2;
|
||||
|
||||
expect(buttonRectMiddle).toBeGreaterThan(
|
||||
firstBlockRect.y + firstBlockRect.height
|
||||
);
|
||||
expect(buttonRectMiddle).toBeGreaterThan(secondBlockRect.y);
|
||||
|
||||
const thirdBlockRect = await blocks[2].boundingBox();
|
||||
assertRectExist(thirdBlockRect);
|
||||
await page.mouse.move(
|
||||
thirdBlockRect.x + 1,
|
||||
thirdBlockRect.y + thirdBlockRect.height / 2
|
||||
);
|
||||
|
||||
slicerButtonRect = await page.locator('.note-slicer-button').boundingBox();
|
||||
assertRectExist(slicerButtonRect);
|
||||
|
||||
buttonRectMiddle = slicerButtonRect.y + slicerButtonRect.height / 2;
|
||||
expect(buttonRectMiddle).toBeGreaterThan(
|
||||
secondBlockRect.y + secondBlockRect.height
|
||||
);
|
||||
expect(buttonRectMiddle).toBeLessThan(thirdBlockRect.y);
|
||||
});
|
||||
|
||||
test('note slicer button should appears at right position when editor is not located at left top corner', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
|
||||
await page.evaluate(() => {
|
||||
const el = document.createElement('div');
|
||||
const app = document.querySelector('#app') as HTMLElement;
|
||||
|
||||
el.style.height = '100px';
|
||||
el.style.background = 'red';
|
||||
|
||||
app!.style.paddingLeft = '80px';
|
||||
|
||||
document.body.insertBefore(el, app);
|
||||
});
|
||||
|
||||
const blocks = await page
|
||||
.locator(`[data-block-id="${noteId}"] [data-block-id]`)
|
||||
.all();
|
||||
expect(blocks.length).toBe(3);
|
||||
|
||||
const firstBlockRect = await blocks[0].boundingBox();
|
||||
assertRectExist(firstBlockRect);
|
||||
const secondBlockRect = await blocks[1].boundingBox();
|
||||
assertRectExist(secondBlockRect);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting');
|
||||
await page.mouse.move(
|
||||
secondBlockRect.x + 1,
|
||||
secondBlockRect.y + secondBlockRect.height / 2
|
||||
);
|
||||
|
||||
const slicerButtonRect = await page
|
||||
.locator('.note-slicer-button')
|
||||
.boundingBox();
|
||||
assertRectExist(slicerButtonRect);
|
||||
|
||||
const buttonRectMiddle = slicerButtonRect.y + slicerButtonRect.height / 2;
|
||||
|
||||
expect(buttonRectMiddle).toBeGreaterThan(
|
||||
firstBlockRect.y + firstBlockRect.height
|
||||
);
|
||||
expect(buttonRectMiddle).toBeGreaterThan(secondBlockRect.y);
|
||||
});
|
||||
});
|
||||
140
blocksuite/tests-legacy/e2e/edgeless/note/undo-redo.spec.ts
Normal file
140
blocksuite/tests-legacy/e2e/edgeless/note/undo-redo.spec.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
activeNoteInEdgeless,
|
||||
click,
|
||||
copyByKeyboard,
|
||||
countBlock,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
fillLine,
|
||||
focusRichText,
|
||||
getNoteRect,
|
||||
initEmptyEdgelessState,
|
||||
initSixParagraphs,
|
||||
pasteByKeyboard,
|
||||
redoByClick,
|
||||
redoByKeyboard,
|
||||
selectNoteInEdgeless,
|
||||
switchEditorMode,
|
||||
triggerComponentToolbarAction,
|
||||
type,
|
||||
undoByClick,
|
||||
undoByKeyboard,
|
||||
waitNextFrame,
|
||||
zoomResetByKeyboard,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertRectEqual } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test('undo/redo should work correctly after clipping', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await initSixParagraphs(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await expect(page.locator('affine-edgeless-note')).toHaveCount(1);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting');
|
||||
|
||||
const button = page.locator('.note-slicer-button');
|
||||
await button.click();
|
||||
await expect(page.locator('affine-edgeless-note')).toHaveCount(2);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await expect(page.locator('affine-edgeless-note')).toHaveCount(1);
|
||||
await redoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await expect(page.locator('affine-edgeless-note')).toHaveCount(2);
|
||||
});
|
||||
|
||||
test('undo/redo should work correctly after resizing', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await activeNoteInEdgeless(page, noteId);
|
||||
await waitNextFrame(page, 400);
|
||||
// current implementation may be a little inefficient
|
||||
await fillLine(page, true);
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page, 400);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
|
||||
const initRect = await getNoteRect(page, noteId);
|
||||
const rightHandle = page.locator('.handle[aria-label="right"] .resize');
|
||||
const box = await rightHandle.boundingBox();
|
||||
if (box === null) throw new Error();
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: box.x + 5, y: box.y + 5 },
|
||||
{ x: box.x + 105, y: box.y + 5 }
|
||||
);
|
||||
const draggedRect = await getNoteRect(page, noteId);
|
||||
assertRectEqual(draggedRect, {
|
||||
x: initRect.x,
|
||||
y: initRect.y,
|
||||
w: initRect.w + 100,
|
||||
h: draggedRect.h, // not assert `h` here
|
||||
});
|
||||
expect(draggedRect.h).toBe(initRect.h);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
const undoRect = await getNoteRect(page, noteId);
|
||||
assertRectEqual(undoRect, initRect);
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
const redoRect = await getNoteRect(page, noteId);
|
||||
assertRectEqual(redoRect, draggedRect);
|
||||
});
|
||||
|
||||
test('continuous undo and redo (note block add operation) should work', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await switchEditorMode(page);
|
||||
await click(page, { x: 260, y: 450 });
|
||||
await copyByKeyboard(page);
|
||||
|
||||
let count = await countBlock(page, 'affine-edgeless-note');
|
||||
expect(count).toBe(1);
|
||||
|
||||
await page.mouse.move(100, 100);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page, 1000);
|
||||
|
||||
await page.mouse.move(200, 200);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page, 1000);
|
||||
|
||||
await page.mouse.move(300, 300);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page, 1000);
|
||||
|
||||
count = await countBlock(page, 'affine-edgeless-note');
|
||||
expect(count).toBe(4);
|
||||
|
||||
await undoByClick(page);
|
||||
count = await countBlock(page, 'affine-edgeless-note');
|
||||
expect(count).toBe(3);
|
||||
|
||||
await undoByClick(page);
|
||||
count = await countBlock(page, 'affine-edgeless-note');
|
||||
expect(count).toBe(2);
|
||||
|
||||
await redoByClick(page);
|
||||
count = await countBlock(page, 'affine-edgeless-note');
|
||||
expect(count).toBe(3);
|
||||
|
||||
await redoByClick(page);
|
||||
count = await countBlock(page, 'affine-edgeless-note');
|
||||
expect(count).toBe(4);
|
||||
});
|
||||
263
blocksuite/tests-legacy/e2e/edgeless/pan.spec.ts
Normal file
263
blocksuite/tests-legacy/e2e/edgeless/pan.spec.ts
Normal file
@@ -0,0 +1,263 @@
|
||||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
activeNoteInEdgeless,
|
||||
addNote,
|
||||
assertEdgelessTool,
|
||||
locatorEdgelessToolButton,
|
||||
multiTouchDown,
|
||||
multiTouchMove,
|
||||
multiTouchUp,
|
||||
setEdgelessTool,
|
||||
switchEditorMode,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
addBasicRectShapeElement,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
toggleEditorReadonly,
|
||||
type,
|
||||
waitForInlineEditorStateUpdated,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessSelectedRect,
|
||||
assertNotHasClass,
|
||||
assertRichTexts,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('pan tool basic', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await addBasicRectShapeElement(page, start, end);
|
||||
|
||||
await setEdgelessTool(page, 'pan');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: start.x + 5,
|
||||
y: start.y + 5,
|
||||
},
|
||||
{
|
||||
x: start.x + 25,
|
||||
y: start.y + 25,
|
||||
}
|
||||
);
|
||||
await setEdgelessTool(page, 'default');
|
||||
|
||||
await page.mouse.click(start.x + 25, start.y + 25);
|
||||
await assertEdgelessSelectedRect(page, [120, 120, 100, 100]);
|
||||
});
|
||||
|
||||
test('pan tool shortcut', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await addBasicRectShapeElement(page, start, end);
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y + 5);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
await page.keyboard.down('Space');
|
||||
await assertEdgelessTool(page, 'pan');
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: start.x + 5,
|
||||
y: start.y + 5,
|
||||
},
|
||||
{
|
||||
x: start.x + 25,
|
||||
y: start.y + 25,
|
||||
}
|
||||
);
|
||||
|
||||
await page.keyboard.up('Space');
|
||||
await assertEdgelessSelectedRect(page, [120, 120, 100, 100]);
|
||||
});
|
||||
|
||||
// FIXME(@doouding): Failed on CI
|
||||
test.skip('pan tool with middle button', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await addBasicRectShapeElement(page, start, end);
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y + 5);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 400,
|
||||
y: 400,
|
||||
},
|
||||
{
|
||||
x: 420,
|
||||
y: 420,
|
||||
},
|
||||
{
|
||||
button: 'middle',
|
||||
}
|
||||
);
|
||||
|
||||
await assertEdgelessTool(page, 'default');
|
||||
await assertEdgelessSelectedRect(page, [120, 120, 100, 100]);
|
||||
});
|
||||
|
||||
test('pan tool shortcut should revert to the previous tool on keyup', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await page.mouse.click(100, 100);
|
||||
|
||||
await setEdgelessTool(page, 'brush');
|
||||
{
|
||||
await page.keyboard.down('Space');
|
||||
await assertEdgelessTool(page, 'pan');
|
||||
|
||||
await page.keyboard.up('Space');
|
||||
await assertEdgelessTool(page, 'brush');
|
||||
}
|
||||
});
|
||||
|
||||
test('pan tool shortcut does not affect other tools while using the tool', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
// Test if while drawing shortcut does not switch to pan tool
|
||||
await setEdgelessTool(page, 'brush');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: 100, y: 110 },
|
||||
{ x: 200, y: 300 },
|
||||
{
|
||||
click: true,
|
||||
beforeMouseUp: async () => {
|
||||
await page.keyboard.down('Space');
|
||||
await assertEdgelessTool(page, 'brush');
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await setEdgelessTool(page, 'eraser');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: 100, y: 110 },
|
||||
{ x: 200, y: 300 },
|
||||
{
|
||||
click: true,
|
||||
beforeMouseUp: async () => {
|
||||
await page.keyboard.down('Space');
|
||||
await assertEdgelessTool(page, 'eraser');
|
||||
},
|
||||
}
|
||||
);
|
||||
// Maybe add other tools too
|
||||
});
|
||||
|
||||
test('pan tool shortcut when user is editing', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const ids = await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await setEdgelessTool(page, 'default');
|
||||
|
||||
await activeNoteInEdgeless(page, ids.noteId);
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await page.keyboard.down('Space');
|
||||
const defaultButton = await locatorEdgelessToolButton(page, 'pan', false);
|
||||
await assertNotHasClass(defaultButton, 'pan');
|
||||
await waitNextFrame(page);
|
||||
});
|
||||
|
||||
test.describe('pan tool in readonly mode', () => {
|
||||
async function setupReadonlyEdgeless(page: Page) {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const noteId = await addNote(page, 'hello world', 100, 200);
|
||||
await page.mouse.click(50, 100);
|
||||
|
||||
const edgelessNote = page.locator(
|
||||
`affine-edgeless-note[data-block-id="${noteId}"]`
|
||||
);
|
||||
const originalBoundingBox = await edgelessNote.boundingBox();
|
||||
expect(originalBoundingBox).not.toBeNull();
|
||||
const { x: originalX, y: originalY } = originalBoundingBox!;
|
||||
|
||||
// Toggle readonly mode
|
||||
await toggleEditorReadonly(page);
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
return { edgelessNote, originalX, originalY };
|
||||
}
|
||||
|
||||
async function assertPanned(
|
||||
edgelessNote: Locator,
|
||||
originalX: number,
|
||||
originalY: number
|
||||
) {
|
||||
const newBoundingBox = await edgelessNote.boundingBox();
|
||||
expect(newBoundingBox).not.toBeNull();
|
||||
const { x: newX, y: newY } = newBoundingBox!;
|
||||
|
||||
expect(newX).toBeGreaterThan(originalX);
|
||||
expect(newY).toBeGreaterThan(originalY);
|
||||
}
|
||||
|
||||
test('can be used by keyboard', async ({ page }) => {
|
||||
const { edgelessNote, originalX, originalY } =
|
||||
await setupReadonlyEdgeless(page);
|
||||
|
||||
await page.keyboard.down('Space');
|
||||
await assertEdgelessTool(page, 'pan');
|
||||
|
||||
// Pan the viewport
|
||||
await dragBetweenCoords(page, { x: 300, y: 300 }, { x: 400, y: 400 });
|
||||
|
||||
await assertPanned(edgelessNote, originalX, originalY);
|
||||
});
|
||||
|
||||
test('can be used by multi-touch', async ({ page }) => {
|
||||
const { edgelessNote, originalX, originalY } =
|
||||
await setupReadonlyEdgeless(page);
|
||||
|
||||
// Pan the viewport using multi-touch
|
||||
const from = [
|
||||
{ x: 300, y: 300 },
|
||||
{ x: 400, y: 300 },
|
||||
];
|
||||
const to = [
|
||||
{ x: 350, y: 350 },
|
||||
{ x: 450, y: 350 },
|
||||
];
|
||||
await multiTouchDown(page, from);
|
||||
await multiTouchMove(page, from, to);
|
||||
await multiTouchUp(page, to);
|
||||
|
||||
await assertPanned(edgelessNote, originalX, originalY);
|
||||
});
|
||||
});
|
||||
125
blocksuite/tests-legacy/e2e/edgeless/paste-block.spec.ts
Normal file
125
blocksuite/tests-legacy/e2e/edgeless/paste-block.spec.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
click,
|
||||
copyByKeyboard,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getAllEdgelessNoteIds,
|
||||
getAllEdgelessTextIds,
|
||||
getNoteBoundBoxInEdgeless,
|
||||
initEmptyEdgelessState,
|
||||
pasteByKeyboard,
|
||||
pasteTestImage,
|
||||
pressEnter,
|
||||
pressEnterWithShortkey,
|
||||
pressEscape,
|
||||
selectAllByKeyboard,
|
||||
setEdgelessTool,
|
||||
switchEditorMode,
|
||||
type,
|
||||
} from '../utils/actions/index.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('pasting blocks', () => {
|
||||
const initContent = async (page: Page) => {
|
||||
// Text
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
// Image
|
||||
await pasteTestImage(page);
|
||||
await pressEnter(page);
|
||||
// Text
|
||||
await type(page, 'world');
|
||||
await pressEnter(page);
|
||||
// code
|
||||
await type(page, '``` ');
|
||||
await type(page, 'code');
|
||||
await pressEnterWithShortkey(page);
|
||||
};
|
||||
test('pasting a note block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await initContent(page);
|
||||
await switchEditorMode(page);
|
||||
await click(page, { x: 0, y: 0 });
|
||||
const box = await getNoteBoundBoxInEdgeless(page, noteId);
|
||||
await click(page, {
|
||||
x: box.x + 10,
|
||||
y: box.y + 10,
|
||||
});
|
||||
await copyByKeyboard(page);
|
||||
await pasteByKeyboard(page);
|
||||
// not equal to noteId
|
||||
const noteIds = await getAllEdgelessNoteIds(page);
|
||||
expect(noteIds.length).toBe(2);
|
||||
expect(noteIds[0]).toBe(noteId);
|
||||
const newNoteId = noteIds[1];
|
||||
const newNote = page.locator(
|
||||
`affine-edgeless-note[data-block-id="${newNoteId}"]`
|
||||
);
|
||||
await expect(newNote).toBeVisible();
|
||||
const blocks = newNote.locator('[data-block-id]');
|
||||
await expect(blocks.nth(0)).toContainText('hello');
|
||||
await expect(blocks.nth(1).locator('.resizable-img')).toBeVisible();
|
||||
await expect(blocks.nth(2)).toContainText('world');
|
||||
await expect(blocks.nth(3)).toContainText('code');
|
||||
});
|
||||
test('pasting a edgeless block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.dblclick(130, 140, {
|
||||
delay: 100,
|
||||
});
|
||||
await initContent(page);
|
||||
await pressEscape(page, 3);
|
||||
await page.mouse.click(130, 140);
|
||||
await copyByKeyboard(page);
|
||||
await page.mouse.move(500, 500);
|
||||
await pasteByKeyboard(page);
|
||||
const textIds = await getAllEdgelessTextIds(page);
|
||||
expect(textIds.length).toBe(2);
|
||||
const newTextId = textIds[1];
|
||||
const newText = page.locator(
|
||||
`affine-edgeless-text[data-block-id="${newTextId}"]`
|
||||
);
|
||||
await expect(newText).toBeVisible();
|
||||
const blocks = newText.locator('[data-block-id]');
|
||||
await expect(blocks.nth(0)).toContainText('hello');
|
||||
await expect(blocks.nth(1).locator('.resizable-img')).toBeVisible();
|
||||
await expect(blocks.nth(2)).toContainText('world');
|
||||
await expect(blocks.nth(3)).toContainText('code');
|
||||
});
|
||||
|
||||
test('pasting a note block from doc mode', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello world');
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await click(page, {
|
||||
x: 100,
|
||||
y: 100,
|
||||
});
|
||||
await pasteByKeyboard(page);
|
||||
|
||||
// not equal to noteId
|
||||
const noteIds = await getAllEdgelessNoteIds(page);
|
||||
expect(noteIds.length).toBe(2);
|
||||
|
||||
const newNoteId = noteIds[1];
|
||||
const newNote = page.locator(
|
||||
`affine-edgeless-note[data-block-id="${newNoteId}"]`
|
||||
);
|
||||
await expect(newNote).toBeVisible();
|
||||
const blocks = newNote.locator('[data-block-id]');
|
||||
await expect(blocks.nth(0)).toContainText('hello world');
|
||||
});
|
||||
});
|
||||
249
blocksuite/tests-legacy/e2e/edgeless/presentation.spec.ts
Normal file
249
blocksuite/tests-legacy/e2e/edgeless/presentation.spec.ts
Normal file
@@ -0,0 +1,249 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
assertEdgelessTool,
|
||||
createFrame,
|
||||
createNote,
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
enterPresentationMode,
|
||||
locatorPresentationToolbarButton,
|
||||
setEdgelessTool,
|
||||
Shape,
|
||||
toggleFramePanel,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
copyByKeyboard,
|
||||
pasteByKeyboard,
|
||||
pressEscape,
|
||||
selectAllBlocksByKeyboard,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import { waitNextFrame } from '../utils/actions/misc.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('presentation', () => {
|
||||
test('should render note when enter presentation mode', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await createNote(page, [300, 100], 'hello');
|
||||
|
||||
// Frame shape
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenViewCoords(page, [80, 80], [220, 220]);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
// Frame note
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenViewCoords(page, [240, 0], [800, 200]);
|
||||
|
||||
expect(await page.locator('affine-frame').count()).toBe(2);
|
||||
|
||||
await enterPresentationMode(page);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
const nextButton = locatorPresentationToolbarButton(page, 'next');
|
||||
await nextButton.click();
|
||||
const edgelessNote = page.locator('affine-edgeless-note');
|
||||
await expect(edgelessNote).toBeVisible();
|
||||
|
||||
const prevButton = locatorPresentationToolbarButton(page, 'previous');
|
||||
await prevButton.click();
|
||||
await expect(edgelessNote).toBeHidden();
|
||||
|
||||
await waitNextFrame(page, 300);
|
||||
await nextButton.click();
|
||||
await expect(edgelessNote).toBeVisible();
|
||||
});
|
||||
|
||||
test('should exit presentation mode when press escape', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createNote(page, [300, 100], 'hello');
|
||||
|
||||
// Frame note
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenViewCoords(page, [240, 0], [800, 200]);
|
||||
|
||||
expect(await page.locator('affine-frame').count()).toBe(1);
|
||||
|
||||
await enterPresentationMode(page);
|
||||
await waitNextFrame(page, 300);
|
||||
|
||||
await assertEdgelessTool(page, 'frameNavigator');
|
||||
const navigatorBlackBackground = page.locator(
|
||||
'.edgeless-navigator-black-background'
|
||||
);
|
||||
await expect(navigatorBlackBackground).toBeVisible();
|
||||
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
await assertEdgelessTool(page, 'default');
|
||||
await expect(navigatorBlackBackground).toBeHidden();
|
||||
});
|
||||
|
||||
test('should be able to adjust order of presentation in toolbar', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
|
||||
await createFrame(page, [100, 100], [100, 200]);
|
||||
await createFrame(page, [200, 100], [300, 200]);
|
||||
await createFrame(page, [300, 100], [400, 200]);
|
||||
await createFrame(page, [400, 100], [500, 200]);
|
||||
|
||||
await enterPresentationMode(page);
|
||||
|
||||
await page.locator('.edgeless-frame-order-button').click();
|
||||
const frameItems = page.locator(
|
||||
'edgeless-frame-order-menu .item.draggable'
|
||||
);
|
||||
const dragIndicators = page.locator(
|
||||
'edgeless-frame-order-menu .drag-indicator'
|
||||
);
|
||||
|
||||
await expect(frameItems).toHaveCount(4);
|
||||
await expect(frameItems.nth(0)).toHaveText('Frame 1');
|
||||
await expect(frameItems.nth(1)).toHaveText('Frame 2');
|
||||
await expect(frameItems.nth(2)).toHaveText('Frame 3');
|
||||
await expect(frameItems.nth(3)).toHaveText('Frame 4');
|
||||
|
||||
// 1 2 3 4
|
||||
await frameItems.nth(2).dragTo(dragIndicators.nth(0));
|
||||
// 3 1 2 4
|
||||
await frameItems.nth(3).dragTo(dragIndicators.nth(2));
|
||||
// 3 1 4 2
|
||||
await frameItems.nth(1).dragTo(dragIndicators.nth(3));
|
||||
// 3 4 1 2
|
||||
|
||||
await expect(frameItems).toHaveCount(4);
|
||||
await expect(frameItems.nth(0)).toHaveText('Frame 3');
|
||||
await expect(frameItems.nth(1)).toHaveText('Frame 4');
|
||||
await expect(frameItems.nth(2)).toHaveText('Frame 1');
|
||||
await expect(frameItems.nth(3)).toHaveText('Frame 2');
|
||||
|
||||
const currentFrame = page.locator('.edgeless-frame-navigator-title');
|
||||
const nextButton = locatorPresentationToolbarButton(page, 'next');
|
||||
|
||||
await expect(currentFrame).toHaveText('Frame 3');
|
||||
await nextButton.click();
|
||||
await expect(currentFrame).toHaveText('Frame 4');
|
||||
await nextButton.click();
|
||||
await expect(currentFrame).toHaveText('Frame 1');
|
||||
await nextButton.click();
|
||||
await expect(currentFrame).toHaveText('Frame 2');
|
||||
});
|
||||
|
||||
test('should be able to adjust order of presentation in frame panel', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
|
||||
await createFrame(page, [100, 100], [100, 200]);
|
||||
await createFrame(page, [200, 100], [300, 200]);
|
||||
await createFrame(page, [300, 100], [400, 200]);
|
||||
await createFrame(page, [400, 100], [500, 200]);
|
||||
|
||||
// await enterPresentationMode(page);
|
||||
|
||||
await toggleFramePanel(page);
|
||||
|
||||
// await page.locator('.edgeless-frame-order-button').click();
|
||||
const frameCards = page.locator('affine-frame-card .frame-card-body');
|
||||
const frameTitles = page.locator('affine-frame-card-title .card-title');
|
||||
|
||||
await expect(frameTitles).toHaveCount(4);
|
||||
await expect(frameTitles.nth(0)).toHaveText('Frame 1');
|
||||
await expect(frameTitles.nth(1)).toHaveText('Frame 2');
|
||||
await expect(frameTitles.nth(2)).toHaveText('Frame 3');
|
||||
await expect(frameTitles.nth(3)).toHaveText('Frame 4');
|
||||
|
||||
const drag = async (from: number, to: number) => {
|
||||
const startBBox = await frameCards.nth(from).boundingBox();
|
||||
expect(startBBox).not.toBeNull();
|
||||
if (startBBox === null) return;
|
||||
|
||||
const endBBox = await frameTitles.nth(to).boundingBox();
|
||||
expect(endBBox).not.toBeNull();
|
||||
if (endBBox === null) return;
|
||||
|
||||
await page.mouse.move(
|
||||
startBBox.x + startBBox.width / 2,
|
||||
startBBox.y + startBBox.height / 2
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(endBBox.x + endBBox.width / 2, endBBox.y, {
|
||||
steps: 2,
|
||||
});
|
||||
await page.mouse.up();
|
||||
};
|
||||
|
||||
// 1 2 3 4
|
||||
await drag(2, 0);
|
||||
// 3 1 2 4
|
||||
await drag(3, 2);
|
||||
// 3 1 4 2
|
||||
await drag(1, 3);
|
||||
// 3 4 1 2
|
||||
|
||||
await expect(frameTitles).toHaveCount(4);
|
||||
await expect(frameTitles.nth(0)).toHaveText('Frame 3');
|
||||
await expect(frameTitles.nth(1)).toHaveText('Frame 4');
|
||||
await expect(frameTitles.nth(2)).toHaveText('Frame 1');
|
||||
await expect(frameTitles.nth(3)).toHaveText('Frame 2');
|
||||
|
||||
await enterPresentationMode(page);
|
||||
await page.locator('.edgeless-frame-order-button').click();
|
||||
const frameItems = page.locator(
|
||||
'edgeless-frame-order-menu .item.draggable'
|
||||
);
|
||||
|
||||
await expect(frameItems).toHaveCount(4);
|
||||
await expect(frameItems.nth(0)).toHaveText('Frame 3');
|
||||
await expect(frameItems.nth(1)).toHaveText('Frame 4');
|
||||
await expect(frameItems.nth(2)).toHaveText('Frame 1');
|
||||
await expect(frameItems.nth(3)).toHaveText('Frame 2');
|
||||
|
||||
const currentFrame = page.locator('.edgeless-frame-navigator-title');
|
||||
const nextButton = locatorPresentationToolbarButton(page, 'next');
|
||||
|
||||
await expect(currentFrame).toHaveText('Frame 3');
|
||||
await nextButton.click();
|
||||
await expect(currentFrame).toHaveText('Frame 4');
|
||||
await nextButton.click();
|
||||
await expect(currentFrame).toHaveText('Frame 1');
|
||||
await nextButton.click();
|
||||
await expect(currentFrame).toHaveText('Frame 2');
|
||||
});
|
||||
|
||||
test('duplicate frames should keep the presentation orders', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
|
||||
await createFrame(page, [100, 100], [100, 200]);
|
||||
await createFrame(page, [200, 100], [300, 200]);
|
||||
await createFrame(page, [300, 100], [400, 200]);
|
||||
await createFrame(page, [400, 100], [500, 200]);
|
||||
|
||||
await selectAllBlocksByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
await pasteByKeyboard(page);
|
||||
|
||||
await enterPresentationMode(page);
|
||||
await page.locator('.edgeless-frame-order-button').click();
|
||||
const frameItems = page.locator(
|
||||
'edgeless-frame-order-menu .item.draggable'
|
||||
);
|
||||
|
||||
await expect(frameItems).toHaveCount(8);
|
||||
await expect(frameItems.nth(0)).toHaveText('Frame 1');
|
||||
await expect(frameItems.nth(1)).toHaveText('Frame 2');
|
||||
await expect(frameItems.nth(2)).toHaveText('Frame 3');
|
||||
await expect(frameItems.nth(3)).toHaveText('Frame 4');
|
||||
await expect(frameItems.nth(4)).toHaveText('Frame 1');
|
||||
await expect(frameItems.nth(5)).toHaveText('Frame 2');
|
||||
await expect(frameItems.nth(6)).toHaveText('Frame 3');
|
||||
await expect(frameItems.nth(7)).toHaveText('Frame 4');
|
||||
});
|
||||
});
|
||||
448
blocksuite/tests-legacy/e2e/edgeless/reordering.spec.ts
Normal file
448
blocksuite/tests-legacy/e2e/edgeless/reordering.spec.ts
Normal file
@@ -0,0 +1,448 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
createShapeElement,
|
||||
edgelessCommonSetup,
|
||||
getFirstContainerId,
|
||||
getSelectedBound,
|
||||
getSortedIds,
|
||||
initThreeOverlapFilledShapes,
|
||||
initThreeOverlapNotes,
|
||||
Shape,
|
||||
shiftClickView,
|
||||
switchEditorMode,
|
||||
triggerComponentToolbarAction,
|
||||
zoomResetByKeyboard,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
captureHistory,
|
||||
clickView,
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
redoByKeyboard,
|
||||
undoByKeyboard,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessSelectedRect,
|
||||
assertSelectedBound,
|
||||
} from '../utils/asserts.js';
|
||||
import {
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
} from '../utils/bs-alternative.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('reordering', () => {
|
||||
test.describe('group index', () => {
|
||||
let sortedIds: string[];
|
||||
|
||||
async function init(page: Page) {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 0], [200, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createShapeElement(page, [300, 0], [400, 100], Shape.Square);
|
||||
sortedIds = await getSortedIds(page);
|
||||
}
|
||||
|
||||
test('group', async ({ page }) => {
|
||||
await init(page);
|
||||
await clickView(page, [50, 50]);
|
||||
await shiftClickView(page, [150, 50]);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const groupId = await getFirstContainerId(page);
|
||||
const currentSortedIds = await getSortedIds(page);
|
||||
|
||||
expect(currentSortedIds).toEqual([
|
||||
...sortedIds.slice(2),
|
||||
groupId,
|
||||
...sortedIds.slice(0, 2),
|
||||
]);
|
||||
});
|
||||
|
||||
test('release from group', async ({ page }) => {
|
||||
await init(page);
|
||||
await clickView(page, [50, 50]);
|
||||
await shiftClickView(page, [150, 50]);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const groupId = await getFirstContainerId(page);
|
||||
await clickView(page, [50, 50]);
|
||||
await triggerComponentToolbarAction(page, 'releaseFromGroup');
|
||||
const currentSortedIds = await getSortedIds(page);
|
||||
const releasedShapeId = sortedIds[0];
|
||||
|
||||
expect(currentSortedIds).toEqual([
|
||||
...sortedIds.slice(2),
|
||||
groupId,
|
||||
sortedIds[1],
|
||||
releasedShapeId,
|
||||
]);
|
||||
});
|
||||
|
||||
test('ungroup', async ({ page }) => {
|
||||
await init(page);
|
||||
await clickView(page, [50, 50]);
|
||||
await shiftClickView(page, [150, 50]);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await triggerComponentToolbarAction(page, 'ungroup');
|
||||
const currentSortedIds = await getSortedIds(page);
|
||||
const ungroupedIds = [sortedIds[0], sortedIds[1]];
|
||||
|
||||
expect(currentSortedIds).toEqual([
|
||||
...sortedIds.filter(id => !ungroupedIds.includes(id)),
|
||||
...ungroupedIds,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('reordering shapes', () => {
|
||||
async function init(page: Page) {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await initThreeOverlapFilledShapes(page);
|
||||
await page.mouse.click(0, 0);
|
||||
}
|
||||
|
||||
test('bring to front', async ({ page }) => {
|
||||
await init(page);
|
||||
|
||||
// should be rect2
|
||||
await page.mouse.click(180, 180);
|
||||
await assertEdgelessSelectedRect(page, [160, 160, 100, 100]);
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be rect1
|
||||
await page.mouse.click(150, 150);
|
||||
await assertEdgelessSelectedRect(page, [130, 130, 100, 100]);
|
||||
|
||||
// should be rect0
|
||||
await page.mouse.click(110, 130);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
// bring rect0 to front
|
||||
await triggerComponentToolbarAction(page, 'bringToFront');
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be rect0
|
||||
await page.mouse.click(180, 180);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
});
|
||||
|
||||
test('bring forward', async ({ page }) => {
|
||||
await init(page);
|
||||
|
||||
// should be rect0
|
||||
await page.mouse.click(120, 120);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
// bring rect0 forward
|
||||
await triggerComponentToolbarAction(page, 'bringForward');
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be rect0
|
||||
await page.mouse.click(150, 150);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
});
|
||||
|
||||
test('send backward', async ({ page }) => {
|
||||
await init(page);
|
||||
|
||||
// should be rect2
|
||||
await page.mouse.click(180, 180);
|
||||
await assertEdgelessSelectedRect(page, [160, 160, 100, 100]);
|
||||
|
||||
// bring rect2 backward
|
||||
await triggerComponentToolbarAction(page, 'sendBackward');
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be rect1
|
||||
await page.mouse.click(180, 180);
|
||||
await assertEdgelessSelectedRect(page, [130, 130, 100, 100]);
|
||||
});
|
||||
|
||||
test('send to back', async ({ page }) => {
|
||||
await init(page);
|
||||
|
||||
// should be rect2
|
||||
await page.mouse.click(180, 180);
|
||||
await assertEdgelessSelectedRect(page, [160, 160, 100, 100]);
|
||||
|
||||
// bring rect2 to back
|
||||
await triggerComponentToolbarAction(page, 'sendToBack');
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be rect1
|
||||
await page.mouse.click(180, 180);
|
||||
await assertEdgelessSelectedRect(page, [130, 130, 100, 100]);
|
||||
|
||||
// send rect1 to back
|
||||
await triggerComponentToolbarAction(page, 'sendToBack');
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be rect0
|
||||
await page.mouse.click(180, 180);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
});
|
||||
|
||||
test('undo and redo', async ({ page }) => {
|
||||
await init(page);
|
||||
|
||||
// should be rect2
|
||||
await page.mouse.click(180, 180);
|
||||
await assertEdgelessSelectedRect(page, [160, 160, 100, 100]);
|
||||
|
||||
// send rect2 to back
|
||||
await triggerComponentToolbarAction(page, 'sendToBack');
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be rect1
|
||||
await page.mouse.click(180, 180);
|
||||
await assertEdgelessSelectedRect(page, [130, 130, 100, 100]);
|
||||
|
||||
// undo
|
||||
await undoByKeyboard(page);
|
||||
|
||||
// clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be rect2
|
||||
await page.mouse.click(180, 180);
|
||||
await assertEdgelessSelectedRect(page, [160, 160, 100, 100]);
|
||||
|
||||
// redo
|
||||
await redoByKeyboard(page);
|
||||
|
||||
// clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be rect2
|
||||
await page.mouse.click(180, 180);
|
||||
await assertEdgelessSelectedRect(page, [130, 130, 100, 100]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('reordering notes', () => {
|
||||
async function init(page: Page) {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await initThreeOverlapNotes(page);
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.click(0, 0);
|
||||
}
|
||||
|
||||
test('bring to front', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await initThreeOverlapNotes(page, 130, 190);
|
||||
await waitNextFrame(page);
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 100);
|
||||
// should be note2
|
||||
await page.mouse.click(180, 200);
|
||||
const bound = await getSelectedBound(page);
|
||||
|
||||
await assertSelectedBound(page, bound);
|
||||
|
||||
await clickView(page, [bound[0] - 15, bound[1] + 10]);
|
||||
bound[0] -= 30;
|
||||
await assertSelectedBound(page, bound);
|
||||
|
||||
await clickView(page, [bound[0] - 15, bound[1] + 10]);
|
||||
bound[0] -= 30;
|
||||
await assertSelectedBound(page, bound);
|
||||
|
||||
// bring note0 to front
|
||||
await triggerComponentToolbarAction(page, 'bringToFront');
|
||||
// clear
|
||||
await page.mouse.click(100, 50);
|
||||
// should be note0
|
||||
await clickView(page, [bound[0] + 40, bound[1] + 10]);
|
||||
await assertSelectedBound(page, bound);
|
||||
});
|
||||
|
||||
test('bring forward', async ({ page }) => {
|
||||
await init(page);
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be note0
|
||||
await page.mouse.click(120, 140);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
100,
|
||||
100,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
]);
|
||||
|
||||
// bring note0 forward
|
||||
await triggerComponentToolbarAction(page, 'bringForward');
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be rect0
|
||||
await page.mouse.click(150, 140);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
100,
|
||||
100,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
]);
|
||||
});
|
||||
|
||||
test('send backward', async ({ page }) => {
|
||||
await init(page);
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be note2
|
||||
await page.mouse.click(180, 140);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
160,
|
||||
100,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
]);
|
||||
|
||||
// bring note2 backward
|
||||
await triggerComponentToolbarAction(page, 'sendBackward');
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be note1
|
||||
await page.mouse.click(180, 140);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
130,
|
||||
100,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
]);
|
||||
});
|
||||
|
||||
test('send to back', async ({ page }) => {
|
||||
await init(page);
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be note2
|
||||
await page.mouse.click(180, 140);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
160,
|
||||
100,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
]);
|
||||
|
||||
// bring note2 to back
|
||||
await triggerComponentToolbarAction(page, 'sendToBack');
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be note1
|
||||
await page.mouse.click(180, 140);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
130,
|
||||
100,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
]);
|
||||
|
||||
// send note1 to back
|
||||
await triggerComponentToolbarAction(page, 'sendToBack');
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be note0
|
||||
await page.mouse.click(180, 140);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
100,
|
||||
100,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
]);
|
||||
});
|
||||
|
||||
test('undo and redo', async ({ page }) => {
|
||||
await init(page);
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be note2
|
||||
await page.mouse.click(180, 140);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
160,
|
||||
100,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
]);
|
||||
|
||||
await captureHistory(page);
|
||||
|
||||
// bring note2 to back
|
||||
await triggerComponentToolbarAction(page, 'sendToBack');
|
||||
|
||||
// click outside to clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
|
||||
// should be note1
|
||||
await page.mouse.click(180, 140);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
130,
|
||||
100,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
]);
|
||||
|
||||
// undo
|
||||
await undoByKeyboard(page);
|
||||
// clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
// should be note2
|
||||
await page.mouse.click(180, 140);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
160,
|
||||
100,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
]);
|
||||
|
||||
// redo
|
||||
await redoByKeyboard(page);
|
||||
// clear selection
|
||||
await page.mouse.click(50, 50);
|
||||
// should be note1
|
||||
await page.mouse.click(180, 140);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
130,
|
||||
100,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
199
blocksuite/tests-legacy/e2e/edgeless/resizing.spec.ts
Normal file
199
blocksuite/tests-legacy/e2e/edgeless/resizing.spec.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
import {
|
||||
switchEditorMode,
|
||||
zoomResetByKeyboard,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
addBasicBrushElement,
|
||||
addBasicRectShapeElement,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
resizeElementByHandle,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessSelectedReactCursor,
|
||||
assertEdgelessSelectedRect,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('resizing shapes and aspect ratio will be maintained', () => {
|
||||
test('positive adjustment', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicBrushElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await page.mouse.click(110, 110);
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 104, 104]);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 210, y: 110 },
|
||||
{ x: 310, y: 210 }
|
||||
);
|
||||
await page.mouse.click(220, 120);
|
||||
await assertEdgelessSelectedRect(page, [210, 110, 100, 100]);
|
||||
|
||||
await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 130 });
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 212, 112]);
|
||||
|
||||
await resizeElementByHandle(page, { x: 50, y: 50 });
|
||||
await assertEdgelessSelectedRect(page, [148, 124.19, 162, 85.81]);
|
||||
|
||||
await page.mouse.move(160, 160);
|
||||
await assertEdgelessSelectedRect(page, [148, 124.19, 162, 85.81]);
|
||||
|
||||
await page.mouse.move(260, 160);
|
||||
await assertEdgelessSelectedRect(page, [148, 124.19, 162, 85.81]);
|
||||
});
|
||||
|
||||
test('negative adjustment', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicBrushElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await page.mouse.click(110, 110);
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 104, 104]);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 210, y: 110 },
|
||||
{ x: 310, y: 210 }
|
||||
);
|
||||
await page.mouse.click(220, 120);
|
||||
await assertEdgelessSelectedRect(page, [210, 110, 100, 100]);
|
||||
|
||||
await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 130 });
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 212, 112]);
|
||||
|
||||
await resizeElementByHandle(page, { x: 400, y: 300 }, 'top-left', 30);
|
||||
await assertEdgelessSelectedRect(page, [310, 210, 356, 188]);
|
||||
|
||||
await page.mouse.move(450, 300);
|
||||
await assertEdgelessSelectedRect(page, [310, 210, 356, 188]);
|
||||
|
||||
await page.mouse.move(320, 220);
|
||||
await assertEdgelessSelectedRect(page, [310, 210, 356, 188]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('cursor style', () => {
|
||||
test('editor is aligned at the start of viewport', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 200, y: 200 },
|
||||
{ x: 300, y: 300 }
|
||||
);
|
||||
await page.mouse.click(250, 250);
|
||||
await assertEdgelessSelectedRect(page, [200, 200, 100, 100]);
|
||||
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top',
|
||||
cursor: 'ns-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'right',
|
||||
cursor: 'ew-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom',
|
||||
cursor: 'ns-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'left',
|
||||
cursor: 'ew-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top-left',
|
||||
cursor: 'nwse-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top-right',
|
||||
cursor: 'nesw-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom-left',
|
||||
cursor: 'nesw-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom-right',
|
||||
cursor: 'nwse-resize',
|
||||
});
|
||||
});
|
||||
|
||||
test('editor is not aligned at the start of viewport', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await page.addStyleTag({
|
||||
content: 'body { padding: 100px 150px; }',
|
||||
});
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 200, y: 200 },
|
||||
{ x: 300, y: 300 }
|
||||
);
|
||||
await page.mouse.click(250, 250);
|
||||
await assertEdgelessSelectedRect(page, [200, 200, 100, 100]);
|
||||
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top',
|
||||
cursor: 'ns-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'right',
|
||||
cursor: 'ew-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom',
|
||||
cursor: 'ns-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'left',
|
||||
cursor: 'ew-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top-left',
|
||||
cursor: 'nwse-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top-right',
|
||||
cursor: 'nesw-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom-left',
|
||||
cursor: 'nesw-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom-right',
|
||||
cursor: 'nwse-resize',
|
||||
});
|
||||
});
|
||||
});
|
||||
227
blocksuite/tests-legacy/e2e/edgeless/rotation.spec.ts
Normal file
227
blocksuite/tests-legacy/e2e/edgeless/rotation.spec.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import {
|
||||
addBasicRectShapeElement,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
resizeElementByHandle,
|
||||
rotateElementByHandle,
|
||||
switchEditorMode,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessSelectedReactCursor,
|
||||
assertEdgelessSelectedRect,
|
||||
assertEdgelessSelectedRectRotation,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('rotation', () => {
|
||||
test('angle adjustment by four corners', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
|
||||
await rotateElementByHandle(page, 45, 'top-left');
|
||||
await assertEdgelessSelectedRectRotation(page, 45);
|
||||
|
||||
await rotateElementByHandle(page, 45, 'top-right');
|
||||
await assertEdgelessSelectedRectRotation(page, 90);
|
||||
|
||||
await rotateElementByHandle(page, 45, 'bottom-right');
|
||||
await assertEdgelessSelectedRectRotation(page, 135);
|
||||
|
||||
await rotateElementByHandle(page, 45, 'bottom-left');
|
||||
await assertEdgelessSelectedRectRotation(page, 180);
|
||||
});
|
||||
|
||||
test('angle snap', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
|
||||
await page.keyboard.down('Shift');
|
||||
|
||||
await rotateElementByHandle(page, 5);
|
||||
await assertEdgelessSelectedRectRotation(page, 0);
|
||||
|
||||
await rotateElementByHandle(page, 10);
|
||||
await assertEdgelessSelectedRectRotation(page, 15);
|
||||
|
||||
await rotateElementByHandle(page, 10);
|
||||
await assertEdgelessSelectedRectRotation(page, 30);
|
||||
|
||||
await rotateElementByHandle(page, 10);
|
||||
await assertEdgelessSelectedRectRotation(page, 45);
|
||||
|
||||
await rotateElementByHandle(page, 5);
|
||||
await assertEdgelessSelectedRectRotation(page, 45);
|
||||
|
||||
await page.keyboard.up('Shift');
|
||||
});
|
||||
|
||||
test('single shape', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
await rotateElementByHandle(page, 45, 'top-right');
|
||||
await assertEdgelessSelectedRectRotation(page, 45);
|
||||
});
|
||||
|
||||
test('multiple shapes', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 200, y: 100 },
|
||||
{ x: 300, y: 200 }
|
||||
);
|
||||
|
||||
await dragBetweenCoords(page, { x: 90, y: 90 }, { x: 310, y: 110 });
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 200, 100]);
|
||||
|
||||
await rotateElementByHandle(page, 90, 'bottom-right');
|
||||
await assertEdgelessSelectedRectRotation(page, 0);
|
||||
await assertEdgelessSelectedRect(page, [150, 50, 100, 200]);
|
||||
});
|
||||
|
||||
test('combination with resizing', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
await rotateElementByHandle(page, 90, 'bottom-left');
|
||||
await assertEdgelessSelectedRectRotation(page, 90);
|
||||
|
||||
await resizeElementByHandle(page, { x: 10, y: -10 }, 'bottom-right');
|
||||
await assertEdgelessSelectedRect(page, [110, 100, 90, 90]);
|
||||
|
||||
await rotateElementByHandle(page, -90, 'bottom-right');
|
||||
await assertEdgelessSelectedRectRotation(page, 0);
|
||||
|
||||
await resizeElementByHandle(page, { x: 10, y: 10 }, 'bottom-right');
|
||||
await assertEdgelessSelectedRect(page, [110, 100, 100, 100]);
|
||||
});
|
||||
|
||||
test('combination with resizing for multiple shapes', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 200, y: 100 },
|
||||
{ x: 300, y: 200 }
|
||||
);
|
||||
|
||||
await dragBetweenCoords(page, { x: 90, y: 90 }, { x: 310, y: 110 });
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 200, 100]);
|
||||
|
||||
await rotateElementByHandle(page, 90, 'bottom-left');
|
||||
await assertEdgelessSelectedRectRotation(page, 0);
|
||||
await assertEdgelessSelectedRect(page, [150, 50, 100, 200]);
|
||||
|
||||
await resizeElementByHandle(page, { x: -10, y: -20 }, 'bottom-right');
|
||||
await assertEdgelessSelectedRect(page, [150, 50, 90, 180]);
|
||||
|
||||
await rotateElementByHandle(page, -90, 'bottom-right');
|
||||
await assertEdgelessSelectedRectRotation(page, 0);
|
||||
await assertEdgelessSelectedRect(page, [105, 95, 180, 90]);
|
||||
|
||||
await resizeElementByHandle(page, { x: 20, y: 10 }, 'bottom-right');
|
||||
await assertEdgelessSelectedRect(page, [105, 95, 200, 100]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('cursor style', () => {
|
||||
test('update resize cursor direction after rotating', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
|
||||
await rotateElementByHandle(page, 45, 'top-left');
|
||||
await assertEdgelessSelectedRectRotation(page, 45);
|
||||
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top',
|
||||
cursor: 'nesw-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'right',
|
||||
cursor: 'nwse-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom',
|
||||
cursor: 'nesw-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'left',
|
||||
cursor: 'nwse-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top-right',
|
||||
cursor: 'ew-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top-left',
|
||||
cursor: 'ns-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom-right',
|
||||
cursor: 'ns-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom-left',
|
||||
cursor: 'ew-resize',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import * as actions from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
addBasicConnectorElement,
|
||||
createConnectorElement,
|
||||
createShapeElement,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
Shape,
|
||||
switchEditorMode,
|
||||
toModelCoord,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test.describe('select multiple connectors', () => {
|
||||
test('should show single selection rect', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicConnectorElement(
|
||||
page,
|
||||
{ x: 100, y: 200 },
|
||||
{ x: 300, y: 200 }
|
||||
);
|
||||
await addBasicConnectorElement(
|
||||
page,
|
||||
{ x: 100, y: 230 },
|
||||
{ x: 300, y: 230 }
|
||||
);
|
||||
await addBasicConnectorElement(
|
||||
page,
|
||||
{ x: 100, y: 260 },
|
||||
{ x: 300, y: 260 }
|
||||
);
|
||||
|
||||
await dragBetweenCoords(page, { x: 50, y: 50 }, { x: 400, y: 290 });
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(
|
||||
await page
|
||||
.locator('.affine-edgeless-selected-rect')
|
||||
.locator('.element-handle')
|
||||
.count()
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('should disable resize when a connector is already connected', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
const start = await toModelCoord(page, [100, 0]);
|
||||
const end = await toModelCoord(page, [200, 100]);
|
||||
await createShapeElement(page, start, end, Shape.Diamond);
|
||||
const c1 = await toModelCoord(page, [200, 50]);
|
||||
const c2 = await toModelCoord(page, [450, 50]);
|
||||
await createConnectorElement(page, c1, c2);
|
||||
|
||||
await addBasicConnectorElement(
|
||||
page,
|
||||
{ x: 250, y: 200 },
|
||||
{ x: 450, y: 200 }
|
||||
);
|
||||
await addBasicConnectorElement(
|
||||
page,
|
||||
{ x: 250, y: 230 },
|
||||
{ x: 450, y: 230 }
|
||||
);
|
||||
await addBasicConnectorElement(
|
||||
page,
|
||||
{ x: 250, y: 260 },
|
||||
{ x: 450, y: 260 }
|
||||
);
|
||||
|
||||
await dragBetweenCoords(page, { x: 500, y: 20 }, { x: 400, y: 290 });
|
||||
await waitNextFrame(page);
|
||||
|
||||
const selectedRectLocalor = page.locator('.affine-edgeless-selected-rect');
|
||||
expect(await selectedRectLocalor.locator('.element-handle').count()).toBe(
|
||||
0
|
||||
);
|
||||
expect(
|
||||
await selectedRectLocalor.locator('.handle').locator('.resize').count()
|
||||
).toBe(0);
|
||||
});
|
||||
});
|
||||
265
blocksuite/tests-legacy/e2e/edgeless/selection/keyboard.spec.ts
Normal file
265
blocksuite/tests-legacy/e2e/edgeless/selection/keyboard.spec.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import * as actions from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
addNote,
|
||||
changeNoteDisplayModeWithId,
|
||||
setEdgelessTool,
|
||||
zoomResetByKeyboard,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
addBasicBrushElement,
|
||||
addBasicRectShapeElement,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
selectAllByKeyboard,
|
||||
switchEditorMode,
|
||||
} from '../../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessDraggingArea,
|
||||
assertEdgelessNonSelectedRect,
|
||||
assertEdgelessSelectedElementHandleCount,
|
||||
assertEdgelessSelectedRect,
|
||||
assertVisibleBlockCount,
|
||||
} from '../../utils/asserts.js';
|
||||
import { NoteDisplayMode } from '../../utils/bs-alternative.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test.describe('translation should constrain to cur axis when dragged with shift key', () => {
|
||||
test('constrain-x', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
|
||||
await page.mouse.move(110, 110);
|
||||
await page.mouse.down();
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
await page.keyboard.down('Shift');
|
||||
await page.mouse.move(110, 200); // constrain to y
|
||||
await page.mouse.move(300, 200); // constrain to x
|
||||
await assertEdgelessSelectedRect(page, [290, 100, 100, 100]); // y should remain same as constrained to x
|
||||
});
|
||||
|
||||
test('constrain-y', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
|
||||
await page.mouse.move(110, 110);
|
||||
await page.mouse.down();
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
await page.keyboard.down('Shift');
|
||||
await page.mouse.move(200, 110); // constrain to x
|
||||
await page.mouse.move(200, 300); // constrain to y
|
||||
await assertEdgelessSelectedRect(page, [100, 290, 100, 100]); // x should remain same as constrained to y
|
||||
});
|
||||
});
|
||||
|
||||
test('select multiple shapes and press "Escape" to cancel selection', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicBrushElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await page.mouse.click(110, 110);
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 104, 104]);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 210, y: 110 }, { x: 310, y: 210 });
|
||||
await page.mouse.click(220, 120);
|
||||
await assertEdgelessSelectedRect(page, [210, 110, 100, 100]);
|
||||
|
||||
// Select both shapes
|
||||
await dragBetweenCoords(page, { x: 90, y: 90 }, { x: 320, y: 220 });
|
||||
|
||||
// assert all shapes are selected
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 212, 112]);
|
||||
|
||||
// Press "Escape" to cancel the selection
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test('should move selection drag area when holding spaceBar', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
await setEdgelessTool(page, 'default');
|
||||
|
||||
// Click to start the initial dragging area
|
||||
await page.mouse.click(100, 100);
|
||||
|
||||
const initialX = 100,
|
||||
initialY = 100;
|
||||
const finalX = 300,
|
||||
finalY = 300;
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: initialX, y: initialY },
|
||||
{ x: finalX, y: finalY },
|
||||
{
|
||||
beforeMouseUp: async () => {
|
||||
await page.keyboard.down('Space');
|
||||
|
||||
const dx = 100,
|
||||
dy = 100;
|
||||
await page.mouse.move(finalX + dx, finalY + dy);
|
||||
await assertEdgelessDraggingArea(page, [
|
||||
initialX + dx,
|
||||
initialY + dy,
|
||||
// width and height should be same
|
||||
finalX - initialX,
|
||||
finalY - initialY,
|
||||
]);
|
||||
|
||||
await page.keyboard.up('Space');
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('selection drag-area start should be same when space is pressed again', async ({
|
||||
page,
|
||||
}) => {
|
||||
//? This test is to check whether there is any flicker or jump when using the space again in the same selection
|
||||
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
// Make the selection out side the rect and move the selection to the rect
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
// Make the selection not selecting the rect
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 },
|
||||
{
|
||||
beforeMouseUp: async () => {
|
||||
await page.keyboard.down('Space');
|
||||
// Move the selection over to the rect
|
||||
await page.mouse.move(300, 300);
|
||||
|
||||
let draggingArea = page.locator('.affine-edgeless-dragging-area');
|
||||
const firstBound = await draggingArea.boundingBox();
|
||||
|
||||
await page.keyboard.up('Space');
|
||||
|
||||
await page.mouse.move(400, 400);
|
||||
await page.keyboard.down('Space');
|
||||
|
||||
await page.mouse.move(410, 410);
|
||||
await page.mouse.move(400, 400);
|
||||
|
||||
draggingArea = page.locator('.affine-edgeless-dragging-area');
|
||||
const newBound = await draggingArea.boundingBox();
|
||||
|
||||
expect(firstBound).not.toBe(null);
|
||||
expect(newBound).not.toBe(null);
|
||||
|
||||
const { x: fx, y: fy } = firstBound!;
|
||||
const { x: nx, y: ny } = newBound!;
|
||||
|
||||
expect([fx, fy]).toStrictEqual([nx, ny]);
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('should be able to update selection dragging area after releasing space', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
await setEdgelessTool(page, 'default');
|
||||
|
||||
// Click to start the initial dragging area
|
||||
await page.mouse.click(100, 100);
|
||||
|
||||
const initialX = 100,
|
||||
initialY = 100;
|
||||
const finalX = 300,
|
||||
finalY = 300;
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: initialX, y: initialY },
|
||||
{ x: finalX, y: finalY },
|
||||
{
|
||||
beforeMouseUp: async () => {
|
||||
await page.keyboard.down('Space');
|
||||
|
||||
const dx = 100,
|
||||
dy = 100;
|
||||
|
||||
// Move the mouse to simulate dragging with spaceBar held
|
||||
await page.mouse.move(finalX + dx, finalY + dy);
|
||||
|
||||
await page.keyboard.up('Space');
|
||||
// scale after moving
|
||||
const dSx = 100;
|
||||
const dSy = 100;
|
||||
|
||||
await page.mouse.move(finalX + dx + dSx, finalY + dy + dSy);
|
||||
|
||||
await assertEdgelessDraggingArea(page, [
|
||||
initialX + dx,
|
||||
initialY + dy,
|
||||
// In the second scale it should scale by dS(.)
|
||||
finalX - initialX + dSx,
|
||||
finalY - initialY + dSy,
|
||||
]);
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('cmd+a should not select doc only note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
const note2 = await addNote(page, 'note2', 100, 200);
|
||||
await addNote(page, 'note3', 200, 300);
|
||||
await page.mouse.click(200, 500);
|
||||
// assert add note success, there should be 2 notes in edgeless page
|
||||
await assertVisibleBlockCount(page, 'edgeless-note', 3);
|
||||
|
||||
// change note display mode to doc only
|
||||
await changeNoteDisplayModeWithId(page, note2, NoteDisplayMode.DocOnly);
|
||||
// there should still be 2 notes in edgeless page
|
||||
await assertVisibleBlockCount(page, 'edgeless-note', 2);
|
||||
|
||||
// cmd+a should not select doc only note
|
||||
await selectAllByKeyboard(page);
|
||||
// there should be only 2 notes in selection
|
||||
await assertEdgelessSelectedElementHandleCount(page, 2);
|
||||
});
|
||||
466
blocksuite/tests-legacy/e2e/edgeless/selection/selection.spec.ts
Normal file
466
blocksuite/tests-legacy/e2e/edgeless/selection/selection.spec.ts
Normal file
@@ -0,0 +1,466 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import * as actions from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
getNoteBoundBoxInEdgeless,
|
||||
setEdgelessTool,
|
||||
switchEditorMode,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
addBasicBrushElement,
|
||||
addBasicRectShapeElement,
|
||||
click,
|
||||
clickInCenter,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
getBoundingRect,
|
||||
initEmptyEdgelessState,
|
||||
initThreeParagraphs,
|
||||
pressEnter,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockCount,
|
||||
assertEdgelessRemoteSelectedModelRect,
|
||||
assertEdgelessRemoteSelectedRect,
|
||||
assertEdgelessSelectedModelRect,
|
||||
assertEdgelessSelectedRect,
|
||||
assertSelectionInNote,
|
||||
} from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test('should update rect of selection when resizing viewport', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await actions.switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 130 });
|
||||
|
||||
const selectedRectClass = '.affine-edgeless-selected-rect';
|
||||
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
await actions.decreaseZoomLevel(page);
|
||||
await waitNextFrame(page);
|
||||
await actions.decreaseZoomLevel(page);
|
||||
await waitNextFrame(page);
|
||||
const selectedRectInZoom = await getBoundingRect(page, selectedRectClass);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
selectedRectInZoom.x,
|
||||
selectedRectInZoom.y,
|
||||
50,
|
||||
50,
|
||||
]);
|
||||
|
||||
await actions.switchEditorEmbedMode(page);
|
||||
await waitNextFrame(page);
|
||||
const selectedRectInEmbed = await getBoundingRect(page, selectedRectClass);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
selectedRectInEmbed.x,
|
||||
selectedRectInEmbed.y,
|
||||
50,
|
||||
50,
|
||||
]);
|
||||
|
||||
await actions.switchEditorEmbedMode(page);
|
||||
await actions.increaseZoomLevel(page);
|
||||
await waitNextFrame(page);
|
||||
await actions.increaseZoomLevel(page);
|
||||
await waitNextFrame(page);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
});
|
||||
|
||||
test('should update react of remote selection when resizing viewport', async ({
|
||||
context,
|
||||
page: pageA,
|
||||
}) => {
|
||||
const room = await enterPlaygroundRoom(pageA);
|
||||
await initEmptyEdgelessState(pageA);
|
||||
await actions.switchEditorMode(pageA);
|
||||
await actions.zoomResetByKeyboard(pageA);
|
||||
|
||||
const pageB = await context.newPage();
|
||||
await enterPlaygroundRoom(pageB, {
|
||||
room,
|
||||
noInit: true,
|
||||
});
|
||||
await actions.switchEditorMode(pageB);
|
||||
await actions.zoomResetByKeyboard(pageB);
|
||||
|
||||
await actions.createShapeElement(
|
||||
pageA,
|
||||
[0, 0],
|
||||
[100, 100],
|
||||
actions.Shape.Square
|
||||
);
|
||||
const point = await actions.toViewCoord(pageA, [50, 50]);
|
||||
await click(pageA, { x: point[0], y: point[1] });
|
||||
await click(pageB, { x: point[0], y: point[1] });
|
||||
|
||||
await assertEdgelessSelectedModelRect(pageB, [0, 0, 100, 100]);
|
||||
await assertEdgelessRemoteSelectedModelRect(pageB, [0, 0, 100, 100]);
|
||||
|
||||
// to 50%
|
||||
await actions.decreaseZoomLevel(pageB);
|
||||
await waitNextFrame(pageB);
|
||||
await actions.decreaseZoomLevel(pageB);
|
||||
await waitNextFrame(pageB);
|
||||
|
||||
const selectedRectInZoom = await getBoundingRect(
|
||||
pageB,
|
||||
'.affine-edgeless-selected-rect'
|
||||
);
|
||||
await assertEdgelessRemoteSelectedRect(pageB, [
|
||||
selectedRectInZoom.x,
|
||||
selectedRectInZoom.y,
|
||||
50,
|
||||
50,
|
||||
]);
|
||||
});
|
||||
|
||||
test('select multiple shapes and translate', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicBrushElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await page.mouse.click(110, 110);
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 104, 104]);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 210, y: 110 }, { x: 310, y: 210 });
|
||||
await page.mouse.click(220, 120);
|
||||
await assertEdgelessSelectedRect(page, [210, 110, 100, 100]);
|
||||
|
||||
await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 130 });
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 212, 112]);
|
||||
|
||||
await dragBetweenCoords(page, { x: 120, y: 120 }, { x: 150, y: 150 });
|
||||
await assertEdgelessSelectedRect(page, [125, 128, 212, 112]);
|
||||
|
||||
await page.mouse.click(160, 160);
|
||||
await assertEdgelessSelectedRect(page, [125, 128, 104, 104]);
|
||||
|
||||
await page.mouse.click(250, 150);
|
||||
await assertEdgelessSelectedRect(page, [237, 140, 100, 100]);
|
||||
});
|
||||
|
||||
test('selection box of shape element sync on fast dragging', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await dragBetweenCoords(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await setEdgelessTool(page, 'default');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: 110, y: 110 },
|
||||
{ x: 660, y: 460 },
|
||||
{ click: true }
|
||||
);
|
||||
|
||||
await assertEdgelessSelectedRect(page, [650, 446, 100, 100]);
|
||||
});
|
||||
|
||||
test('when the selection is always a note, it should remain in an active state', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const ids = await initEmptyEdgelessState(page);
|
||||
await initThreeParagraphs(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
const bound = await getNoteBoundBoxInEdgeless(page, ids.noteId);
|
||||
|
||||
await setEdgelessTool(page, 'note');
|
||||
const newNoteX = bound.x;
|
||||
const newNoteY = bound.y + bound.height + 100;
|
||||
// add text
|
||||
await page.mouse.click(newNoteX, newNoteY);
|
||||
await waitNextFrame(page);
|
||||
await page.keyboard.type('hello');
|
||||
await pressEnter(page);
|
||||
// should wait for inline editor update and resizeObserver callback
|
||||
await waitNextFrame(page);
|
||||
// assert add text success
|
||||
await assertBlockCount(page, 'edgeless-note', 2);
|
||||
|
||||
await clickInCenter(page, bound);
|
||||
await clickInCenter(page, bound);
|
||||
await waitNextFrame(page);
|
||||
await assertSelectionInNote(page, ids.noteId, 'affine-edgeless-note');
|
||||
});
|
||||
|
||||
test('should auto panning when selection rectangle reaches viewport edges', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 200, y: 100 }, { x: 300, y: 200 });
|
||||
await page.mouse.click(210, 110);
|
||||
await assertEdgelessSelectedRect(page, [200, 100, 100, 100]);
|
||||
|
||||
const selectedRectClass = '.affine-edgeless-selected-rect';
|
||||
|
||||
// Panning to the left
|
||||
await setEdgelessTool(page, 'pan');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 600,
|
||||
y: 200,
|
||||
},
|
||||
{
|
||||
x: 200,
|
||||
y: 200,
|
||||
}
|
||||
);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.click(210, 110);
|
||||
let selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(300);
|
||||
await expect(selectedRect).toBeHidden();
|
||||
// Click to start selection and hold the mouse to trigger auto panning to the left
|
||||
await page.mouse.move(210, 110);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(0, 210, { steps: 20 });
|
||||
await page.waitForTimeout(500);
|
||||
await page.mouse.up();
|
||||
|
||||
// Expect to select the shape element
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(300);
|
||||
await expect(selectedRect).toBeVisible();
|
||||
|
||||
// Panning to the top
|
||||
await page.mouse.click(400, 600);
|
||||
await setEdgelessTool(page, 'pan');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 400,
|
||||
y: 600,
|
||||
},
|
||||
{
|
||||
x: 400,
|
||||
y: 100,
|
||||
}
|
||||
);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.click(600, 100);
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(300);
|
||||
await expect(selectedRect).toBeHidden();
|
||||
// Click to start selection and hold the mouse to trigger auto panning to the top
|
||||
await page.mouse.move(600, 100);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(400, 0, { steps: 20 });
|
||||
await page.waitForTimeout(500);
|
||||
await page.mouse.up();
|
||||
|
||||
// Expect to select the empty note
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(300);
|
||||
await expect(selectedRect).toBeVisible();
|
||||
|
||||
// Panning to the right
|
||||
await page.mouse.click(100, 600);
|
||||
await setEdgelessTool(page, 'pan');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 20,
|
||||
y: 600,
|
||||
},
|
||||
{
|
||||
x: 1000,
|
||||
y: 600,
|
||||
}
|
||||
);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.click(800, 600);
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(100);
|
||||
await expect(selectedRect).toBeHidden();
|
||||
// Click to start selection and hold the mouse to trigger auto panning to the right
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 800,
|
||||
y: 600,
|
||||
},
|
||||
{
|
||||
x: 1000,
|
||||
y: 200,
|
||||
},
|
||||
{
|
||||
beforeMouseUp: async () => {
|
||||
await page.waitForTimeout(600);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Expect to select the empty note
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(300);
|
||||
await expect(selectedRect).toBeVisible();
|
||||
|
||||
// Panning to the bottom
|
||||
await page.mouse.click(400, 100);
|
||||
await setEdgelessTool(page, 'pan');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 400,
|
||||
y: 100,
|
||||
},
|
||||
{
|
||||
x: 400,
|
||||
y: 850,
|
||||
},
|
||||
{
|
||||
click: true,
|
||||
}
|
||||
);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await waitNextFrame(page, 500);
|
||||
await page.mouse.click(400, 400);
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(100);
|
||||
await expect(selectedRect).toBeHidden();
|
||||
|
||||
// Click to start selection and hold the mouse to trigger auto panning to the right
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 800,
|
||||
y: 300,
|
||||
},
|
||||
{
|
||||
x: 820,
|
||||
y: 1150,
|
||||
},
|
||||
{
|
||||
click: true,
|
||||
beforeMouseUp: async () => {
|
||||
await page.waitForTimeout(500);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Expect to select the empty note
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(300);
|
||||
await expect(selectedRect).toBeVisible();
|
||||
});
|
||||
|
||||
test('should also update dragging area when viewport changes', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
// Panning to the top
|
||||
await page.mouse.click(400, 600);
|
||||
await setEdgelessTool(page, 'pan');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 400,
|
||||
y: 600,
|
||||
},
|
||||
{
|
||||
x: 400,
|
||||
y: 100,
|
||||
}
|
||||
);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.click(200, 300);
|
||||
|
||||
const selectedRectClass = '.affine-edgeless-selected-rect';
|
||||
let selectedRect = page.locator(selectedRectClass);
|
||||
await expect(selectedRect).toBeHidden();
|
||||
// set up initial dragging area
|
||||
await page.mouse.move(200, 300);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(600, 200, { steps: 20 });
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// wheel the viewport to the top
|
||||
await page.mouse.wheel(0, -300);
|
||||
await page.waitForTimeout(300);
|
||||
await page.mouse.up();
|
||||
|
||||
// Expect to select the empty note
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(300);
|
||||
await expect(selectedRect).toBeVisible();
|
||||
await page.waitForTimeout(300);
|
||||
});
|
||||
|
||||
test('should select shapes while moving selection', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
|
||||
// Make the selection out side the rect and move the selection to the rect
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
// Make the selection not selecting the rect
|
||||
{ x: 70, y: 70 },
|
||||
{ x: 90, y: 90 },
|
||||
{
|
||||
beforeMouseUp: async () => {
|
||||
await page.keyboard.down('Space');
|
||||
// Move the selection over to the rect
|
||||
await page.mouse.move(120, 120);
|
||||
await page.keyboard.up('Space');
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
await addBasicBrushElement(page, { x: 210, y: 100 }, { x: 310, y: 300 });
|
||||
await page.mouse.click(211, 101);
|
||||
|
||||
// Make a wide selection and move it to select both of the shapes
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
// Make the selection above the spaces
|
||||
{ x: 70, y: 70 },
|
||||
{ x: 400, y: 90 },
|
||||
{
|
||||
beforeMouseUp: async () => {
|
||||
await page.keyboard.down('Space');
|
||||
// Move the selection over both of the shapes
|
||||
await page.mouse.move(400, 120);
|
||||
await page.keyboard.up('Space');
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 98, 212, 204]);
|
||||
});
|
||||
739
blocksuite/tests-legacy/e2e/edgeless/shape.spec.ts
Normal file
739
blocksuite/tests-legacy/e2e/edgeless/shape.spec.ts
Normal file
@@ -0,0 +1,739 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
import { lightThemeV2 } from '@toeverything/theme/v2';
|
||||
|
||||
import {
|
||||
assertEdgelessTool,
|
||||
changeShapeFillColor,
|
||||
changeShapeFillColorToTransparent,
|
||||
changeShapeStrokeColor,
|
||||
changeShapeStrokeStyle,
|
||||
changeShapeStrokeWidth,
|
||||
changeShapeStyle,
|
||||
clickComponentToolbarMoreMenuButton,
|
||||
getEdgelessSelectedRect,
|
||||
locatorComponentToolbar,
|
||||
locatorEdgelessToolButton,
|
||||
locatorShapeStrokeStyleButton,
|
||||
openComponentToolbarMoreMenu,
|
||||
pickColorAtPoints,
|
||||
resizeElementByHandle,
|
||||
setEdgelessTool,
|
||||
switchEditorMode,
|
||||
triggerComponentToolbarAction,
|
||||
zoomResetByKeyboard,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
addBasicBrushElement,
|
||||
addBasicRectShapeElement,
|
||||
copyByKeyboard,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyEdgelessState,
|
||||
pasteByKeyboard,
|
||||
pressEscape,
|
||||
type,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessCanvasText,
|
||||
assertEdgelessColorSameWithHexColor,
|
||||
assertEdgelessNonSelectedRect,
|
||||
assertEdgelessSelectedRect,
|
||||
assertExists,
|
||||
assertRichTexts,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('add shape', () => {
|
||||
test('without holding shift key', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start0 = { x: 100, y: 100 };
|
||||
const end0 = { x: 150, y: 200 };
|
||||
await addBasicRectShapeElement(page, start0, end0);
|
||||
|
||||
await assertEdgelessTool(page, 'default');
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 50, 100]);
|
||||
|
||||
const start1 = { x: 100, y: 100 };
|
||||
const end1 = { x: 200, y: 150 };
|
||||
await addBasicRectShapeElement(page, start1, end1);
|
||||
|
||||
await assertEdgelessTool(page, 'default');
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 50]);
|
||||
});
|
||||
|
||||
test('with holding shift key', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await page.keyboard.down('Shift');
|
||||
|
||||
const start0 = { x: 100, y: 100 };
|
||||
const end0 = { x: 150, y: 200 };
|
||||
await addBasicRectShapeElement(page, start0, end0);
|
||||
|
||||
await page.keyboard.up('Shift');
|
||||
|
||||
await assertEdgelessTool(page, 'default');
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
await page.keyboard.down('Shift');
|
||||
|
||||
const start1 = { x: 100, y: 100 };
|
||||
const end1 = { x: 200, y: 150 };
|
||||
await addBasicRectShapeElement(page, start1, end1);
|
||||
|
||||
await assertEdgelessTool(page, 'default');
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
});
|
||||
test('with holding space bar', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start0 = { x: 100, y: 100 };
|
||||
const end0 = { x: 200, y: 200 };
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await dragBetweenCoords(page, start0, end0, {
|
||||
steps: 50,
|
||||
beforeMouseUp: async () => {
|
||||
// move the shape
|
||||
await page.keyboard.down('Space');
|
||||
await page.mouse.move(300, 300);
|
||||
await page.keyboard.up('Space');
|
||||
|
||||
await page.mouse.move(500, 600);
|
||||
},
|
||||
});
|
||||
|
||||
await assertEdgelessSelectedRect(page, [200, 200, 300, 400]);
|
||||
});
|
||||
|
||||
test('with holding space bar + shift', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start0 = { x: 100, y: 100 };
|
||||
const end0 = { x: 200, y: 200 };
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await page.keyboard.down('Shift');
|
||||
await dragBetweenCoords(page, start0, end0, {
|
||||
steps: 50,
|
||||
beforeMouseUp: async () => {
|
||||
// move the shape
|
||||
await page.keyboard.down('Space');
|
||||
await page.mouse.move(300, 300);
|
||||
await page.keyboard.up('Space');
|
||||
|
||||
await page.mouse.move(500, 600);
|
||||
},
|
||||
});
|
||||
|
||||
await assertEdgelessSelectedRect(page, [200, 200, 400, 400]);
|
||||
});
|
||||
});
|
||||
|
||||
test('delete shape by component-toolbar', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await addBasicBrushElement(page, start, end);
|
||||
|
||||
await page.mouse.click(110, 110);
|
||||
await openComponentToolbarMoreMenu(page);
|
||||
await clickComponentToolbarMoreMenuButton(page, 'delete');
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
//FIXME: need a way to test hand-drawn-like style
|
||||
test.skip('change shape fill color', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const rect = {
|
||||
start: { x: 100, y: 100 },
|
||||
end: { x: 200, y: 200 },
|
||||
};
|
||||
await addBasicRectShapeElement(page, rect.start, rect.end);
|
||||
|
||||
await page.mouse.click(rect.start.x + 5, rect.start.y + 5);
|
||||
await triggerComponentToolbarAction(page, 'changeShapeFillColor');
|
||||
await changeShapeFillColor(page, 'MediumGrey');
|
||||
await page.waitForTimeout(50);
|
||||
const [picked] = await pickColorAtPoints(page, [
|
||||
[rect.start.x + 20, rect.start.y + 20],
|
||||
]);
|
||||
|
||||
await assertEdgelessColorSameWithHexColor(
|
||||
page,
|
||||
lightThemeV2['edgeless/palette/medium/greyMedium'],
|
||||
picked
|
||||
);
|
||||
});
|
||||
|
||||
test('change shape stroke color', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const rect = {
|
||||
start: { x: 100, y: 100 },
|
||||
end: { x: 200, y: 200 },
|
||||
};
|
||||
await addBasicRectShapeElement(page, rect.start, rect.end);
|
||||
|
||||
await page.mouse.click(rect.start.x + 5, rect.start.y + 5);
|
||||
await triggerComponentToolbarAction(page, 'changeShapeStrokeColor');
|
||||
await changeShapeStrokeColor(page, 'HeavyYellow');
|
||||
await page.waitForTimeout(50);
|
||||
const [picked] = await pickColorAtPoints(page, [
|
||||
[rect.start.x + 1, rect.start.y + 1],
|
||||
]);
|
||||
|
||||
await assertEdgelessColorSameWithHexColor(
|
||||
page,
|
||||
lightThemeV2['edgeless/palette/heavy/yellow'],
|
||||
picked
|
||||
);
|
||||
});
|
||||
|
||||
test('the tooltip of shape tool button should be hidden when the shape menu is shown', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const shapeTool = await locatorEdgelessToolButton(page, 'shape');
|
||||
const shapeToolBox = await shapeTool.boundingBox();
|
||||
const tooltip = page.locator('.affine-tooltip');
|
||||
|
||||
assertExists(shapeToolBox);
|
||||
|
||||
await page.mouse.move(shapeToolBox.x + 2, shapeToolBox.y + 2);
|
||||
await expect(tooltip).toBeVisible();
|
||||
|
||||
await page.mouse.click(shapeToolBox.x + 2, shapeToolBox.y + 2);
|
||||
await expect(tooltip).toBeHidden();
|
||||
|
||||
await page.mouse.click(shapeToolBox.x + 2, shapeToolBox.y + 2);
|
||||
await expect(tooltip).toBeVisible();
|
||||
});
|
||||
|
||||
test('delete shape block by keyboard', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await dragBetweenCoords(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
|
||||
await setEdgelessTool(page, 'default');
|
||||
const startPoint = await page.evaluate(() => {
|
||||
const hitbox = document.querySelector('[data-block-id="3"]');
|
||||
if (!hitbox) {
|
||||
throw new Error('hitbox is null');
|
||||
}
|
||||
const rect = hitbox.getBoundingClientRect();
|
||||
if (rect == null) {
|
||||
throw new Error('rect is null');
|
||||
}
|
||||
return {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
};
|
||||
});
|
||||
await page.mouse.click(startPoint.x + 2, startPoint.y + 2);
|
||||
await waitNextFrame(page);
|
||||
await page.keyboard.press('Backspace');
|
||||
const exist = await page.evaluate(() => {
|
||||
return document.querySelector('[data-block-id="3"]') != null;
|
||||
});
|
||||
expect(exist).toBe(false);
|
||||
});
|
||||
|
||||
test('edgeless toolbar shape menu shows up and close normally', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const toolbarLocator = page.locator('.edgeless-toolbar-container');
|
||||
await expect(toolbarLocator).toBeVisible();
|
||||
|
||||
const shapeTool = await locatorEdgelessToolButton(page, 'shape');
|
||||
const shapeToolBox = await shapeTool.boundingBox();
|
||||
|
||||
assertExists(shapeToolBox);
|
||||
|
||||
await page.mouse.click(shapeToolBox.x + 2, shapeToolBox.y + 2);
|
||||
|
||||
const shapeMenu = page.locator('edgeless-shape-menu');
|
||||
await expect(shapeMenu).toBeVisible();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.mouse.click(shapeToolBox.x + 2, shapeToolBox.y + 2);
|
||||
await page.waitForTimeout(500);
|
||||
await expect(shapeMenu).toBeHidden();
|
||||
});
|
||||
|
||||
test('hovering on shape should not have effect on underlying block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
const block = page.locator('affine-edgeless-note');
|
||||
const blockBox = await block.boundingBox();
|
||||
if (blockBox === null) throw new Error('Unexpected box value: box is null');
|
||||
|
||||
const { x, y } = blockBox;
|
||||
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await dragBetweenCoords(page, { x, y }, { x: x + 100, y: y + 100 });
|
||||
await setEdgelessTool(page, 'default');
|
||||
|
||||
await page.mouse.click(x + 10, y + 10);
|
||||
await assertEdgelessSelectedRect(page, [x, y, 100, 100]);
|
||||
});
|
||||
|
||||
test('shape element should not move when the selected state is inactive', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await dragBetweenCoords(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await setEdgelessTool(page, 'default');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: 50, y: 50 },
|
||||
{ x: 110, y: 110 },
|
||||
{ steps: 2 }
|
||||
);
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
});
|
||||
|
||||
test('change shape stroke width', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start = { x: 100, y: 150 };
|
||||
const end = { x: 200, y: 250 };
|
||||
await addBasicRectShapeElement(page, start, end);
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y + 5);
|
||||
await triggerComponentToolbarAction(page, 'changeShapeStrokeColor');
|
||||
await changeShapeStrokeColor(page, 'MediumMagenta');
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeShapeStrokeStyles');
|
||||
await changeShapeStrokeWidth(page);
|
||||
await page.mouse.click(start.x + 5, start.y + 5);
|
||||
await assertEdgelessSelectedRect(page, [100, 150, 100, 100]);
|
||||
|
||||
await waitNextFrame(page);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeShapeStrokeStyles');
|
||||
});
|
||||
|
||||
test('change shape stroke style', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start = { x: 100, y: 150 };
|
||||
const end = { x: 200, y: 250 };
|
||||
await addBasicRectShapeElement(page, start, end);
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y + 5);
|
||||
await triggerComponentToolbarAction(page, 'changeShapeStrokeColor');
|
||||
await changeShapeStrokeColor(page, 'MediumBlue');
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeShapeStrokeStyles');
|
||||
await changeShapeStrokeStyle(page, 'dash');
|
||||
await waitNextFrame(page);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeShapeStrokeStyles');
|
||||
const activeButton = locatorShapeStrokeStyleButton(page, 'dash');
|
||||
const className = await activeButton.evaluate(ele => ele.className);
|
||||
expect(className.includes(' active')).toBeTruthy();
|
||||
|
||||
const pickedColor = await pickColorAtPoints(page, [[start.x + 20, start.y]]);
|
||||
expect(pickedColor[0]).toBe('#000000');
|
||||
});
|
||||
|
||||
test('click to add shape', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await waitNextFrame(page, 500);
|
||||
|
||||
await page.mouse.move(400, 400);
|
||||
await page.mouse.move(200, 200);
|
||||
await page.mouse.click(200, 200, { button: 'left', delay: 300 });
|
||||
|
||||
await assertEdgelessTool(page, 'default');
|
||||
await assertEdgelessSelectedRect(page, [200, 200, 100, 100]);
|
||||
});
|
||||
|
||||
test('dbclick to add text in shape', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await waitNextFrame(page, 500);
|
||||
|
||||
await page.mouse.click(200, 150);
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.dblclick(250, 200);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
await assertEdgelessCanvasText(page, 'hello');
|
||||
await assertEdgelessTool(page, 'default');
|
||||
|
||||
// test select, copy, paste
|
||||
const select = async () => {
|
||||
await page.mouse.move(245, 205);
|
||||
await page.mouse.down();
|
||||
|
||||
await page.mouse.move(245, 205);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(262, 205, {
|
||||
steps: 10,
|
||||
});
|
||||
await page.mouse.up();
|
||||
};
|
||||
await select();
|
||||
// h|ell|o
|
||||
await waitNextFrame(page);
|
||||
await copyByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
// FIXME(@Flrande): this is a workaround, we should keep selection
|
||||
await select();
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'ddd', 50);
|
||||
await waitNextFrame(page);
|
||||
await assertEdgelessCanvasText(page, 'hdddo');
|
||||
|
||||
await pasteByKeyboard(page);
|
||||
await assertEdgelessCanvasText(page, 'hdddello');
|
||||
});
|
||||
|
||||
test('should show selected rect after exiting editing by pressing Escape', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await waitNextFrame(page, 500);
|
||||
|
||||
await dragBetweenCoords(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.dblclick(150, 150);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
await assertEdgelessCanvasText(page, 'hello');
|
||||
|
||||
await pressEscape(page);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
});
|
||||
|
||||
test('auto wrap text in shape', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await waitNextFrame(page, 500);
|
||||
|
||||
await page.mouse.click(200, 150);
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.dblclick(250, 200);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await type(page, 'aaaa\nbbbb\n');
|
||||
await assertEdgelessCanvasText(page, 'aaaa\nbbbb\n');
|
||||
await assertEdgelessTool(page, 'default');
|
||||
|
||||
// blur to finish typing
|
||||
await page.mouse.click(150, 150);
|
||||
// select shape
|
||||
await page.mouse.click(200, 150);
|
||||
// the height of shape should be increased because of \n
|
||||
let selectedRect = await getEdgelessSelectedRect(page);
|
||||
let lastWidth = selectedRect.width;
|
||||
let lastHeight = selectedRect.height;
|
||||
|
||||
await page.mouse.dblclick(250, 200);
|
||||
await waitNextFrame(page);
|
||||
// type long text
|
||||
await type(page, '\ncccccccc');
|
||||
await assertEdgelessCanvasText(page, 'aaaa\nbbbb\ncccccccc');
|
||||
|
||||
// blur to finish typing
|
||||
await page.mouse.click(150, 150);
|
||||
// select shape
|
||||
await page.mouse.click(200, 150);
|
||||
// the height of shape should be increased because of long text
|
||||
// cccccccc -- wrap --> cccccc\ncc
|
||||
selectedRect = await getEdgelessSelectedRect(page);
|
||||
expect(selectedRect.width).toBe(lastWidth);
|
||||
expect(selectedRect.height).toBeGreaterThan(lastHeight);
|
||||
lastWidth = selectedRect.width;
|
||||
lastHeight = selectedRect.height;
|
||||
|
||||
// try to decrease height
|
||||
await resizeElementByHandle(page, { x: 0, y: -50 }, 'bottom-right');
|
||||
// you can't decrease height because of min height to fit text
|
||||
selectedRect = await getEdgelessSelectedRect(page);
|
||||
expect(selectedRect.width).toBe(lastWidth);
|
||||
expect(selectedRect.height).toBeGreaterThanOrEqual(lastHeight);
|
||||
lastWidth = selectedRect.width;
|
||||
lastHeight = selectedRect.height;
|
||||
|
||||
// increase width to make text not wrap
|
||||
await resizeElementByHandle(page, { x: 50, y: -10 }, 'bottom-right');
|
||||
// the height of shape should be decreased because of long text not wrap
|
||||
selectedRect = await getEdgelessSelectedRect(page);
|
||||
expect(selectedRect.width).toBeGreaterThan(lastWidth);
|
||||
expect(selectedRect.height).toBeLessThan(lastHeight);
|
||||
|
||||
// try to decrease width
|
||||
await resizeElementByHandle(page, { x: -140, y: 0 }, 'bottom-right');
|
||||
// you can't decrease width after text can't wrap (each line just has 1 char)
|
||||
await assertEdgelessSelectedRect(page, [200, 150, 52, 404]);
|
||||
});
|
||||
|
||||
test('change shape style', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start = { x: 100, y: 150 };
|
||||
const end = { x: 200, y: 250 };
|
||||
await addBasicRectShapeElement(page, start, end);
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y + 5);
|
||||
await triggerComponentToolbarAction(page, 'changeShapeStyle');
|
||||
await changeShapeStyle(page, 'general');
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y + 5);
|
||||
await triggerComponentToolbarAction(page, 'changeShapeStrokeColor');
|
||||
const color = 'LightPurple';
|
||||
await changeShapeStrokeColor(page, color);
|
||||
await page.waitForTimeout(50);
|
||||
const [picked] = await pickColorAtPoints(page, [[start.x + 1, start.y + 1]]);
|
||||
|
||||
await assertEdgelessColorSameWithHexColor(
|
||||
page,
|
||||
lightThemeV2['edgeless/palette/light/purpleLight'],
|
||||
picked
|
||||
);
|
||||
});
|
||||
|
||||
test('shape adds text by button', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await waitNextFrame(page, 500);
|
||||
|
||||
await page.mouse.click(200, 150);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'addText');
|
||||
await type(page, 'hello');
|
||||
await assertEdgelessCanvasText(page, 'hello');
|
||||
});
|
||||
|
||||
test('should reset shape text when text is empty', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await waitNextFrame(page, 500);
|
||||
|
||||
await page.mouse.click(200, 150);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'addText');
|
||||
await type(page, ' a ');
|
||||
await assertEdgelessCanvasText(page, ' a ');
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.click(200, 150);
|
||||
|
||||
const addTextBtn = locatorComponentToolbar(page).getByRole('button', {
|
||||
name: 'Add text',
|
||||
});
|
||||
await expect(addTextBtn).toBeHidden();
|
||||
|
||||
await page.mouse.dblclick(250, 200);
|
||||
await assertEdgelessCanvasText(page, 'a');
|
||||
|
||||
await page.keyboard.press('Backspace');
|
||||
await assertEdgelessCanvasText(page, '');
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.click(200, 150);
|
||||
|
||||
await expect(addTextBtn).toBeVisible();
|
||||
});
|
||||
|
||||
test.describe('shape hit test', () => {
|
||||
async function addTransparentRect(
|
||||
page: Page,
|
||||
start: { x: number; y: number },
|
||||
end: { x: number; y: number }
|
||||
) {
|
||||
const rect = {
|
||||
start,
|
||||
end,
|
||||
};
|
||||
await addBasicRectShapeElement(page, rect.start, rect.end);
|
||||
|
||||
await page.mouse.click(rect.start.x + 5, rect.start.y + 5);
|
||||
await triggerComponentToolbarAction(page, 'changeShapeFillColor');
|
||||
await changeShapeFillColorToTransparent(page);
|
||||
await page.waitForTimeout(50);
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await page.evaluate(() => {
|
||||
window.doc
|
||||
.get(window.$blocksuite.blocks.FeatureFlagService)
|
||||
.setFlag('enable_edgeless_text', false);
|
||||
});
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
});
|
||||
|
||||
const rect = {
|
||||
start: { x: 100, y: 100 },
|
||||
end: { x: 200, y: 200 },
|
||||
};
|
||||
|
||||
test('can select hollow shape by clicking center area', async ({ page }) => {
|
||||
await addTransparentRect(page, rect.start, rect.end);
|
||||
await page.mouse.click(rect.start.x - 20, rect.start.y - 20);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
|
||||
await page.mouse.click(rect.start.x + 50, rect.start.y + 50);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
});
|
||||
|
||||
test('double click can add text in shape hollow area', async ({ page }) => {
|
||||
await addTransparentRect(page, rect.start, rect.end);
|
||||
await page.mouse.click(rect.start.x - 20, rect.start.y - 20);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
|
||||
await assertEdgelessTool(page, 'default');
|
||||
await page.mouse.dblclick(rect.start.x + 20, rect.start.y + 20);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
await assertEdgelessCanvasText(page, 'hello');
|
||||
});
|
||||
|
||||
// FIXME(@flrande): This is broken by recent changes
|
||||
// In Playwright, we can't add text in shape hollow area
|
||||
test.fixme(
|
||||
'using text tool to add text in shape hollow area',
|
||||
async ({ page }) => {
|
||||
await addTransparentRect(page, rect.start, rect.end);
|
||||
await page.mouse.click(rect.start.x - 20, rect.start.y - 20);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
|
||||
await assertEdgelessTool(page, 'default');
|
||||
await setEdgelessTool(page, 'text');
|
||||
await page.mouse.click(rect.start.x + 50, rect.start.y + 50);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
await assertEdgelessCanvasText(page, 'hello');
|
||||
}
|
||||
);
|
||||
|
||||
test('should enter edit mode when double-clicking a text area in a shape with a transparent background', async ({
|
||||
page,
|
||||
}) => {
|
||||
await addTransparentRect(page, rect.start, rect.end);
|
||||
await page.mouse.click(rect.start.x - 20, rect.start.y - 20);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
|
||||
await assertEdgelessTool(page, 'default');
|
||||
await page.mouse.dblclick(rect.start.x + 50, rect.start.y + 50);
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'hello');
|
||||
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
const textAlignBtn = locatorComponentToolbar(page).getByRole('button', {
|
||||
name: 'Alignment',
|
||||
});
|
||||
await textAlignBtn.click();
|
||||
|
||||
await page
|
||||
.locator('edgeless-align-panel')
|
||||
.getByRole('button', { name: 'Left' })
|
||||
.click();
|
||||
|
||||
// creates an edgeless-text
|
||||
await page.mouse.dblclick(rect.start.x + 80, rect.start.y + 20);
|
||||
await waitNextFrame(page);
|
||||
await page.locator('edgeless-text-editor').isVisible();
|
||||
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
// enters edit mode
|
||||
await page.mouse.dblclick(rect.start.x + 20, rect.start.y + 50);
|
||||
await page.locator('edgeless-shape-text-editor').isVisible();
|
||||
await type(page, ' world');
|
||||
await assertEdgelessCanvasText(page, 'hello world');
|
||||
});
|
||||
});
|
||||
366
blocksuite/tests-legacy/e2e/edgeless/shortcut.spec.ts
Normal file
366
blocksuite/tests-legacy/e2e/edgeless/shortcut.spec.ts
Normal file
@@ -0,0 +1,366 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addBasicRectShapeElement,
|
||||
assertEdgelessShapeType,
|
||||
createShapeElement,
|
||||
edgelessCommonSetup,
|
||||
getEdgelessSelectedRect,
|
||||
getZoomLevel,
|
||||
locatorEdgelessToolButton,
|
||||
setEdgelessTool,
|
||||
type ShapeName,
|
||||
switchEditorMode,
|
||||
zoomFitByKeyboard,
|
||||
zoomInByKeyboard,
|
||||
zoomOutByKeyboard,
|
||||
zoomResetByKeyboard,
|
||||
zoomToSelection,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
clickView,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyEdgelessState,
|
||||
pressBackspace,
|
||||
pressEscape,
|
||||
pressForwardDelete,
|
||||
selectAllByKeyboard,
|
||||
selectNoteInEdgeless,
|
||||
type,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockCount,
|
||||
assertEdgelessNonSelectedRect,
|
||||
assertEdgelessSelectedModelRect,
|
||||
assertEdgelessSelectedRect,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('shortcut', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await page.mouse.click(100, 100);
|
||||
|
||||
// text is removed temporarily
|
||||
// await page.keyboard.press('t');
|
||||
// const textButton = await locatorEdgelessToolButton(page, 'text');
|
||||
// await expect(textButton).toHaveAttribute('active', '');
|
||||
|
||||
await page.keyboard.press('s');
|
||||
const shapeButton = await locatorEdgelessToolButton(page, 'shape');
|
||||
await expect(shapeButton).toHaveAttribute('active', '');
|
||||
|
||||
await page.keyboard.press('p');
|
||||
const penButton = await locatorEdgelessToolButton(page, 'brush');
|
||||
await expect(penButton).toHaveAttribute('active', '');
|
||||
|
||||
await page.keyboard.press('h');
|
||||
const panButton = await locatorEdgelessToolButton(page, 'pan');
|
||||
await expect(panButton).toHaveAttribute('active', '');
|
||||
|
||||
await page.keyboard.press('c');
|
||||
const connectorButton = await locatorEdgelessToolButton(page, 'connector');
|
||||
await expect(connectorButton).toHaveAttribute('active', '');
|
||||
|
||||
// await page.keyboard.press('l');
|
||||
// const lassoButton = await locatorEdgelessToolButton(page, 'lasso');
|
||||
// await expect(lassoButton).toHaveAttribute('active', '');
|
||||
});
|
||||
|
||||
test.skip('toggle lasso tool modes', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await page.mouse.click(100, 100);
|
||||
|
||||
const lassoButton = await locatorEdgelessToolButton(page, 'lasso', false);
|
||||
|
||||
const isLassoMode = async (type: 'freehand' | 'polygonal') => {
|
||||
const classes = (await lassoButton.getAttribute('class'))?.split(' ') ?? [];
|
||||
return classes.includes(type);
|
||||
};
|
||||
|
||||
await page.keyboard.press('Shift+l');
|
||||
expect(await isLassoMode('freehand')).toBe(true);
|
||||
|
||||
await page.keyboard.press('Shift+l');
|
||||
expect(await isLassoMode('polygonal')).toBe(true);
|
||||
|
||||
await page.keyboard.press('Shift+l');
|
||||
expect(await isLassoMode('freehand')).toBe(true);
|
||||
});
|
||||
|
||||
test('toggle shapes shortcut', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await page.mouse.click(100, 100);
|
||||
await setEdgelessTool(page, 'shape');
|
||||
|
||||
const shapesInOrder = [
|
||||
'ellipse',
|
||||
'diamond',
|
||||
'triangle',
|
||||
'roundedRect',
|
||||
'rect',
|
||||
'ellipse',
|
||||
'diamond',
|
||||
'triangle',
|
||||
'roundedRect',
|
||||
] as ShapeName[];
|
||||
for (const shape of shapesInOrder) {
|
||||
await page.keyboard.press('Shift+s');
|
||||
await assertEdgelessShapeType(page, shape);
|
||||
}
|
||||
});
|
||||
|
||||
test('should not switch shapes in editing', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await waitNextFrame(page);
|
||||
await assertEdgelessShapeType(page, 'rect');
|
||||
|
||||
await page.mouse.click(200, 150);
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.dblclick(250, 200);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
await page.keyboard.press('Shift+s');
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 200);
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await assertEdgelessShapeType(page, 'rect');
|
||||
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.dblclick(250, 200);
|
||||
await waitNextFrame(page);
|
||||
await page.keyboard.press('Shift+S');
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page);
|
||||
await waitNextFrame(page, 200);
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await assertEdgelessShapeType(page, 'rect');
|
||||
});
|
||||
|
||||
test('pressing the ESC key will return to the default state', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await addBasicRectShapeElement(page, start, end);
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y + 5);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
await pressEscape(page);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test.describe('zooming', () => {
|
||||
test('zoom fit to screen', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
const start = { x: 0, y: 0 };
|
||||
const end = { x: 900, y: 200 };
|
||||
await addBasicRectShapeElement(page, start, end);
|
||||
await zoomFitByKeyboard(page);
|
||||
|
||||
const zoom = await getZoomLevel(page);
|
||||
expect(zoom).not.toBe(100);
|
||||
});
|
||||
test('zoom out', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await clickView(page, [0, 0]);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await zoomOutByKeyboard(page);
|
||||
|
||||
let zoom = await getZoomLevel(page);
|
||||
expect(zoom).toBe(75);
|
||||
|
||||
await zoomOutByKeyboard(page);
|
||||
|
||||
zoom = await getZoomLevel(page);
|
||||
expect(zoom).toBe(50);
|
||||
});
|
||||
test('zoom reset', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await clickView(page, [0, 0]);
|
||||
await zoomResetByKeyboard(page);
|
||||
let zoom = await getZoomLevel(page);
|
||||
expect(zoom).toBe(100);
|
||||
|
||||
await zoomOutByKeyboard(page);
|
||||
|
||||
zoom = await getZoomLevel(page);
|
||||
expect(zoom).toBe(75);
|
||||
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
zoom = await getZoomLevel(page);
|
||||
expect(zoom).toBe(100);
|
||||
});
|
||||
test('zoom in', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await clickView(page, [0, 0]);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await zoomInByKeyboard(page);
|
||||
|
||||
let zoom = await getZoomLevel(page);
|
||||
expect(zoom).toBe(125);
|
||||
|
||||
await zoomInByKeyboard(page);
|
||||
|
||||
zoom = await getZoomLevel(page);
|
||||
expect(zoom).toBe(150);
|
||||
});
|
||||
|
||||
test('zoom to selection', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomToSelection(page);
|
||||
|
||||
const start = { x: 0, y: 0 };
|
||||
const end = { x: 900, y: 200 };
|
||||
await addBasicRectShapeElement(page, start, end);
|
||||
await page.keyboard.down('Space');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 200,
|
||||
y: 200,
|
||||
},
|
||||
{
|
||||
x: 200 - 50,
|
||||
y: 200 - 50,
|
||||
}
|
||||
);
|
||||
await page.keyboard.up('Space');
|
||||
|
||||
await zoomFitByKeyboard(page);
|
||||
const shapeContained = await page.evaluate(() => {
|
||||
const edgelessBlock = document.querySelector('affine-edgeless-root');
|
||||
if (!edgelessBlock) {
|
||||
throw new Error('edgeless block not found');
|
||||
}
|
||||
|
||||
const gfx = edgelessBlock.gfx;
|
||||
const element = gfx.selection.selectedElements[0];
|
||||
|
||||
return gfx.viewport.viewportBounds.contains(element.elementBound);
|
||||
});
|
||||
|
||||
expect(shapeContained).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('cmd + A should select all elements by default', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100]);
|
||||
await createShapeElement(page, [100, 0], [200, 100]);
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 200, 100]);
|
||||
});
|
||||
|
||||
test('cmd + A should not fire inside active note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await switchEditorMode(page);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
// second click become active
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await selectAllByKeyboard(page);
|
||||
|
||||
// should not have selected rect
|
||||
let error = null;
|
||||
try {
|
||||
await getEdgelessSelectedRect(page);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error).not.toBeNull();
|
||||
});
|
||||
|
||||
test.describe('delete', () => {
|
||||
test('do not delete element when active', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await switchEditorMode(page);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
const box1 = await getEdgelessSelectedRect(page);
|
||||
await page.mouse.click(box1.x + 10, box1.y + 10);
|
||||
await pressBackspace(page);
|
||||
await assertBlockCount(page, 'edgeless-note', 1);
|
||||
await pressForwardDelete(page);
|
||||
await assertBlockCount(page, 'edgeless-note', 1);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Arrow Keys should move selection', () => {
|
||||
test('with shift increment by 10px', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
|
||||
await page.keyboard.down('Shift');
|
||||
|
||||
for (let i = 0; i < 10; i++) await page.keyboard.press('ArrowLeft');
|
||||
for (let i = 0; i < 10; i++) await page.keyboard.press('ArrowDown');
|
||||
|
||||
await assertEdgelessSelectedRect(page, [0, 200, 100, 100]);
|
||||
});
|
||||
|
||||
test('without shift increment by 1px', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
|
||||
for (let i = 0; i < 10; i++) await page.keyboard.press('ArrowRight');
|
||||
for (let i = 0; i < 10; i++) await page.keyboard.press('ArrowUp');
|
||||
|
||||
await assertEdgelessSelectedRect(page, [110, 90, 100, 100]);
|
||||
});
|
||||
});
|
||||
45
blocksuite/tests-legacy/e2e/edgeless/snap.spec.ts
Normal file
45
blocksuite/tests-legacy/e2e/edgeless/snap.spec.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { undoByClick } from '../utils/actions/click.js';
|
||||
import {
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
Shape,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import { waitNextFrame } from '../utils/actions/misc.js';
|
||||
import { assertSelectedBound } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('snap', () => {
|
||||
test('snap', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [300, 0], [300 + 100, 100], Shape.Square);
|
||||
|
||||
await assertSelectedBound(page, [300, 0, 100, 100]);
|
||||
|
||||
await dragBetweenViewCoords(page, [300 + 5, 50], [300 + 5, 50 + 5]);
|
||||
await assertSelectedBound(page, [300, 5, 100, 100]);
|
||||
|
||||
await undoByClick(page);
|
||||
await dragBetweenViewCoords(page, [300 + 5, 50], [300 + 5, 50 + 3]);
|
||||
await assertSelectedBound(page, [300, 0, 100, 100]);
|
||||
});
|
||||
|
||||
test('snapDistribute', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [300, 0], [300 + 100, 100], Shape.Square);
|
||||
await createShapeElement(page, [144, 0], [144 + 100, 100], Shape.Square);
|
||||
|
||||
await assertSelectedBound(page, [144, 0, 100, 100]);
|
||||
await dragBetweenViewCoords(
|
||||
page,
|
||||
[144 + 100 - 9, 100 - 9],
|
||||
[144 + 100 - 9 + 3, 100 - 9]
|
||||
);
|
||||
await assertSelectedBound(page, [150, 0, 100, 100]);
|
||||
await waitNextFrame(page);
|
||||
});
|
||||
});
|
||||
317
blocksuite/tests-legacy/e2e/edgeless/text.spec.ts
Normal file
317
blocksuite/tests-legacy/e2e/edgeless/text.spec.ts
Normal file
@@ -0,0 +1,317 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
assertEdgelessTool,
|
||||
enterPlaygroundRoom,
|
||||
getEdgelessSelectedRect,
|
||||
initEmptyEdgelessState,
|
||||
pressArrowLeft,
|
||||
pressEnter,
|
||||
setEdgelessTool,
|
||||
SHORT_KEY,
|
||||
switchEditorMode,
|
||||
type,
|
||||
waitForInlineEditorStateUpdated,
|
||||
waitNextFrame,
|
||||
zoomResetByKeyboard,
|
||||
} from '../utils/actions/index.js';
|
||||
import { getLinkedDocPopover } from '../utils/actions/linked-doc.js';
|
||||
import { assertEdgelessCanvasText } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
async function assertTextFont(page: Page, font: string) {
|
||||
const fontButton = page.getByRole('button', {
|
||||
name: /^Font$/,
|
||||
});
|
||||
const fontPanel = page.locator('edgeless-font-family-panel');
|
||||
const isFontPanelShow = await fontPanel.isVisible();
|
||||
if (!isFontPanelShow) {
|
||||
if (!(await fontButton.isVisible()))
|
||||
throw new Error('edgeless change text toolbar is not visible');
|
||||
|
||||
await fontButton.click();
|
||||
}
|
||||
|
||||
const button = fontPanel.locator(`[data-font="${font}"]`);
|
||||
await expect(button.locator('.active-mode-color[active]')).toBeVisible();
|
||||
}
|
||||
|
||||
test.describe('edgeless canvas text', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await page.evaluate(() => {
|
||||
window.doc
|
||||
.get(window.$blocksuite.blocks.FeatureFlagService)
|
||||
.setFlag('enable_edgeless_text', false);
|
||||
});
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
});
|
||||
|
||||
test('add text element in default mode', async ({ page }) => {
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.dblclick(130, 140);
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
await assertEdgelessCanvasText(page, 'hello');
|
||||
await assertEdgelessTool(page, 'default');
|
||||
|
||||
await page.mouse.click(120, 140);
|
||||
|
||||
expect(await page.locator('edgeless-text-editor').count()).toBe(0);
|
||||
|
||||
await page.mouse.dblclick(145, 155);
|
||||
await waitNextFrame(page);
|
||||
await page.locator('edgeless-text-editor').waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
await type(page, 'hello');
|
||||
await assertEdgelessCanvasText(page, 'hhelloello');
|
||||
|
||||
await pressArrowLeft(page, 5);
|
||||
await type(page, 'ddd\n');
|
||||
await assertEdgelessCanvasText(page, 'hddd\nhelloello');
|
||||
});
|
||||
|
||||
test('should not trigger linked doc popover in canvas text', async ({
|
||||
page,
|
||||
}) => {
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.dblclick(130, 140);
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await type(page, '@');
|
||||
const { linkedDocPopover } = getLinkedDocPopover(page);
|
||||
await expect(linkedDocPopover).not.toBeVisible();
|
||||
await pressEnter(page);
|
||||
await assertEdgelessCanvasText(page, '@\n');
|
||||
});
|
||||
|
||||
// it's also a little flaky
|
||||
test('add text element in text mode', async ({ page }) => {
|
||||
await page.mouse.dblclick(130, 140);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
await assertEdgelessCanvasText(page, 'hello');
|
||||
await assertEdgelessTool(page, 'default');
|
||||
|
||||
await page.mouse.click(120, 140);
|
||||
|
||||
expect(await page.locator('edgeless-text-editor').count()).toBe(0);
|
||||
|
||||
await page.mouse.dblclick(145, 145);
|
||||
|
||||
await page.locator('edgeless-text-editor').waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
await type(page, 'hello');
|
||||
await page.waitForTimeout(100);
|
||||
await assertEdgelessCanvasText(page, 'hhelloello');
|
||||
|
||||
await page.mouse.click(145, 155);
|
||||
await type(page, 'ddd\n');
|
||||
await assertEdgelessCanvasText(page, 'hddd\nhelloello');
|
||||
});
|
||||
|
||||
test('copy and paste', async ({ page }) => {
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.dblclick(130, 140);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
await assertEdgelessCanvasText(page, 'hello');
|
||||
await assertEdgelessTool(page, 'default');
|
||||
|
||||
await page.mouse.move(145, 155);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(170, 155, {
|
||||
steps: 10,
|
||||
});
|
||||
await page.mouse.up();
|
||||
// h|ell|o
|
||||
await waitNextFrame(page, 200);
|
||||
await page.keyboard.press(`${SHORT_KEY}+c`);
|
||||
|
||||
await waitNextFrame(page, 200);
|
||||
await type(page, 'ddd', 100);
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessCanvasText(page, 'hdddo');
|
||||
|
||||
await page.keyboard.press(`${SHORT_KEY}+v`);
|
||||
await assertEdgelessCanvasText(page, 'hdddello');
|
||||
});
|
||||
|
||||
test('normalize text element rect after change its font', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.mouse.dblclick(200, 200);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await type(page, 'aaa\nbbbbbbbb\n\ncc');
|
||||
await assertEdgelessCanvasText(page, 'aaa\nbbbbbbbb\n\ncc');
|
||||
await assertEdgelessTool(page, 'default');
|
||||
await page.mouse.click(10, 100);
|
||||
|
||||
await page.mouse.click(220, 210);
|
||||
await waitNextFrame(page);
|
||||
let { width: lastWidth, height: lastHeight } =
|
||||
await getEdgelessSelectedRect(page);
|
||||
const fontButton = page.getByRole('button', { name: /^Font$/ });
|
||||
await fontButton.click();
|
||||
|
||||
// Default is Inter
|
||||
await assertTextFont(page, 'Inter');
|
||||
const kalamTextFont = page.getByText('Kalam');
|
||||
await kalamTextFont.click();
|
||||
await waitNextFrame(page);
|
||||
let selectedRect = await getEdgelessSelectedRect(page);
|
||||
expect(selectedRect.width).not.toEqual(lastWidth);
|
||||
expect(selectedRect.height).not.toEqual(lastHeight);
|
||||
|
||||
lastWidth = selectedRect.width;
|
||||
lastHeight = selectedRect.height;
|
||||
await fontButton.click();
|
||||
await assertTextFont(page, 'Kalam');
|
||||
const InterTextFont = page.getByText('Inter');
|
||||
await InterTextFont.click();
|
||||
await waitNextFrame(page);
|
||||
selectedRect = await getEdgelessSelectedRect(page);
|
||||
expect(selectedRect.width).not.toEqual(lastWidth);
|
||||
expect(selectedRect.height).not.toEqual(lastHeight);
|
||||
});
|
||||
|
||||
test('auto wrap text by dragging left and right edge', async ({ page }) => {
|
||||
await zoomResetByKeyboard(page);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.dblclick(130, 140);
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await type(page, 'hellohello');
|
||||
await assertEdgelessCanvasText(page, 'hellohello');
|
||||
await assertEdgelessTool(page, 'default');
|
||||
|
||||
// quit edit mode
|
||||
await page.mouse.click(120, 140);
|
||||
|
||||
// select text element
|
||||
await page.mouse.click(150, 140);
|
||||
await waitNextFrame(page);
|
||||
|
||||
// should exit selected rect and record last width and height, then compare them
|
||||
let selectedRect = await getEdgelessSelectedRect(page);
|
||||
let lastWidth = selectedRect.width;
|
||||
let lastHeight = selectedRect.height;
|
||||
|
||||
// move cursor to the right edge and drag it to resize the width of text element
|
||||
await page.mouse.move(130 + lastWidth, 160);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(130 + lastWidth / 2, 160, {
|
||||
steps: 10,
|
||||
});
|
||||
await page.mouse.up();
|
||||
|
||||
// the text should be wrapped, so check the width and height of text element
|
||||
selectedRect = await getEdgelessSelectedRect(page);
|
||||
expect(selectedRect.width).toBeLessThan(lastWidth);
|
||||
expect(selectedRect.height).toBeGreaterThan(lastHeight);
|
||||
|
||||
await page.mouse.dblclick(140, 160);
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
await waitNextFrame(page);
|
||||
await assertEdgelessCanvasText(page, 'hellohello');
|
||||
|
||||
// quit edit mode
|
||||
await page.mouse.click(120, 140);
|
||||
|
||||
// select text element
|
||||
await page.mouse.click(150, 140);
|
||||
await waitNextFrame(page);
|
||||
|
||||
// check selected rect and record the last width and height
|
||||
selectedRect = await getEdgelessSelectedRect(page);
|
||||
lastWidth = selectedRect.width;
|
||||
lastHeight = selectedRect.height;
|
||||
// move cursor to the left edge and drag it to resize the width of text element
|
||||
await page.mouse.move(130, 160);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(60, 160, {
|
||||
steps: 10,
|
||||
});
|
||||
await page.mouse.up();
|
||||
|
||||
// the text should be unwrapped, check the width and height of text element
|
||||
selectedRect = await getEdgelessSelectedRect(page);
|
||||
expect(selectedRect.width).toBeGreaterThan(lastWidth);
|
||||
expect(selectedRect.height).toBeLessThan(lastHeight);
|
||||
|
||||
await page.mouse.dblclick(100, 160);
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
await waitNextFrame(page);
|
||||
await assertEdgelessCanvasText(page, 'hellohello');
|
||||
});
|
||||
|
||||
test('text element should have maxWidth after adjusting width by dragging left or right edge', async ({
|
||||
page,
|
||||
}) => {
|
||||
await zoomResetByKeyboard(page);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.dblclick(130, 140);
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await type(page, 'hellohello');
|
||||
await assertEdgelessCanvasText(page, 'hellohello');
|
||||
await assertEdgelessTool(page, 'default');
|
||||
|
||||
// quit edit mode
|
||||
await page.mouse.click(120, 140);
|
||||
|
||||
// select text element
|
||||
await page.mouse.click(150, 140);
|
||||
await waitNextFrame(page);
|
||||
|
||||
let selectedRect = await getEdgelessSelectedRect(page);
|
||||
let lastWidth = selectedRect.width;
|
||||
let lastHeight = selectedRect.height;
|
||||
|
||||
// move cursor to the right edge and drag it to resize the width of text element
|
||||
await page.mouse.move(130 + lastWidth, 160);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(130 + lastWidth / 2, 160, {
|
||||
steps: 10,
|
||||
});
|
||||
await page.mouse.up();
|
||||
|
||||
// the text should be wrapped, so check the width and height of text element
|
||||
selectedRect = await getEdgelessSelectedRect(page);
|
||||
expect(selectedRect.width).toBeLessThan(lastWidth);
|
||||
expect(selectedRect.height).toBeGreaterThan(lastHeight);
|
||||
lastWidth = selectedRect.width;
|
||||
lastHeight = selectedRect.height;
|
||||
|
||||
// enter edit mode
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.dblclick(140, 180);
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'hello');
|
||||
await assertEdgelessCanvasText(page, 'hellohellohello');
|
||||
|
||||
// quit edit mode
|
||||
await page.mouse.click(120, 140);
|
||||
|
||||
// select text element
|
||||
await page.mouse.click(150, 140);
|
||||
await waitNextFrame(page);
|
||||
|
||||
// after input, the width of the text element should be the same as before, but the height should be changed
|
||||
selectedRect = await getEdgelessSelectedRect(page);
|
||||
expect(selectedRect.width).toBeCloseTo(Math.round(lastWidth));
|
||||
expect(selectedRect.height).toBeGreaterThan(lastHeight);
|
||||
});
|
||||
});
|
||||
261
blocksuite/tests-legacy/e2e/embed-synced-doc.spec.ts
Normal file
261
blocksuite/tests-legacy/e2e/embed-synced-doc.spec.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import type { DatabaseBlockModel } from '@blocksuite/affine-model';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import { switchEditorMode } from './utils/actions/edgeless.js';
|
||||
import { getLinkedDocPopover } from './utils/actions/linked-doc.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyEdgelessState,
|
||||
initEmptyParagraphState,
|
||||
waitNextFrame,
|
||||
} from './utils/actions/misc.js';
|
||||
import { test } from './utils/playwright.js';
|
||||
|
||||
test.describe('Embed synced doc', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
});
|
||||
|
||||
async function createAndConvertToEmbedSyncedDoc(page: Page) {
|
||||
const { createLinkedDoc } = getLinkedDocPopover(page);
|
||||
const linkedDoc = await createLinkedDoc('page1');
|
||||
const lickedDocBox = await linkedDoc.boundingBox();
|
||||
assertExists(lickedDocBox);
|
||||
await page.mouse.move(
|
||||
lickedDocBox.x + lickedDocBox.width / 2,
|
||||
lickedDocBox.y + lickedDocBox.height / 2
|
||||
);
|
||||
|
||||
await waitNextFrame(page, 200);
|
||||
const referencePopup = page.locator('.affine-reference-popover-container');
|
||||
await expect(referencePopup).toBeVisible();
|
||||
|
||||
const switchButton = page.getByRole('button', { name: 'Switch view' });
|
||||
await switchButton.click();
|
||||
|
||||
const embedSyncedDocBtn = page.getByRole('button', { name: 'Embed view' });
|
||||
await expect(embedSyncedDocBtn).toBeVisible();
|
||||
|
||||
await embedSyncedDocBtn.click();
|
||||
await waitNextFrame(page, 200);
|
||||
|
||||
const embedSyncedBlock = page.locator('affine-embed-synced-doc-block');
|
||||
expect(await embedSyncedBlock.count()).toBe(1);
|
||||
}
|
||||
|
||||
test('can change linked doc to embed synced doc', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await createAndConvertToEmbedSyncedDoc(page);
|
||||
});
|
||||
|
||||
test('can change embed synced doc to card view', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await createAndConvertToEmbedSyncedDoc(page);
|
||||
|
||||
const syncedDoc = page.locator(`affine-embed-synced-doc-block`);
|
||||
const syncedDocBox = await syncedDoc.boundingBox();
|
||||
assertExists(syncedDocBox);
|
||||
await page.mouse.click(
|
||||
syncedDocBox.x + syncedDocBox.width / 2,
|
||||
syncedDocBox.y + syncedDocBox.height / 2
|
||||
);
|
||||
|
||||
await waitNextFrame(page, 200);
|
||||
const toolbar = page.locator('.embed-card-toolbar');
|
||||
await expect(toolbar).toBeVisible();
|
||||
|
||||
const switchBtn = toolbar.getByRole('button', { name: 'Switch view' });
|
||||
await expect(switchBtn).toBeVisible();
|
||||
|
||||
await switchBtn.click();
|
||||
await waitNextFrame(page, 200);
|
||||
|
||||
const cardBtn = toolbar.getByRole('button', { name: 'Card view' });
|
||||
await cardBtn.click();
|
||||
await waitNextFrame(page, 200);
|
||||
|
||||
const embedSyncedBlock = page.locator('affine-embed-linked-doc-block');
|
||||
expect(await embedSyncedBlock.count()).toBe(1);
|
||||
});
|
||||
|
||||
test.fixme(
|
||||
'drag embed synced doc to whiteboard should fit in height',
|
||||
async ({ page }) => {
|
||||
await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await createAndConvertToEmbedSyncedDoc(page);
|
||||
|
||||
// Focus on the embed synced doc
|
||||
const embedSyncedBlock = page.locator('affine-embed-synced-doc-block');
|
||||
let embedSyncedBox = await embedSyncedBlock.boundingBox();
|
||||
assertExists(embedSyncedBox);
|
||||
await page.mouse.click(
|
||||
embedSyncedBox.x + embedSyncedBox.width / 2,
|
||||
embedSyncedBox.y + embedSyncedBox.height / 2
|
||||
);
|
||||
|
||||
// Switch to edgeless mode
|
||||
await switchEditorMode(page);
|
||||
await waitNextFrame(page, 200);
|
||||
|
||||
// Double click on note to enter edit status
|
||||
const noteBlock = page.locator('affine-edgeless-note');
|
||||
const noteBlockBox = await noteBlock.boundingBox();
|
||||
assertExists(noteBlockBox);
|
||||
await page.mouse.dblclick(noteBlockBox.x + 10, noteBlockBox.y + 10);
|
||||
await waitNextFrame(page, 200);
|
||||
|
||||
// Drag the embed synced doc to whiteboard
|
||||
embedSyncedBox = await embedSyncedBlock.boundingBox();
|
||||
assertExists(embedSyncedBox);
|
||||
const height = embedSyncedBox.height;
|
||||
await page.mouse.move(embedSyncedBox.x - 10, embedSyncedBox.y - 100);
|
||||
await page.mouse.move(embedSyncedBox.x - 10, embedSyncedBox.y + 10);
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(100, 200, { steps: 30 });
|
||||
await page.mouse.up();
|
||||
|
||||
// Check the height of the embed synced doc portal, it should be the same as the embed synced doc in note
|
||||
const EmbedSyncedDocBlock = page.locator(
|
||||
'affine-embed-edgeless-synced-doc-block'
|
||||
);
|
||||
const EmbedSyncedDocBlockBox = await EmbedSyncedDocBlock.boundingBox();
|
||||
const border = 1;
|
||||
assertExists(EmbedSyncedDocBlockBox);
|
||||
expect(EmbedSyncedDocBlockBox.height).toBeCloseTo(height + 2 * border, 1);
|
||||
}
|
||||
);
|
||||
|
||||
test('nested embed synced doc should be rendered as card when depth >=1', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(() => {
|
||||
const { doc, collection } = window;
|
||||
const rootId = doc.addBlock('affine:page', {
|
||||
title: new window.$blocksuite.store.Text(),
|
||||
});
|
||||
|
||||
const noteId = doc.addBlock('affine:note', {}, rootId);
|
||||
doc.addBlock('affine:paragraph', {}, noteId);
|
||||
|
||||
const doc2 = collection.createDoc({ id: 'doc2' });
|
||||
doc2.load();
|
||||
const rootId2 = doc2.addBlock('affine:page', {
|
||||
title: new window.$blocksuite.store.Text('Doc 2'),
|
||||
});
|
||||
|
||||
const noteId2 = doc2.addBlock('affine:note', {}, rootId2);
|
||||
doc2.addBlock(
|
||||
'affine:paragraph',
|
||||
{
|
||||
text: new window.$blocksuite.store.Text('Hello from Doc 2'),
|
||||
},
|
||||
noteId2
|
||||
);
|
||||
|
||||
const doc3 = collection.createDoc({ id: 'doc3' });
|
||||
doc3.load();
|
||||
const rootId3 = doc3.addBlock('affine:page', {
|
||||
title: new window.$blocksuite.store.Text('Doc 3'),
|
||||
});
|
||||
|
||||
const noteId3 = doc3.addBlock('affine:note', {}, rootId3);
|
||||
doc3.addBlock(
|
||||
'affine:paragraph',
|
||||
{
|
||||
text: new window.$blocksuite.store.Text('Hello from Doc 3'),
|
||||
},
|
||||
noteId3
|
||||
);
|
||||
|
||||
doc2.addBlock(
|
||||
'affine:embed-synced-doc',
|
||||
{
|
||||
pageId: 'doc3',
|
||||
},
|
||||
noteId2
|
||||
);
|
||||
doc.addBlock(
|
||||
'affine:embed-synced-doc',
|
||||
{
|
||||
pageId: 'doc2',
|
||||
},
|
||||
noteId
|
||||
);
|
||||
});
|
||||
expect(await page.locator('affine-embed-synced-doc-block').count()).toBe(2);
|
||||
expect(await page.locator('affine-paragraph').count()).toBe(2);
|
||||
expect(await page.locator('affine-embed-synced-doc-card').count()).toBe(1);
|
||||
expect(await page.locator('editor-host').count()).toBe(2);
|
||||
});
|
||||
|
||||
test.describe('synced doc should be readonly', () => {
|
||||
test('synced doc should be readonly', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await createAndConvertToEmbedSyncedDoc(page);
|
||||
const locator = page.locator('affine-embed-synced-doc-block');
|
||||
await locator.click();
|
||||
|
||||
const toolbar = page.locator('editor-toolbar');
|
||||
const openMenu = toolbar.getByRole('button', { name: 'Open' });
|
||||
await openMenu.click();
|
||||
|
||||
const button = toolbar.getByRole('button', { name: 'Open this doc' });
|
||||
await button.click();
|
||||
|
||||
await page.evaluate(async () => {
|
||||
const { collection } = window;
|
||||
const getDocCollection = () => {
|
||||
for (const [id, doc] of collection.docs.entries()) {
|
||||
if (id === 'doc:home') {
|
||||
continue;
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const doc2Collection = getDocCollection();
|
||||
const doc2 = doc2Collection!.getStore();
|
||||
const [noteBlock] = doc2!.getBlocksByFlavour('affine:note');
|
||||
const noteId = noteBlock.id;
|
||||
|
||||
const databaseId = doc2.addBlock(
|
||||
'affine:database',
|
||||
{
|
||||
title: new window.$blocksuite.store.Text('Database 1'),
|
||||
},
|
||||
noteId
|
||||
);
|
||||
const model = doc2.getBlockById(databaseId) as DatabaseBlockModel;
|
||||
const datasource =
|
||||
new window.$blocksuite.blocks.DatabaseBlockDataSource(model);
|
||||
datasource.viewManager.viewAdd('table');
|
||||
});
|
||||
|
||||
// go back to previous doc
|
||||
await page.evaluate(() => {
|
||||
const { collection, editor } = window;
|
||||
editor.doc = collection.getDoc('doc:home')!;
|
||||
});
|
||||
|
||||
const databaseFirstCell = page.locator(
|
||||
'.affine-database-column-header.database-row'
|
||||
);
|
||||
await databaseFirstCell.click({ force: true });
|
||||
const selectedCount = await page
|
||||
.locator('.affine-embed-synced-doc-container.selected')
|
||||
.count();
|
||||
expect(selectedCount).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
BIN
blocksuite/tests-legacy/e2e/fixtures/smile.png
Normal file
BIN
blocksuite/tests-legacy/e2e/fixtures/smile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 96 B |
1030
blocksuite/tests-legacy/e2e/format-bar.spec.ts
Normal file
1030
blocksuite/tests-legacy/e2e/format-bar.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
356
blocksuite/tests-legacy/e2e/fragments/frame-panel.spec.ts
Normal file
356
blocksuite/tests-legacy/e2e/fragments/frame-panel.spec.ts
Normal file
@@ -0,0 +1,356 @@
|
||||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
|
||||
import { dragBetweenCoords } from '../utils/actions/drag.js';
|
||||
import {
|
||||
addBasicShapeElement,
|
||||
addNote,
|
||||
createNote,
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
enterPresentationMode,
|
||||
getZoomLevel,
|
||||
setEdgelessTool,
|
||||
Shape,
|
||||
switchEditorMode,
|
||||
toggleFramePanel,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import { waitNextFrame } from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessNonSelectedRect,
|
||||
assertEdgelessSelectedRect,
|
||||
assertZoomLevel,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
async function dragFrameCard(
|
||||
page: Page,
|
||||
fromCard: Locator,
|
||||
toCard: Locator,
|
||||
direction: 'up' | 'down' = 'down'
|
||||
) {
|
||||
const fromRect = await fromCard.boundingBox();
|
||||
const toRect = await toCard.boundingBox();
|
||||
// drag to the center of the toCard
|
||||
const center = { x: toRect!.width / 2, y: toRect!.height / 2 };
|
||||
const offset = direction === 'up' ? { x: 0, y: -20 } : { x: 0, y: 20 };
|
||||
await page.mouse.move(fromRect!.x + center.x, fromRect!.y + center.y);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(
|
||||
toRect!.x + center.x + offset.x,
|
||||
toRect!.y + center.y + offset.y,
|
||||
{ steps: 10 }
|
||||
);
|
||||
await page.mouse.up();
|
||||
}
|
||||
|
||||
test.describe('frame panel', () => {
|
||||
test('should display empty placeholder when no frames', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await toggleFramePanel(page);
|
||||
const frameCards = page.locator('affine-frame-card');
|
||||
expect(await frameCards.count()).toBe(0);
|
||||
|
||||
const placeholder = page.locator('.no-frame-placeholder');
|
||||
expect(await placeholder.isVisible()).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should display frame cards when there are frames', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await toggleFramePanel(page);
|
||||
|
||||
await addBasicShapeElement(
|
||||
page,
|
||||
{ x: 300, y: 300 },
|
||||
{ x: 350, y: 350 },
|
||||
Shape.Square
|
||||
);
|
||||
|
||||
await addNote(page, 'hello', 150, 500);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(page, { x: 250, y: 250 }, { x: 360, y: 360 });
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(page, { x: 100, y: 440 }, { x: 600, y: 600 });
|
||||
|
||||
const frames = page.locator('affine-frame');
|
||||
expect(await frames.count()).toBe(2);
|
||||
const frameCards = page.locator('affine-frame-card');
|
||||
expect(await frameCards.count()).toBe(2);
|
||||
});
|
||||
|
||||
test('should render edgeless note correctly in frame preview', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await toggleFramePanel(page);
|
||||
|
||||
await addNote(page, 'hello', 150, 500);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(page, { x: 100, y: 440 }, { x: 600, y: 600 });
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
const frames = page.locator('affine-frame');
|
||||
expect(await frames.count()).toBe(1);
|
||||
const frameCards = page.locator('affine-frame-card');
|
||||
expect(await frameCards.count()).toBe(1);
|
||||
const edgelessNote = page.locator('affine-frame-card affine-edgeless-note');
|
||||
expect(await edgelessNote.count()).toBe(1);
|
||||
});
|
||||
|
||||
test('should update panel when frames change', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await toggleFramePanel(page);
|
||||
const frameCards = page.locator('affine-frame-card');
|
||||
expect(await frameCards.count()).toBe(0);
|
||||
|
||||
await addNote(page, 'hello', 150, 500);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(page, { x: 100, y: 440 }, { x: 600, y: 600 });
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(page, { x: 50, y: 300 }, { x: 120, y: 400 });
|
||||
await waitNextFrame(page);
|
||||
|
||||
const frames = page.locator('affine-frame');
|
||||
expect(await frames.count()).toBe(2);
|
||||
expect(await frameCards.count()).toBe(2);
|
||||
|
||||
await page.mouse.click(50, 300);
|
||||
await page.keyboard.press('Delete');
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(await frames.count()).toBe(1);
|
||||
expect(await frameCards.count()).toBe(1);
|
||||
});
|
||||
|
||||
test.describe('frame panel behavior after mode switch', () => {
|
||||
async function setupFrameTest(page: Page) {
|
||||
await edgelessCommonSetup(page);
|
||||
await toggleFramePanel(page);
|
||||
|
||||
await addNote(page, 'hello', 150, 500);
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: 100, y: 440 },
|
||||
{ x: 640, y: 600 },
|
||||
{ steps: 10 }
|
||||
);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
const edgelessNote = page.locator(
|
||||
'affine-frame-card affine-edgeless-note'
|
||||
);
|
||||
expect(await edgelessNote.count()).toBe(1);
|
||||
|
||||
return edgelessNote;
|
||||
}
|
||||
|
||||
test('should render edgeless note correctly after mode switch', async ({
|
||||
page,
|
||||
}) => {
|
||||
const edgelessNote = await setupFrameTest(page);
|
||||
|
||||
const initialNoteRect = await edgelessNote.boundingBox();
|
||||
expect(initialNoteRect).not.toBeNull();
|
||||
|
||||
const {
|
||||
width: noteWidth,
|
||||
height: noteHeight,
|
||||
x: noteX,
|
||||
y: noteY,
|
||||
} = initialNoteRect!;
|
||||
|
||||
const checkNoteRect = async () => {
|
||||
expect(await edgelessNote.count()).toBe(1);
|
||||
|
||||
const newNoteRect = await edgelessNote.boundingBox();
|
||||
expect(newNoteRect).not.toBeNull();
|
||||
|
||||
expect(newNoteRect!.width).toBe(noteWidth);
|
||||
expect(newNoteRect!.height).toBe(noteHeight);
|
||||
expect(newNoteRect!.x).toBe(noteX);
|
||||
expect(newNoteRect!.y).toBe(noteY);
|
||||
};
|
||||
|
||||
await switchEditorMode(page);
|
||||
await checkNoteRect();
|
||||
|
||||
await switchEditorMode(page);
|
||||
await checkNoteRect();
|
||||
});
|
||||
|
||||
test('should update frame preview when note is moved', async ({ page }) => {
|
||||
const edgelessNote = await setupFrameTest(page);
|
||||
|
||||
const initialNoteRect = await edgelessNote.boundingBox();
|
||||
expect(initialNoteRect).not.toBeNull();
|
||||
|
||||
await switchEditorMode(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
async function moveNoteAndCheck(
|
||||
start: { x: number; y: number },
|
||||
end: { x: number; y: number },
|
||||
comparison: 'greaterThan' | 'lessThan'
|
||||
) {
|
||||
await page.mouse.move(start.x, start.y);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(end.x, end.y);
|
||||
await page.mouse.up();
|
||||
await waitNextFrame(page);
|
||||
|
||||
const newNoteRect = await edgelessNote.boundingBox();
|
||||
expect(newNoteRect).not.toBeNull();
|
||||
|
||||
if (comparison === 'greaterThan') {
|
||||
expect(newNoteRect!.x).toBeGreaterThan(initialNoteRect!.x);
|
||||
expect(newNoteRect!.y).toBeGreaterThan(initialNoteRect!.y);
|
||||
} else {
|
||||
expect(newNoteRect!.x).toBeLessThan(initialNoteRect!.x);
|
||||
expect(newNoteRect!.y).toBeLessThan(initialNoteRect!.y);
|
||||
}
|
||||
}
|
||||
|
||||
// Move the note to the right
|
||||
await moveNoteAndCheck(
|
||||
{ x: 150, y: 500 },
|
||||
{ x: 200, y: 550 },
|
||||
'greaterThan'
|
||||
);
|
||||
|
||||
// Move the note back to the left
|
||||
await moveNoteAndCheck(
|
||||
{ x: 200, y: 550 },
|
||||
{ x: 100, y: 450 },
|
||||
'lessThan'
|
||||
);
|
||||
|
||||
// Move the note diagonally
|
||||
await moveNoteAndCheck(
|
||||
{ x: 100, y: 450 },
|
||||
{ x: 250, y: 600 },
|
||||
'greaterThan'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('select and de-select frame', () => {
|
||||
async function setupFrameTest(page: Page) {
|
||||
await edgelessCommonSetup(page);
|
||||
await toggleFramePanel(page);
|
||||
|
||||
await addNote(page, 'hello', 150, 500);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(page, { x: 100, y: 440 }, { x: 640, y: 600 });
|
||||
await waitNextFrame(page);
|
||||
|
||||
const frames = page.locator('affine-frame');
|
||||
const frameCards = page.locator('affine-frame-card');
|
||||
expect(await frames.count()).toBe(1);
|
||||
expect(await frameCards.count()).toBe(1);
|
||||
|
||||
return { frames, frameCards };
|
||||
}
|
||||
|
||||
test('by click on frame card', async ({ page }) => {
|
||||
const { frameCards } = await setupFrameTest(page);
|
||||
|
||||
// click on the first frame card
|
||||
await frameCards.nth(0).click();
|
||||
await assertEdgelessSelectedRect(page, [100, 440, 540, 160]);
|
||||
|
||||
await frameCards.nth(0).click();
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test('by click on blank area', async ({ page }) => {
|
||||
const { frameCards } = await setupFrameTest(page);
|
||||
|
||||
// click on the first frame card
|
||||
await frameCards.nth(0).click();
|
||||
await assertEdgelessSelectedRect(page, [100, 440, 540, 160]);
|
||||
|
||||
const framePanel = page.locator('.frame-panel-container');
|
||||
const panelRect = await framePanel.boundingBox();
|
||||
expect(panelRect).not.toBeNull();
|
||||
const { x, y, width, height } = panelRect!;
|
||||
await page.mouse.click(x + width / 2, y + height / 2);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
});
|
||||
|
||||
test('should fit the viewport to the frame when double click frame card', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await toggleFramePanel(page);
|
||||
|
||||
await assertZoomLevel(page, 100);
|
||||
|
||||
await addNote(page, 'hello', 150, 500);
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(page, { x: 100, y: 440 }, { x: 600, y: 600 });
|
||||
await waitNextFrame(page);
|
||||
|
||||
const frameCards = page.locator('affine-frame-card');
|
||||
await frameCards.nth(0).dblclick();
|
||||
|
||||
const zoomLevel = await getZoomLevel(page);
|
||||
expect(zoomLevel).toBeGreaterThan(100);
|
||||
});
|
||||
|
||||
test('should reorder frames when drag and drop frame card', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await createNote(page, [300, 100], 'hello');
|
||||
|
||||
// Frame shape
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenViewCoords(page, [80, 80], [220, 220]);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
// Frame note
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenViewCoords(page, [240, 0], [800, 200]);
|
||||
|
||||
expect(await page.locator('affine-frame').count()).toBe(2);
|
||||
|
||||
await toggleFramePanel(page);
|
||||
|
||||
const frameCards = page.locator('affine-frame-card');
|
||||
expect(await frameCards.count()).toBe(2);
|
||||
|
||||
// Drag the first frame card to the second
|
||||
await dragFrameCard(page, frameCards.nth(0), frameCards.nth(1));
|
||||
|
||||
await enterPresentationMode(page);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
// Check if frame contains note now is the first
|
||||
const edgelessNote = page.locator(
|
||||
'affine-edgeless-root affine-edgeless-note'
|
||||
);
|
||||
await expect(edgelessNote).toBeVisible();
|
||||
});
|
||||
});
|
||||
93
blocksuite/tests-legacy/e2e/hotkey/bracket.spec.ts
Normal file
93
blocksuite/tests-legacy/e2e/hotkey/bracket.spec.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
dragBetweenIndices,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getPageSnapshot,
|
||||
initEmptyCodeBlockState,
|
||||
initEmptyParagraphState,
|
||||
initThreeParagraphs,
|
||||
resetHistory,
|
||||
type,
|
||||
undoByClick,
|
||||
} from '../utils/actions/index.js';
|
||||
import { assertRichTexts } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('should bracket complete works', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '([{');
|
||||
// type without selection should not trigger bracket complete
|
||||
await assertRichTexts(page, ['([{']);
|
||||
|
||||
await dragBetweenIndices(page, [0, 1], [0, 2]);
|
||||
await type(page, '(');
|
||||
await assertRichTexts(page, ['(([){']);
|
||||
|
||||
await type(page, ')');
|
||||
// Should not trigger bracket complete when type right bracket
|
||||
await assertRichTexts(page, ['(()){']);
|
||||
});
|
||||
|
||||
test('bracket complete should not work when selecting mutiple lines', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
|
||||
// 1(23 45)6 789
|
||||
await dragBetweenIndices(page, [0, 1], [1, 2]);
|
||||
await type(page, '(');
|
||||
await assertRichTexts(page, ['1(6', '789']);
|
||||
});
|
||||
|
||||
test('should bracket complete with backtick works', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello world');
|
||||
|
||||
await dragBetweenIndices(page, [0, 2], [0, 5]);
|
||||
await resetHistory(page);
|
||||
await type(page, '`');
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
|
||||
await undoByClick(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_undo.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('auto delete bracket right', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '(');
|
||||
await assertRichTexts(page, ['()']);
|
||||
await type(page, '(');
|
||||
await assertRichTexts(page, ['(())']);
|
||||
await page.keyboard.press('Backspace');
|
||||
await assertRichTexts(page, ['()']);
|
||||
await page.keyboard.press('Backspace');
|
||||
await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test('skip redundant right bracket', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '(');
|
||||
await assertRichTexts(page, ['()']);
|
||||
await type(page, ')');
|
||||
await assertRichTexts(page, ['()']);
|
||||
await type(page, ')');
|
||||
await assertRichTexts(page, ['())']);
|
||||
});
|
||||
474
blocksuite/tests-legacy/e2e/hotkey/hotkey.spec.ts
Normal file
474
blocksuite/tests-legacy/e2e/hotkey/hotkey.spec.ts
Normal file
@@ -0,0 +1,474 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
dragBetweenIndices,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getPageSnapshot,
|
||||
initEmptyParagraphState,
|
||||
initThreeParagraphs,
|
||||
inlineCode,
|
||||
MODIFIER_KEY,
|
||||
pressArrowDown,
|
||||
pressArrowLeft,
|
||||
pressArrowRight,
|
||||
pressArrowUp,
|
||||
pressEnter,
|
||||
pressForwardDelete,
|
||||
pressShiftTab,
|
||||
pressTab,
|
||||
readClipboardText,
|
||||
redoByClick,
|
||||
redoByKeyboard,
|
||||
resetHistory,
|
||||
setInlineRangeInSelectedRichText,
|
||||
SHIFT_KEY,
|
||||
SHORT_KEY,
|
||||
strikethrough,
|
||||
type,
|
||||
undoByClick,
|
||||
undoByKeyboard,
|
||||
updateBlockType,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockChildrenIds,
|
||||
assertRichTextInlineRange,
|
||||
assertRichTextModelType,
|
||||
assertRichTexts,
|
||||
assertTextFormat,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('rich-text hotkey scope on single press', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
await assertRichTexts(page, ['hello', 'world']);
|
||||
|
||||
await dragBetweenIndices(page, [0, 0], [1, 5]);
|
||||
await page.keyboard.press('Backspace');
|
||||
await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test('single line rich-text inline code hotkey', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await dragBetweenIndices(page, [0, 0], [0, 5]);
|
||||
await inlineCode(page);
|
||||
await assertTextFormat(page, 0, 5, { code: true });
|
||||
|
||||
// undo
|
||||
await undoByKeyboard(page);
|
||||
await assertTextFormat(page, 0, 5, {});
|
||||
// redo
|
||||
await redoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await assertTextFormat(page, 0, 5, { code: true });
|
||||
|
||||
// the format should be removed after trigger the hotkey again
|
||||
await inlineCode(page);
|
||||
await assertTextFormat(page, 0, 5, {});
|
||||
});
|
||||
|
||||
test('type character jump out code node', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'Hello');
|
||||
await setInlineRangeInSelectedRichText(page, 0, 5);
|
||||
await inlineCode(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_1.json`
|
||||
);
|
||||
await focusRichText(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+ArrowRight`);
|
||||
await type(page, 'block suite');
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_2.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('single line rich-text strikethrough hotkey', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await dragBetweenIndices(page, [0, 0], [0, 5]);
|
||||
await strikethrough(page);
|
||||
await assertTextFormat(page, 0, 5, { strike: true });
|
||||
|
||||
await undoByClick(page);
|
||||
await assertTextFormat(page, 0, 5, {});
|
||||
|
||||
await redoByClick(page);
|
||||
await assertTextFormat(page, 0, 5, { strike: true });
|
||||
|
||||
await waitNextFrame(page);
|
||||
// the format should be removed after trigger the hotkey again
|
||||
await strikethrough(page);
|
||||
await assertTextFormat(page, 0, 5, {});
|
||||
});
|
||||
|
||||
test('use formatted cursor with hotkey', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'aaa');
|
||||
// format italic
|
||||
await page.keyboard.press(`${SHORT_KEY}+i`, { delay: 50 });
|
||||
await type(page, 'bbb');
|
||||
// format bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 });
|
||||
await type(page, 'ccc');
|
||||
// unformat italic
|
||||
await page.keyboard.press(`${SHORT_KEY}+i`, { delay: 50 });
|
||||
await type(page, 'ddd');
|
||||
// unformat bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 });
|
||||
await type(page, 'eee');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
// format bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 });
|
||||
await type(page, 'fff');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_bold.json`
|
||||
);
|
||||
|
||||
await pressArrowLeft(page);
|
||||
await pressArrowRight(page);
|
||||
await type(page, 'ggg');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_bold_ggg.json`
|
||||
);
|
||||
|
||||
await setInlineRangeInSelectedRichText(page, 3, 0);
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'hhh');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_bold_hhh.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('use formatted cursor with hotkey at empty line', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
// format bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 });
|
||||
await type(page, 'aaa');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_bold.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('should single line format hotkey work', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await dragBetweenIndices(page, [0, 1], [0, 4]);
|
||||
|
||||
// bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 });
|
||||
// italic
|
||||
await page.keyboard.press(`${SHORT_KEY}+i`, { delay: 50 });
|
||||
// underline
|
||||
await page.keyboard.press(`${SHORT_KEY}+u`, { delay: 50 });
|
||||
// strikethrough
|
||||
await page.keyboard.press(`${SHORT_KEY}+Shift+s`, { delay: 50 });
|
||||
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
// bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 });
|
||||
// italic
|
||||
await page.keyboard.press(`${SHORT_KEY}+i`, { delay: 50 });
|
||||
// underline
|
||||
await page.keyboard.press(`${SHORT_KEY}+u`, { delay: 50 });
|
||||
// strikethrough
|
||||
await page.keyboard.press(`${SHORT_KEY}+Shift+s`, { delay: 50 });
|
||||
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_finial.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('should hotkey work in paragraph', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page, 0);
|
||||
await type(page, 'hello');
|
||||
|
||||
// XXX wait for group to be updated
|
||||
await page.waitForTimeout(10);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+1`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+6`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_press_6.json`
|
||||
);
|
||||
await page.waitForTimeout(50);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+8`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_press_8.json`
|
||||
);
|
||||
await page.waitForTimeout(50);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+9`);
|
||||
await waitNextFrame(page, 200);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_press_9.json`
|
||||
);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+0`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_press_0.json`
|
||||
);
|
||||
await page.waitForTimeout(50);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+d`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_press_d.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('format list to h1', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page, 0);
|
||||
await updateBlockType(page, 'affine:list', 'bulleted');
|
||||
await type(page, 'aa');
|
||||
await focusRichText(page, 0);
|
||||
await updateBlockType(page, 'affine:paragraph', 'h1');
|
||||
await assertRichTextModelType(page, 'h1');
|
||||
await undoByClick(page);
|
||||
await assertRichTextModelType(page, 'bulleted');
|
||||
await redoByClick(page);
|
||||
await assertRichTextModelType(page, 'h1');
|
||||
});
|
||||
|
||||
test('should cut work single line', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await resetHistory(page);
|
||||
await dragBetweenIndices(page, [0, 1], [0, 4]);
|
||||
// cut
|
||||
await page.keyboard.press(`${SHORT_KEY}+x`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
await undoByKeyboard(page);
|
||||
const text = await readClipboardText(page);
|
||||
expect(text).toBe('ell');
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_undo.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('should ctrl+enter create new block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '123');
|
||||
await pressArrowLeft(page, 2);
|
||||
await pressEnter(page);
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['1', '23']);
|
||||
await page.keyboard.press(`${SHORT_KEY}+Enter`);
|
||||
await assertRichTexts(page, ['1', '23', '']);
|
||||
});
|
||||
|
||||
test('should left/right key navigator works', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await focusRichText(page, 0);
|
||||
await assertRichTextInlineRange(page, 0, 3);
|
||||
await page.keyboard.press(`${SHORT_KEY}+ArrowLeft`, { delay: 50 });
|
||||
await assertRichTextInlineRange(page, 0, 0);
|
||||
await pressArrowLeft(page);
|
||||
await assertRichTextInlineRange(page, 0, 0);
|
||||
await page.keyboard.press(`${SHORT_KEY}+ArrowRight`, { delay: 50 });
|
||||
await assertRichTextInlineRange(page, 0, 3);
|
||||
await pressArrowRight(page);
|
||||
await assertRichTextInlineRange(page, 1, 0);
|
||||
await pressArrowLeft(page);
|
||||
await assertRichTextInlineRange(page, 0, 3);
|
||||
await pressArrowRight(page, 4);
|
||||
await assertRichTextInlineRange(page, 1, 3);
|
||||
await pressArrowRight(page);
|
||||
await assertRichTextInlineRange(page, 2, 0);
|
||||
await pressArrowLeft(page);
|
||||
await assertRichTextInlineRange(page, 1, 3);
|
||||
});
|
||||
|
||||
test('should up/down key navigator works', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await focusRichText(page, 0);
|
||||
await assertRichTextInlineRange(page, 0, 3);
|
||||
await pressArrowDown(page);
|
||||
await assertRichTextInlineRange(page, 1, 3);
|
||||
await pressArrowDown(page);
|
||||
await assertRichTextInlineRange(page, 2, 3);
|
||||
await page.keyboard.press(`${SHORT_KEY}+ArrowLeft`, { delay: 50 });
|
||||
await assertRichTextInlineRange(page, 2, 0);
|
||||
await pressArrowUp(page);
|
||||
await assertRichTextInlineRange(page, 1, 0);
|
||||
await pressArrowRight(page);
|
||||
await pressArrowUp(page);
|
||||
await assertRichTextInlineRange(page, 0, 1);
|
||||
await pressArrowDown(page);
|
||||
await assertRichTextInlineRange(page, 1, 1);
|
||||
});
|
||||
|
||||
test('should support ctrl/cmd+shift+l convert to linked doc', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[2, 3],
|
||||
[0, 0],
|
||||
{ x: 20, y: 20 },
|
||||
{ x: 0, y: 0 }
|
||||
);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${SHIFT_KEY}+l`);
|
||||
|
||||
const linkedDocCard = page.locator('affine-embed-linked-doc-block');
|
||||
await expect(linkedDocCard).toBeVisible();
|
||||
|
||||
const title = page.locator('.affine-embed-linked-doc-content-title-text');
|
||||
expect(await title.innerText()).toBe('Untitled');
|
||||
|
||||
const noteContent = page.locator('.affine-embed-linked-doc-content-note');
|
||||
expect(await noteContent.innerText()).toBe('123');
|
||||
});
|
||||
|
||||
test('should forwardDelete works when delete single character', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page, 0);
|
||||
await type(page, 'hello');
|
||||
await pressArrowLeft(page, 5);
|
||||
await pressForwardDelete(page);
|
||||
await assertRichTexts(page, ['ello']);
|
||||
});
|
||||
|
||||
test.describe('keyboard operation to move block up or down', () => {
|
||||
test('common paragraph', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
await pressEnter(page);
|
||||
await type(page, 'foo');
|
||||
await pressEnter(page);
|
||||
await type(page, 'bar');
|
||||
await assertRichTexts(page, ['hello', 'world', 'foo', 'bar']);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowUp`);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowUp`);
|
||||
await assertRichTexts(page, ['hello', 'bar', 'world', 'foo']);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowDown`);
|
||||
await assertRichTexts(page, ['hello', 'world', 'bar', 'foo']);
|
||||
});
|
||||
|
||||
test('with indent', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'world');
|
||||
await pressEnter(page);
|
||||
await pressShiftTab(page);
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'foo');
|
||||
await assertRichTexts(page, ['hello', 'world', 'foo']);
|
||||
await assertBlockChildrenIds(page, '2', ['3']);
|
||||
await pressArrowUp(page, 2);
|
||||
await waitNextFrame(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowDown`);
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['foo', 'hello', 'world']);
|
||||
await assertBlockChildrenIds(page, '1', ['4', '2']);
|
||||
await assertBlockChildrenIds(page, '2', ['3']);
|
||||
});
|
||||
|
||||
test('keep cursor', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
await pressEnter(page);
|
||||
await type(page, 'foo');
|
||||
await assertRichTexts(page, ['hello', 'world', 'foo']);
|
||||
await assertRichTextInlineRange(page, 2, 3);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowUp`);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowUp`);
|
||||
await assertRichTextInlineRange(page, 0, 3);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowDown`);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowDown`);
|
||||
await assertRichTextInlineRange(page, 2, 3);
|
||||
});
|
||||
});
|
||||
|
||||
test('Enter key should as expected after setting heading by shortkey', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/4987',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+1`);
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
});
|
||||
176
blocksuite/tests-legacy/e2e/hotkey/multiline.spec.ts
Normal file
176
blocksuite/tests-legacy/e2e/hotkey/multiline.spec.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
dragBetweenIndices,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getPageSnapshot,
|
||||
initEmptyParagraphState,
|
||||
initThreeParagraphs,
|
||||
inlineCode,
|
||||
pressArrowLeft,
|
||||
pressArrowUp,
|
||||
pressEnter,
|
||||
pressForwardDelete,
|
||||
pressShiftEnter,
|
||||
readClipboardText,
|
||||
redoByClick,
|
||||
resetHistory,
|
||||
setInlineRangeInSelectedRichText,
|
||||
SHORT_KEY,
|
||||
type,
|
||||
undoByClick,
|
||||
undoByKeyboard,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockSelections,
|
||||
assertRichTextInlineRange,
|
||||
assertRichTexts,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('should multiple line format hotkey work', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
// 0 1 2
|
||||
// 1|23 456 78|9
|
||||
await dragBetweenIndices(page, [0, 1], [2, 2]);
|
||||
|
||||
// bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`);
|
||||
// italic
|
||||
await page.keyboard.press(`${SHORT_KEY}+i`);
|
||||
// underline
|
||||
await page.keyboard.press(`${SHORT_KEY}+u`);
|
||||
// strikethrough
|
||||
await page.keyboard.press(`${SHORT_KEY}+Shift+S`);
|
||||
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
// bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 });
|
||||
// italic
|
||||
await page.keyboard.press(`${SHORT_KEY}+i`, { delay: 50 });
|
||||
// underline
|
||||
await page.keyboard.press(`${SHORT_KEY}+u`, { delay: 50 });
|
||||
// strikethrough
|
||||
await page.keyboard.press(`${SHORT_KEY}+Shift+s`, { delay: 50 });
|
||||
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_finial.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('multi line rich-text inline code hotkey', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
// 0 1 2
|
||||
// 1|23 456 78|9
|
||||
await dragBetweenIndices(page, [0, 1], [2, 2]);
|
||||
await inlineCode(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await undoByClick(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_undo.json`
|
||||
);
|
||||
|
||||
await redoByClick(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_redo.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('should cut work multiple line', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await resetHistory(page);
|
||||
// 0 1 2
|
||||
// 1|23 456 78|9
|
||||
await dragBetweenIndices(page, [0, 1], [2, 2]);
|
||||
// cut
|
||||
await page.keyboard.press(`${SHORT_KEY}+x`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
await undoByKeyboard(page);
|
||||
const text = await readClipboardText(page);
|
||||
expect(text).toBe(`23 456 78`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_undo.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('arrow up and down behavior on multiline text blocks when previous is non-text', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await pressEnter(page);
|
||||
await pressArrowUp(page);
|
||||
await type(page, '--- ');
|
||||
await pressEnter(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '124');
|
||||
await pressShiftEnter(page);
|
||||
await type(page, '1234');
|
||||
|
||||
await pressArrowUp(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await assertRichTextInlineRange(page, 0, 3);
|
||||
|
||||
await pressArrowUp(page);
|
||||
await assertBlockSelections(page, ['4']);
|
||||
});
|
||||
|
||||
test('should forwardDelete works when delete multi characters', async ({
|
||||
page,
|
||||
}) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/3122',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page, 0);
|
||||
await type(page, 'hello');
|
||||
await pressArrowLeft(page, 5);
|
||||
await setInlineRangeInSelectedRichText(page, 1, 3);
|
||||
await pressForwardDelete(page);
|
||||
await assertRichTexts(page, ['ho']);
|
||||
});
|
||||
|
||||
test('should drag multiple block and input text works', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2982',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await dragBetweenIndices(page, [0, 1], [2, 1]);
|
||||
await type(page, 'ab');
|
||||
await assertRichTexts(page, ['1ab89']);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
});
|
||||
43
blocksuite/tests-legacy/e2e/hotkey/title.spec.ts
Normal file
43
blocksuite/tests-legacy/e2e/hotkey/title.spec.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
cutByKeyboard,
|
||||
dragOverTitle,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
focusTitle,
|
||||
initEmptyParagraphState,
|
||||
pasteByKeyboard,
|
||||
pressEnter,
|
||||
type,
|
||||
} from '../utils/actions/index.js';
|
||||
import { assertRichTexts, assertTitle } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('should cut in title works', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusTitle(page);
|
||||
await type(page, 'hello');
|
||||
await assertTitle(page, 'hello');
|
||||
|
||||
await dragOverTitle(page);
|
||||
await cutByKeyboard(page);
|
||||
await assertTitle(page, '');
|
||||
|
||||
await focusRichText(page);
|
||||
await pasteByKeyboard(page);
|
||||
await assertRichTexts(page, ['hello']);
|
||||
});
|
||||
|
||||
test('enter in title should move cursor in new paragraph block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusTitle(page);
|
||||
await type(page, 'hello');
|
||||
await assertTitle(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
await assertRichTexts(page, ['world', '']);
|
||||
});
|
||||
154
blocksuite/tests-legacy/e2e/image/image.spec.ts
Normal file
154
blocksuite/tests-legacy/e2e/image/image.spec.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import '../utils/declare-test-window.js';
|
||||
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
activeEmbed,
|
||||
copyByKeyboard,
|
||||
dragEmbedResizeByTopLeft,
|
||||
dragEmbedResizeByTopRight,
|
||||
enterPlaygroundRoom,
|
||||
initImageState,
|
||||
moveToImage,
|
||||
pasteByKeyboard,
|
||||
pressArrowLeft,
|
||||
pressEnter,
|
||||
redoByClick,
|
||||
redoByKeyboard,
|
||||
type,
|
||||
undoByKeyboard,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertImageOption,
|
||||
assertImageSize,
|
||||
assertRichDragButton,
|
||||
assertRichImage,
|
||||
assertRichTextInlineRange,
|
||||
assertRichTexts,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
async function focusCaption(page: Page) {
|
||||
await page.click(
|
||||
'.affine-image-toolbar-container .image-toolbar-button.caption'
|
||||
);
|
||||
}
|
||||
|
||||
test('can drag resize image by left menu', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page);
|
||||
await assertRichImage(page, 1);
|
||||
|
||||
await activeEmbed(page);
|
||||
await assertRichDragButton(page);
|
||||
await assertImageSize(page, { width: 752, height: 564 });
|
||||
|
||||
await dragEmbedResizeByTopLeft(page);
|
||||
await waitNextFrame(page);
|
||||
await assertImageSize(page, { width: 358, height: 268 });
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await assertImageSize(page, { width: 752, height: 564 });
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await assertImageSize(page, { width: 358, height: 268 });
|
||||
});
|
||||
|
||||
test('can drag resize image by right menu', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page);
|
||||
await assertRichImage(page, 1);
|
||||
|
||||
await activeEmbed(page);
|
||||
await assertRichDragButton(page);
|
||||
await assertImageSize(page, { width: 752, height: 564 });
|
||||
|
||||
await dragEmbedResizeByTopRight(page);
|
||||
await assertImageSize(page, { width: 338, height: 253 });
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertImageSize(page, { width: 752, height: 564 });
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await assertImageSize(page, { width: 338, height: 253 });
|
||||
});
|
||||
|
||||
test('can click and delete image', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page);
|
||||
await assertRichImage(page, 1);
|
||||
|
||||
await activeEmbed(page);
|
||||
await page.keyboard.press('Backspace');
|
||||
await assertRichImage(page, 0);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertRichImage(page, 1);
|
||||
|
||||
await redoByClick(page);
|
||||
await assertRichImage(page, 0);
|
||||
});
|
||||
|
||||
test('can click and copy image', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page);
|
||||
await assertRichImage(page, 1);
|
||||
|
||||
await activeEmbed(page);
|
||||
await copyByKeyboard(page);
|
||||
await pressEnter(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await pasteByKeyboard(page);
|
||||
await waitNextFrame(page, 200);
|
||||
await assertRichImage(page, 2);
|
||||
});
|
||||
|
||||
test('enter shortcut on focusing embed block and its caption', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page);
|
||||
await assertRichImage(page, 1);
|
||||
|
||||
await moveToImage(page);
|
||||
await assertImageOption(page);
|
||||
|
||||
const caption = page.locator('affine-image block-caption-editor textarea');
|
||||
await focusCaption(page);
|
||||
await type(page, '123');
|
||||
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2495',
|
||||
});
|
||||
|
||||
// blur
|
||||
await page.mouse.click(0, 500);
|
||||
await caption.click({ position: { x: 0, y: 0 } });
|
||||
await type(page, 'abc');
|
||||
await expect(caption).toHaveValue('abc123');
|
||||
});
|
||||
|
||||
test('should support the enter key of image caption', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page);
|
||||
await assertRichImage(page, 1);
|
||||
|
||||
await moveToImage(page);
|
||||
await assertImageOption(page);
|
||||
|
||||
const caption = page.locator('affine-image block-caption-editor textarea');
|
||||
await focusCaption(page);
|
||||
await type(page, 'abc123');
|
||||
await pressArrowLeft(page, 3);
|
||||
await pressEnter(page);
|
||||
await expect(caption).toHaveValue('abc');
|
||||
|
||||
await assertRichTexts(page, ['123']);
|
||||
await assertRichTextInlineRange(page, 0, 0, 0);
|
||||
});
|
||||
79
blocksuite/tests-legacy/e2e/image/keymap.spec.ts
Normal file
79
blocksuite/tests-legacy/e2e/image/keymap.spec.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
activeEmbed,
|
||||
enterPlaygroundRoom,
|
||||
initImageState,
|
||||
pressArrowDown,
|
||||
pressArrowUp,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
type,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockCount,
|
||||
assertBlockSelections,
|
||||
assertRichImage,
|
||||
assertRichTextInlineRange,
|
||||
assertRichTexts,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page, true);
|
||||
await assertRichImage(page, 1);
|
||||
});
|
||||
|
||||
test('press enter will create new block when click and select image', async ({
|
||||
page,
|
||||
}) => {
|
||||
await activeEmbed(page);
|
||||
await pressEnter(page);
|
||||
await type(page, 'aa');
|
||||
await assertRichTexts(page, ['', 'aa']);
|
||||
});
|
||||
|
||||
test('press backspace after image block can select image block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await activeEmbed(page);
|
||||
await pressEnter(page);
|
||||
await assertRichTextInlineRange(page, 1, 0);
|
||||
await assertBlockCount(page, 'paragraph', 2);
|
||||
await pressBackspace(page);
|
||||
await assertBlockSelections(page, ['3']);
|
||||
await assertBlockCount(page, 'paragraph', 1);
|
||||
});
|
||||
|
||||
test('press enter when image is selected should move next paragraph and should placeholder', async ({
|
||||
page,
|
||||
}) => {
|
||||
await activeEmbed(page);
|
||||
await pressEnter(page);
|
||||
|
||||
const placeholder = page.locator('.affine-paragraph-placeholder.visible');
|
||||
await expect(placeholder).toBeVisible();
|
||||
});
|
||||
|
||||
test('press arrow up when image is selected should move to previous paragraph', async ({
|
||||
page,
|
||||
}) => {
|
||||
await activeEmbed(page);
|
||||
await pressArrowUp(page);
|
||||
await assertRichTextInlineRange(page, 0, 0);
|
||||
await type(page, 'aa');
|
||||
await assertRichTexts(page, ['aa']);
|
||||
});
|
||||
|
||||
test('press arrow down when image is selected should move to previous paragraph', async ({
|
||||
page,
|
||||
}) => {
|
||||
await activeEmbed(page);
|
||||
await pressEnter(page);
|
||||
await type(page, 'aa');
|
||||
await activeEmbed(page);
|
||||
await pressArrowDown(page);
|
||||
await type(page, 'bb');
|
||||
await assertRichTexts(page, ['', 'bbaa']);
|
||||
});
|
||||
168
blocksuite/tests-legacy/e2e/image/load.spec.ts
Normal file
168
blocksuite/tests-legacy/e2e/image/load.spec.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
expectConsoleMessage,
|
||||
} from '../utils/actions/index.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
const mockImageId = '_e2e_test_image_id_';
|
||||
|
||||
async function initMockImage(page: Page) {
|
||||
await page.evaluate(() => {
|
||||
const { doc } = window;
|
||||
doc.captureSync();
|
||||
const rootId = doc.addBlock('affine:page');
|
||||
const noteId = doc.addBlock('affine:note', {}, rootId);
|
||||
doc.addBlock(
|
||||
'affine:image',
|
||||
{
|
||||
sourceId: '_e2e_test_image_id_',
|
||||
width: 200,
|
||||
height: 180,
|
||||
},
|
||||
noteId
|
||||
);
|
||||
doc.captureSync();
|
||||
});
|
||||
}
|
||||
|
||||
test('image loading but failed', async ({ page }) => {
|
||||
expectConsoleMessage(
|
||||
page,
|
||||
'Error: Failed to fetch blob _e2e_test_image_id_',
|
||||
'warning'
|
||||
);
|
||||
expectConsoleMessage(
|
||||
page,
|
||||
'Failed to load resource: the server responded with a status of 404 (Not Found)'
|
||||
);
|
||||
expectConsoleMessage(
|
||||
page,
|
||||
'Error: Image blob is missing!, retrying',
|
||||
'warning'
|
||||
);
|
||||
|
||||
const room = await enterPlaygroundRoom(page, { blobSource: ['mock'] });
|
||||
const timeout = 2000;
|
||||
|
||||
// block image data request, force wait 100ms for loading test,
|
||||
// always return 404
|
||||
await page.route(
|
||||
`**/api/collection/${room}/blob/${mockImageId}`,
|
||||
async route => {
|
||||
await page.waitForTimeout(timeout);
|
||||
// broken image
|
||||
return route.fulfill({
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
await initMockImage(page);
|
||||
|
||||
const loadingContent = await page
|
||||
.locator(
|
||||
'.affine-image-fallback-card .affine-image-fallback-card-title-text'
|
||||
)
|
||||
.innerText();
|
||||
expect(loadingContent).toBe('Loading image...');
|
||||
|
||||
await page.waitForTimeout(3 * timeout);
|
||||
|
||||
await expect(
|
||||
page.locator(
|
||||
'.affine-image-fallback-card .affine-image-fallback-card-title-text'
|
||||
)
|
||||
).toContainText('Image loading failed.');
|
||||
});
|
||||
|
||||
test('image loading but success', async ({ page }) => {
|
||||
expectConsoleMessage(
|
||||
page,
|
||||
'Error: Failed to fetch blob _e2e_test_image_id_',
|
||||
'warning'
|
||||
);
|
||||
expectConsoleMessage(
|
||||
page,
|
||||
'Failed to load resource: the server responded with a status of 404 (Not Found)'
|
||||
);
|
||||
expectConsoleMessage(
|
||||
page,
|
||||
'Error: Image blob is missing!, retrying',
|
||||
'warning'
|
||||
);
|
||||
|
||||
const room = await enterPlaygroundRoom(page, { blobSource: ['mock'] });
|
||||
const imageBuffer = await readFile(
|
||||
fileURLToPath(new URL('../fixtures/smile.png', import.meta.url))
|
||||
);
|
||||
|
||||
const timeout = 2000;
|
||||
let count = 0;
|
||||
|
||||
// block image data request, force wait 100ms for loading test,
|
||||
// always return 404
|
||||
await page.route(
|
||||
`**/api/collection/${room}/blob/${mockImageId}`,
|
||||
async route => {
|
||||
await page.waitForTimeout(timeout);
|
||||
count++;
|
||||
if (count === 3) {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
body: imageBuffer,
|
||||
});
|
||||
}
|
||||
// broken image
|
||||
return route.fulfill({
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
await initMockImage(page);
|
||||
|
||||
const loadingContent = await page
|
||||
.locator(
|
||||
'.affine-image-fallback-card .affine-image-fallback-card-title-text'
|
||||
)
|
||||
.innerText();
|
||||
expect(loadingContent).toBe('Loading image...');
|
||||
|
||||
await page.waitForTimeout(3 * timeout);
|
||||
|
||||
const img = page.locator('.affine-image-container img');
|
||||
await expect(img).toBeVisible();
|
||||
const src = await img.getAttribute('src');
|
||||
expect(src).toBeDefined();
|
||||
});
|
||||
|
||||
test('image loaded successfully', async ({ page }) => {
|
||||
const room = await enterPlaygroundRoom(page, { blobSource: ['mock'] });
|
||||
const imageBuffer = await readFile(
|
||||
fileURLToPath(new URL('../fixtures/smile.png', import.meta.url))
|
||||
);
|
||||
await page.route(
|
||||
`**/api/collection/${room}/blob/${mockImageId}`,
|
||||
async route => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
body: imageBuffer,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
await initMockImage(page);
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const img = page.locator('.affine-image-container img');
|
||||
await expect(img).toBeVisible();
|
||||
const src = await img.getAttribute('src');
|
||||
expect(src).toBeDefined();
|
||||
});
|
||||
90
blocksuite/tests-legacy/e2e/image/menu.spec.ts
Normal file
90
blocksuite/tests-legacy/e2e/image/menu.spec.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
activeEmbed,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
initImageState,
|
||||
insertThreeLevelLists,
|
||||
pressEnter,
|
||||
scrollToTop,
|
||||
} from '../utils/actions/index.js';
|
||||
import { assertRichImage } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
// FIXME(@fundon): This behavior is not meeting the design spec
|
||||
test.skip('popup menu should follow position of image when scrolling', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page);
|
||||
await activeEmbed(page);
|
||||
await pressEnter(page);
|
||||
await insertThreeLevelLists(page, 0);
|
||||
await pressEnter(page);
|
||||
await insertThreeLevelLists(page, 3);
|
||||
await pressEnter(page);
|
||||
await insertThreeLevelLists(page, 6);
|
||||
await pressEnter(page);
|
||||
await insertThreeLevelLists(page, 9);
|
||||
await pressEnter(page);
|
||||
await insertThreeLevelLists(page, 12);
|
||||
|
||||
await scrollToTop(page);
|
||||
|
||||
const rect = await page.locator('.affine-image-container img').boundingBox();
|
||||
if (!rect) throw new Error('image not found');
|
||||
|
||||
await page.mouse.move(rect.x + rect.width / 2, rect.y + rect.height / 2);
|
||||
|
||||
await page.waitForTimeout(150);
|
||||
|
||||
const menu = page.locator('.affine-image-toolbar-container');
|
||||
|
||||
await expect(menu).toBeVisible();
|
||||
|
||||
await page.evaluate(
|
||||
([rect]) => {
|
||||
const viewport = document.querySelector('.affine-page-viewport');
|
||||
if (!viewport) {
|
||||
throw new Error();
|
||||
}
|
||||
// const distance = viewport.scrollHeight - viewport.clientHeight;
|
||||
viewport.scrollTo(0, (rect.height + rect.y) / 2);
|
||||
},
|
||||
[rect]
|
||||
);
|
||||
|
||||
await page.waitForTimeout(150);
|
||||
const image = page.locator('.affine-image-container img');
|
||||
const imageRect = await image.boundingBox();
|
||||
const menuRect = await menu.boundingBox();
|
||||
if (!imageRect) throw new Error('image not found');
|
||||
if (!menuRect) throw new Error('menu not found');
|
||||
expect(imageRect.y).toBeCloseTo((rect.y - rect.height) / 2, 172);
|
||||
expect(menuRect.y).toBeCloseTo(65, -0.325);
|
||||
});
|
||||
|
||||
test('select image should not show format bar', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page);
|
||||
await assertRichImage(page, 1);
|
||||
|
||||
const image = page.locator('affine-image');
|
||||
const rect = await image.boundingBox();
|
||||
if (!rect) {
|
||||
throw new Error('image not found');
|
||||
}
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: rect.x - 20, y: rect.y + 20 },
|
||||
{ x: rect.x + 20, y: rect.y + 40 }
|
||||
);
|
||||
const rects = page.locator('affine-block-selection').locator('visible=true');
|
||||
await expect(rects).toHaveCount(1);
|
||||
const formatQuickBar = page.locator(`.format-quick-bar`);
|
||||
await expect(formatQuickBar).not.toBeVisible();
|
||||
await page.mouse.wheel(0, rect.y + rect.height);
|
||||
await expect(formatQuickBar).not.toBeVisible();
|
||||
await page.mouse.click(0, 0);
|
||||
});
|
||||
1154
blocksuite/tests-legacy/e2e/inline/inline-editor.spec.ts
Normal file
1154
blocksuite/tests-legacy/e2e/inline/inline-editor.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
62
blocksuite/tests-legacy/e2e/latex/block.spec.ts
Normal file
62
blocksuite/tests-legacy/e2e/latex/block.spec.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getPageSnapshot,
|
||||
initEmptyParagraphState,
|
||||
type,
|
||||
} from '../utils/actions/index.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('add latex block using slash menu', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await type(page, '/eq\naaa');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_finial.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('add latex block using markdown shortcut with space', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await type(page, '$$$$ aaa');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_finial.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('add latex block using markdown shortcut with enter', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await type(page, '$$$$\naaa');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_finial.json`
|
||||
);
|
||||
});
|
||||
337
blocksuite/tests-legacy/e2e/latex/inline.spec.ts
Normal file
337
blocksuite/tests-legacy/e2e/latex/inline.spec.ts
Normal file
@@ -0,0 +1,337 @@
|
||||
import { ZERO_WIDTH_SPACE } from '@blocksuite/inline';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
cutByKeyboard,
|
||||
pasteByKeyboard,
|
||||
pressArrowLeft,
|
||||
pressArrowRight,
|
||||
pressArrowUp,
|
||||
pressBackspace,
|
||||
pressBackspaceWithShortKey,
|
||||
pressEnter,
|
||||
pressShiftEnter,
|
||||
redoByKeyboard,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
undoByKeyboard,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyParagraphState,
|
||||
} from '../utils/actions/misc.js';
|
||||
import {
|
||||
assertRichTextInlineDeltas,
|
||||
assertRichTextInlineRange,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('add inline latex at the start of line', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const latexEditorLine = page.locator('latex-editor-menu v-line div');
|
||||
const latexElement = page.locator(
|
||||
'affine-paragraph rich-text affine-latex-node'
|
||||
);
|
||||
|
||||
expect(await latexEditorLine.isVisible()).not.toBeTruthy();
|
||||
expect(await latexElement.isVisible()).not.toBeTruthy();
|
||||
await type(page, '$$ ');
|
||||
expect(await latexEditorLine.isVisible()).toBeTruthy();
|
||||
expect(await latexElement.isVisible()).toBeTruthy();
|
||||
expect(await latexElement.locator('.placeholder').innerText()).toBe(
|
||||
'Equation'
|
||||
);
|
||||
await type(page, 'E=mc^2');
|
||||
expect(await latexEditorLine.innerText()).toBe('E=mc^2');
|
||||
expect(await latexElement.locator('.katex').innerHTML()).toBe(
|
||||
'<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2</annotation></semantics></math>'
|
||||
);
|
||||
|
||||
await pressEnter(page);
|
||||
expect(await latexEditorLine.isVisible()).not.toBeTruthy();
|
||||
expect(await latexElement.locator('.katex').innerHTML()).toBe(
|
||||
'<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2</annotation></semantics></math>'
|
||||
);
|
||||
});
|
||||
|
||||
test('add inline latex in the middle of text', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const latexEditorLine = page.locator('latex-editor-menu v-line div');
|
||||
const latexElement = page.locator(
|
||||
'affine-paragraph rich-text affine-latex-node'
|
||||
);
|
||||
|
||||
expect(await latexEditorLine.isVisible()).not.toBeTruthy();
|
||||
expect(await latexElement.isVisible()).not.toBeTruthy();
|
||||
await type(page, 'aaaa');
|
||||
await pressArrowLeft(page, 2);
|
||||
await type(page, '$$ ');
|
||||
expect(await latexEditorLine.isVisible()).toBeTruthy();
|
||||
expect(await latexElement.isVisible()).toBeTruthy();
|
||||
expect(await latexElement.locator('.placeholder').innerText()).toBe(
|
||||
'Equation'
|
||||
);
|
||||
await type(page, 'E=mc^2');
|
||||
expect(await latexEditorLine.innerText()).toBe('E=mc^2');
|
||||
expect(await latexElement.locator('.katex').innerHTML()).toBe(
|
||||
'<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2</annotation></semantics></math>'
|
||||
);
|
||||
|
||||
await pressEnter(page);
|
||||
expect(await latexEditorLine.isVisible()).not.toBeTruthy();
|
||||
expect(await latexElement.locator('.katex').innerHTML()).toBe(
|
||||
'<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2</annotation></semantics></math>'
|
||||
);
|
||||
});
|
||||
|
||||
test('update inline latex by clicking the node', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const latexEditorLine = page.locator('latex-editor-menu v-line div');
|
||||
const latexElement = page.locator(
|
||||
'affine-paragraph rich-text affine-latex-node'
|
||||
);
|
||||
|
||||
expect(await latexEditorLine.isVisible()).not.toBeTruthy();
|
||||
await type(page, '$$ ');
|
||||
expect(await latexEditorLine.isVisible()).toBeTruthy();
|
||||
await type(page, 'E=mc^2');
|
||||
await pressEnter(page);
|
||||
expect(await latexEditorLine.isVisible()).not.toBeTruthy();
|
||||
await latexElement.click();
|
||||
expect(await latexEditorLine.isVisible()).toBeTruthy();
|
||||
await pressBackspace(page, 6);
|
||||
await type(page, String.raw`\def\arraystretch{1.5}`);
|
||||
await pressShiftEnter(page);
|
||||
await type(page, String.raw`\begin{array}{c:c:c}`);
|
||||
await pressShiftEnter(page);
|
||||
await type(page, String.raw`a & b & c \\ \\ hline`);
|
||||
await pressShiftEnter(page);
|
||||
await type(page, String.raw`d & e & f \\`);
|
||||
await pressShiftEnter(page);
|
||||
await type(page, String.raw`\hdashline`);
|
||||
await pressShiftEnter(page);
|
||||
await type(page, String.raw`g & h & i`);
|
||||
await pressShiftEnter(page);
|
||||
await type(page, String.raw`\end{array}`);
|
||||
expect(await latexElement.locator('.katex').innerHTML()).toBe(
|
||||
'<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.66em" columnalign="center center center" columnlines="dashed dashed" columnspacing="1em" rowlines="none none dashed"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>a</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>b</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>c</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi>h</mi><mi>l</mi><mi>i</mi><mi>n</mi><mi>e</mi><mi>d</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>e</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>f</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>g</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>h</mi></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>i</mi></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\\def\\arraystretch{1.5}\n\\begin{array}{c:c:c}\na & b & c \\\\ \\\\ hline\nd & e & f \\\\\n\\hdashline\ng & h & i\n\\end{array}</annotation></semantics></math>'
|
||||
);
|
||||
|
||||
// click outside to hide the editor
|
||||
await page.click('affine-editor-container');
|
||||
expect(await latexEditorLine.isVisible()).not.toBeTruthy();
|
||||
});
|
||||
|
||||
test('latex editor', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const latexEditorLine = page.locator('latex-editor-menu v-line div');
|
||||
const latexElement = page.locator(
|
||||
'affine-paragraph rich-text affine-latex-node'
|
||||
);
|
||||
|
||||
expect(await latexEditorLine.isVisible()).not.toBeTruthy();
|
||||
await type(page, '$$ ');
|
||||
expect(await latexEditorLine.isVisible()).toBeTruthy();
|
||||
// test cursor movement works as expected
|
||||
// https://github.com/toeverything/blocksuite/pull/8368
|
||||
await type(page, 'ababababababababababababababababababababababababab');
|
||||
expect(await latexEditorLine.innerText()).toBe(
|
||||
'ababababababababababababababababababababababababab'
|
||||
);
|
||||
// click outside to hide the editor
|
||||
expect(await latexEditorLine.isVisible()).toBeTruthy();
|
||||
await page.mouse.click(130, 130);
|
||||
expect(await latexEditorLine.isVisible()).not.toBeTruthy();
|
||||
await latexElement.click();
|
||||
expect(await latexEditorLine.isVisible()).toBeTruthy();
|
||||
expect(await latexEditorLine.innerText()).toBe(
|
||||
'ababababababababababababababababababababababababab'
|
||||
);
|
||||
|
||||
await pressBackspaceWithShortKey(page, 2);
|
||||
expect(await latexEditorLine.innerText()).toBe(ZERO_WIDTH_SPACE);
|
||||
await undoByKeyboard(page);
|
||||
expect(await latexEditorLine.innerText()).toBe(
|
||||
'ababababababababababababababababababababababababab'
|
||||
);
|
||||
await redoByKeyboard(page);
|
||||
expect(await latexEditorLine.innerText()).toBe(ZERO_WIDTH_SPACE);
|
||||
await undoByKeyboard(page);
|
||||
expect(await latexEditorLine.innerText()).toBe(
|
||||
'ababababababababababababababababababababababababab'
|
||||
);
|
||||
|
||||
// undo-redo
|
||||
await pressArrowLeft(page, 5);
|
||||
await page.keyboard.down('Shift');
|
||||
await pressArrowUp(page);
|
||||
await pressArrowRight(page);
|
||||
await page.keyboard.up('Shift');
|
||||
/**
|
||||
* abababababababababab|ababab
|
||||
* abababababababababa|babab
|
||||
*/
|
||||
await cutByKeyboard(page);
|
||||
expect(await latexEditorLine.innerText()).toBe('ababababababababababababab');
|
||||
/**
|
||||
* abababababababababab|babab
|
||||
*/
|
||||
await pressArrowRight(page, 2);
|
||||
/**
|
||||
* ababababababababababba|bab
|
||||
*/
|
||||
await pasteByKeyboard(page);
|
||||
expect(await latexEditorLine.innerText()).toBe(
|
||||
'ababababababababababababababababababababababababab'
|
||||
);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await pressBackspace(page);
|
||||
expect(await latexEditorLine.innerText()).toBe(ZERO_WIDTH_SPACE);
|
||||
|
||||
// highlight
|
||||
await type(
|
||||
page,
|
||||
String.raw`a+\left(\vcenter{\hbox{$\frac{\frac a b}c$}}\right)`
|
||||
);
|
||||
expect(
|
||||
(await latexEditorLine.locator('latex-editor-unit').innerHTML()).replace(
|
||||
/lit\$\d+\$/g,
|
||||
'lit$test$'
|
||||
)
|
||||
).toBe(
|
||||
'\x3C!----><span>\x3C!--?lit$test$-->\x3C!----><v-text style="color:#000000;">\x3C!----><span data-v-text="true" style="word-break:break-word;text-wrap:wrap;white-space-collapse:break-spaces;">\x3C!--?lit$test$-->a+</span></v-text>\x3C!---->\x3C!----><v-text style="color:#795E26;">\x3C!----><span data-v-text="true" style="word-break:break-word;text-wrap:wrap;white-space-collapse:break-spaces;">\x3C!--?lit$test$-->\\left</span></v-text>\x3C!---->\x3C!----><v-text style="color:#000000;">\x3C!----><span data-v-text="true" style="word-break:break-word;text-wrap:wrap;white-space-collapse:break-spaces;">\x3C!--?lit$test$-->(</span></v-text>\x3C!---->\x3C!----><v-text style="color:#795E26;">\x3C!----><span data-v-text="true" style="word-break:break-word;text-wrap:wrap;white-space-collapse:break-spaces;">\x3C!--?lit$test$-->\\vcenter</span></v-text>\x3C!---->\x3C!----><v-text style="color:#000000;">\x3C!----><span data-v-text="true" style="word-break:break-word;text-wrap:wrap;white-space-collapse:break-spaces;">\x3C!--?lit$test$-->{</span></v-text>\x3C!---->\x3C!----><v-text style="color:#795E26;">\x3C!----><span data-v-text="true" style="word-break:break-word;text-wrap:wrap;white-space-collapse:break-spaces;">\x3C!--?lit$test$-->\\hbox</span></v-text>\x3C!---->\x3C!----><v-text style="color:#000000;">\x3C!----><span data-v-text="true" style="word-break:break-word;text-wrap:wrap;white-space-collapse:break-spaces;">\x3C!--?lit$test$-->{</span></v-text>\x3C!---->\x3C!----><v-text style="color:#267F99;">\x3C!----><span data-v-text="true" style="word-break:break-word;text-wrap:wrap;white-space-collapse:break-spaces;">\x3C!--?lit$test$-->$</span></v-text>\x3C!---->\x3C!----><v-text style="color:#267F99;">\x3C!----><span data-v-text="true" style="word-break:break-word;text-wrap:wrap;white-space-collapse:break-spaces;">\x3C!--?lit$test$-->\\frac{\\frac a b}c</span></v-text>\x3C!---->\x3C!----><v-text style="color:#267F99;">\x3C!----><span data-v-text="true" style="word-break:break-word;text-wrap:wrap;white-space-collapse:break-spaces;">\x3C!--?lit$test$-->$</span></v-text>\x3C!---->\x3C!----><v-text style="color:#000000;">\x3C!----><span data-v-text="true" style="word-break:break-word;text-wrap:wrap;white-space-collapse:break-spaces;">\x3C!--?lit$test$-->}}</span></v-text>\x3C!---->\x3C!----><v-text style="color:#795E26;">\x3C!----><span data-v-text="true" style="word-break:break-word;text-wrap:wrap;white-space-collapse:break-spaces;">\x3C!--?lit$test$-->\\right</span></v-text>\x3C!---->\x3C!----><v-text style="color:#000000;">\x3C!----><span data-v-text="true" style="word-break:break-word;text-wrap:wrap;white-space-collapse:break-spaces;">\x3C!--?lit$test$-->)</span></v-text>\x3C!----></span>'
|
||||
);
|
||||
});
|
||||
|
||||
test('add inline latex using slash menu', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const latexEditorLine = page.locator('latex-editor-menu v-line div');
|
||||
const latexElement = page.locator(
|
||||
'affine-paragraph rich-text affine-latex-node'
|
||||
);
|
||||
|
||||
expect(await latexEditorLine.isVisible()).not.toBeTruthy();
|
||||
expect(await latexElement.isVisible()).not.toBeTruthy();
|
||||
await type(page, '/ieq\n');
|
||||
expect(await latexEditorLine.isVisible()).toBeTruthy();
|
||||
expect(await latexElement.isVisible()).toBeTruthy();
|
||||
expect(await latexElement.locator('.placeholder').innerText()).toBe(
|
||||
'Equation'
|
||||
);
|
||||
await type(page, 'E=mc^2');
|
||||
expect(await latexEditorLine.innerText()).toBe('E=mc^2');
|
||||
expect(await latexElement.locator('.katex').innerHTML()).toBe(
|
||||
'<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2</annotation></semantics></math>'
|
||||
);
|
||||
|
||||
await pressEnter(page);
|
||||
expect(await latexEditorLine.isVisible()).not.toBeTruthy();
|
||||
expect(await latexElement.locator('.katex').innerHTML()).toBe(
|
||||
'<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2</annotation></semantics></math>'
|
||||
);
|
||||
});
|
||||
|
||||
test('add inline latex using markdown shortcut', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
// toggle by space or enter
|
||||
await type(page, 'aa$$bb$$ cc$$dd$$\n');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa',
|
||||
},
|
||||
{
|
||||
insert: ' ',
|
||||
attributes: {
|
||||
latex: 'bb',
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'cc',
|
||||
},
|
||||
{
|
||||
insert: ' ',
|
||||
attributes: {
|
||||
latex: 'dd',
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
await pressArrowUp(page);
|
||||
await pressArrowRight(page, 3);
|
||||
await pressBackspace(page);
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aacc',
|
||||
},
|
||||
{
|
||||
insert: ' ',
|
||||
attributes: {
|
||||
latex: 'dd',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('undo-redo when add inline latex using markdown shortcut', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'aa$$bb$$ ');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa',
|
||||
},
|
||||
{
|
||||
insert: ' ',
|
||||
attributes: {
|
||||
latex: 'bb',
|
||||
},
|
||||
},
|
||||
]);
|
||||
await assertRichTextInlineRange(page, 0, 3, 0);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa$$bb$$ ',
|
||||
},
|
||||
]);
|
||||
await assertRichTextInlineRange(page, 0, 9, 0);
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa',
|
||||
},
|
||||
{
|
||||
insert: ' ',
|
||||
attributes: {
|
||||
latex: 'bb',
|
||||
},
|
||||
},
|
||||
]);
|
||||
await assertRichTextInlineRange(page, 0, 3, 0);
|
||||
});
|
||||
421
blocksuite/tests-legacy/e2e/link.spec.ts
Normal file
421
blocksuite/tests-legacy/e2e/link.spec.ts
Normal file
@@ -0,0 +1,421 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
cutByKeyboard,
|
||||
dragBetweenIndices,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
focusRichTextEnd,
|
||||
getPageSnapshot,
|
||||
initEmptyParagraphState,
|
||||
pasteByKeyboard,
|
||||
pressEnter,
|
||||
pressShiftEnter,
|
||||
pressTab,
|
||||
selectAllByKeyboard,
|
||||
setSelection,
|
||||
SHORT_KEY,
|
||||
switchReadonly,
|
||||
type,
|
||||
waitNextFrame,
|
||||
} from './utils/actions/index.js';
|
||||
import { assertKeyboardWorkInInput } from './utils/asserts.js';
|
||||
import { test } from './utils/playwright.js';
|
||||
|
||||
const pressCreateLinkShortCut = async (page: Page) => {
|
||||
await page.keyboard.press(`${SHORT_KEY}+k`);
|
||||
};
|
||||
|
||||
test('basic link', async ({ page }, testInfo) => {
|
||||
const linkText = 'linkText';
|
||||
const link = 'http://example.com';
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, linkText);
|
||||
|
||||
// Create link
|
||||
await dragBetweenIndices(page, [0, 0], [0, 8]);
|
||||
await pressCreateLinkShortCut(page);
|
||||
await page.mouse.move(0, 0);
|
||||
|
||||
const createLinkPopoverLocator = page.locator('.affine-link-popover.create');
|
||||
await expect(createLinkPopoverLocator).toBeVisible();
|
||||
const linkPopoverInput = page.locator('.affine-link-popover-input');
|
||||
await expect(linkPopoverInput).toBeVisible();
|
||||
await type(page, link);
|
||||
await pressEnter(page);
|
||||
await expect(createLinkPopoverLocator).not.toBeVisible();
|
||||
|
||||
const linkLocator = page.locator('affine-link a');
|
||||
await expect(linkLocator).toHaveAttribute('href', link);
|
||||
|
||||
// clear text selection
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
|
||||
const viewLinkPopoverLocator = page.locator('.affine-link-popover.view');
|
||||
// Hover link
|
||||
await expect(viewLinkPopoverLocator).not.toBeVisible();
|
||||
await linkLocator.hover();
|
||||
// wait for popover delay open
|
||||
await page.waitForTimeout(200);
|
||||
await expect(viewLinkPopoverLocator).toBeVisible();
|
||||
|
||||
// Edit link
|
||||
const text2 = 'link2';
|
||||
const link2 = 'https://github.com';
|
||||
const editLinkBtn = viewLinkPopoverLocator.getByTestId('edit');
|
||||
await editLinkBtn.click();
|
||||
|
||||
const editLinkPopoverLocator = page.locator('.affine-link-edit-popover');
|
||||
await expect(editLinkPopoverLocator).toBeVisible();
|
||||
// workaround to make tab key work as expected
|
||||
await editLinkPopoverLocator.click({
|
||||
position: { x: 5, y: 5 },
|
||||
});
|
||||
await page.keyboard.press('Tab');
|
||||
await type(page, text2);
|
||||
await page.keyboard.press('Tab');
|
||||
await type(page, link2);
|
||||
await page.keyboard.press('Tab');
|
||||
await pressEnter(page);
|
||||
const link2Locator = page.locator('affine-link a');
|
||||
|
||||
await expect(link2Locator).toHaveAttribute('href', link2);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('add link when dragging from empty line', async ({ page }) => {
|
||||
const linkText = 'linkText\n\n';
|
||||
const link = 'http://example.com';
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, linkText);
|
||||
|
||||
// Create link
|
||||
await dragBetweenIndices(page, [2, 0], [0, 0], {
|
||||
x: 1,
|
||||
y: 2,
|
||||
});
|
||||
await pressCreateLinkShortCut(page);
|
||||
await page.mouse.move(0, 0);
|
||||
|
||||
const createLinkPopoverLocator = page.locator('.affine-link-popover.create');
|
||||
await expect(createLinkPopoverLocator).toBeVisible();
|
||||
const linkPopoverInput = page.locator('.affine-link-popover-input');
|
||||
await expect(linkPopoverInput).toBeVisible();
|
||||
await type(page, link);
|
||||
await pressEnter(page);
|
||||
await expect(createLinkPopoverLocator).not.toBeVisible();
|
||||
|
||||
const linkLocator = page.locator('affine-link a');
|
||||
await expect(linkLocator).toHaveAttribute('href', link);
|
||||
});
|
||||
|
||||
async function createLinkBlock(page: Page, str: string, link: string) {
|
||||
const id = await page.evaluate(
|
||||
([str, link]) => {
|
||||
const { doc } = window;
|
||||
const rootId = doc.addBlock('affine:page', {
|
||||
title: new window.$blocksuite.store.Text('title'),
|
||||
});
|
||||
const noteId = doc.addBlock('affine:note', {}, rootId);
|
||||
|
||||
const text = new window.$blocksuite.store.Text([
|
||||
{ insert: 'Hello' },
|
||||
{ insert: str, attributes: { link } },
|
||||
]);
|
||||
const id = doc.addBlock(
|
||||
'affine:paragraph',
|
||||
{ type: 'text', text: text },
|
||||
noteId
|
||||
);
|
||||
return id;
|
||||
},
|
||||
[str, link]
|
||||
);
|
||||
return id;
|
||||
}
|
||||
|
||||
test('type character in link should not jump out link node', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await createLinkBlock(page, 'link text', 'http://example.com');
|
||||
await focusRichText(page, 0);
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
await type(page, 'IN_LINK');
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('type character after link should not extend the link attributes', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await createLinkBlock(page, 'link text', 'http://example.com');
|
||||
await focusRichText(page, 0);
|
||||
await type(page, 'AFTER_LINK');
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('readonly mode should not trigger link popup', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const linkText = 'linkText';
|
||||
await createLinkBlock(page, 'linkText', 'http://example.com');
|
||||
await focusRichText(page, 0);
|
||||
const linkLocator = page.locator(`text="${linkText}"`);
|
||||
|
||||
// Hover link
|
||||
const linkPopoverLocator = page.locator('.affine-link-popover');
|
||||
await linkLocator.hover();
|
||||
await expect(linkPopoverLocator).toBeVisible();
|
||||
await switchReadonly(page);
|
||||
|
||||
await page.mouse.move(0, 0);
|
||||
// XXX Wait for readonly delay
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await linkLocator.hover();
|
||||
await expect(linkPopoverLocator).not.toBeVisible();
|
||||
|
||||
// ---
|
||||
// press hotkey should not trigger create link popup
|
||||
|
||||
await dragBetweenIndices(page, [0, 0], [0, 3]);
|
||||
await pressCreateLinkShortCut(page);
|
||||
|
||||
await expect(linkPopoverLocator).not.toBeVisible();
|
||||
const linkPopoverInput = page.locator('.affine-link-popover-input');
|
||||
await expect(linkPopoverInput).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('should mock selection not stored', async ({ page }, testInfo) => {
|
||||
const linkText = 'linkText';
|
||||
const link = 'http://example.com';
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, linkText);
|
||||
|
||||
// Create link
|
||||
await dragBetweenIndices(page, [0, 0], [0, 8]);
|
||||
await pressCreateLinkShortCut(page);
|
||||
|
||||
const mockSelectNode = page.locator('.mock-selection');
|
||||
await expect(mockSelectNode).toHaveCount(1);
|
||||
await expect(mockSelectNode).toBeVisible();
|
||||
|
||||
// the mock select node should not be stored in the Y doc
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
|
||||
await type(page, link);
|
||||
await pressEnter(page);
|
||||
|
||||
// the mock select node should be removed after link created
|
||||
await expect(mockSelectNode).not.toBeVisible();
|
||||
await expect(mockSelectNode).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('should keyboard work in link popover', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const linkText = 'linkText';
|
||||
await createLinkBlock(page, linkText, 'http://example.com');
|
||||
|
||||
await dragBetweenIndices(page, [0, 0], [0, 8]);
|
||||
await pressCreateLinkShortCut(page);
|
||||
const linkPopoverInput = page.locator('.affine-link-popover-input');
|
||||
await assertKeyboardWorkInInput(page, linkPopoverInput);
|
||||
await page.mouse.click(500, 500);
|
||||
|
||||
const linkLocator = page.locator(`text="${linkText}"`);
|
||||
const linkPopover = page.locator('.affine-link-popover');
|
||||
await linkLocator.hover();
|
||||
await waitNextFrame(page, 200);
|
||||
await expect(linkLocator).toBeVisible();
|
||||
// Hover link
|
||||
await linkLocator.hover();
|
||||
// wait for popover delay open
|
||||
await page.waitForTimeout(200);
|
||||
await expect(linkPopover).toBeVisible();
|
||||
const editLinkBtn = linkPopover.getByTestId('edit');
|
||||
await editLinkBtn.click();
|
||||
|
||||
const editLinkPopover = page.locator('.affine-link-edit-popover');
|
||||
await expect(editLinkPopover).toBeVisible();
|
||||
|
||||
const editTextInput = editLinkPopover.locator(
|
||||
'.affine-edit-area.text .affine-edit-input'
|
||||
);
|
||||
await assertKeyboardWorkInInput(page, editTextInput);
|
||||
const editLinkInput = editLinkPopover.locator(
|
||||
'.affine-edit-area.link .affine-edit-input'
|
||||
);
|
||||
await assertKeyboardWorkInInput(page, editLinkInput);
|
||||
});
|
||||
|
||||
test('link bar should not be appear when the range is collapsed', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'aaa');
|
||||
|
||||
await pressCreateLinkShortCut(page);
|
||||
const linkPopoverLocator = page.locator('.affine-link-popover');
|
||||
await expect(linkPopoverLocator).not.toBeVisible();
|
||||
|
||||
await dragBetweenIndices(page, [0, 0], [0, 3]);
|
||||
await pressCreateLinkShortCut(page);
|
||||
await expect(linkPopoverLocator).toBeVisible();
|
||||
|
||||
await focusRichText(page); // click to cancel the link popover
|
||||
await focusRichTextEnd(page);
|
||||
await pressShiftEnter(page);
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'bbb');
|
||||
await dragBetweenIndices(page, [0, 1], [0, 5]);
|
||||
await pressCreateLinkShortCut(page);
|
||||
await expect(linkPopoverLocator).toBeVisible();
|
||||
|
||||
await focusRichTextEnd(page);
|
||||
await pressEnter(page);
|
||||
// create auto line-break in span element
|
||||
await type(page, 'd'.repeat(67));
|
||||
await page.mouse.click(1, 1);
|
||||
await waitNextFrame(page);
|
||||
await dragBetweenIndices(page, [1, 1], [1, 66]);
|
||||
await pressCreateLinkShortCut(page);
|
||||
await expect(linkPopoverLocator).toBeVisible();
|
||||
});
|
||||
|
||||
test('create link with paste', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'aaa');
|
||||
|
||||
await dragBetweenIndices(page, [0, 0], [0, 3]);
|
||||
await pressCreateLinkShortCut(page);
|
||||
|
||||
const createLinkPopoverLocator = page.locator('.affine-link-popover.create');
|
||||
const confirmBtn = createLinkPopoverLocator.locator('.affine-confirm-button');
|
||||
|
||||
await expect(createLinkPopoverLocator).toBeVisible();
|
||||
await expect(confirmBtn).toHaveAttribute('disabled');
|
||||
|
||||
await type(page, 'affine.pro');
|
||||
await expect(confirmBtn).not.toHaveAttribute('disabled');
|
||||
await selectAllByKeyboard(page);
|
||||
await cutByKeyboard(page);
|
||||
|
||||
// press enter should not trigger confirm
|
||||
await pressEnter(page);
|
||||
await expect(createLinkPopoverLocator).toBeVisible();
|
||||
await expect(confirmBtn).toHaveAttribute('disabled');
|
||||
|
||||
await pasteByKeyboard(page, false);
|
||||
await expect(confirmBtn).not.toHaveAttribute('disabled');
|
||||
await pressEnter(page);
|
||||
await expect(createLinkPopoverLocator).not.toBeVisible();
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('convert link to card', async ({ page }, testInfo) => {
|
||||
const linkText = 'alinkTexta';
|
||||
const link = 'http://example.com';
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'aaa');
|
||||
await pressEnter(page);
|
||||
await type(page, linkText);
|
||||
|
||||
// Create link
|
||||
await setSelection(page, 3, 1, 3, 9);
|
||||
await pressCreateLinkShortCut(page);
|
||||
await waitNextFrame(page);
|
||||
const linkPopoverLocator = page.locator('.affine-link-popover');
|
||||
await expect(linkPopoverLocator).toBeVisible();
|
||||
const linkPopoverInput = page.locator('.affine-link-popover-input');
|
||||
await expect(linkPopoverInput).toBeVisible();
|
||||
await type(page, link);
|
||||
await pressEnter(page);
|
||||
await expect(linkPopoverLocator).not.toBeVisible();
|
||||
await focusRichText(page, 1);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
|
||||
const linkLocator = page.locator('affine-link a');
|
||||
|
||||
await linkLocator.hover();
|
||||
await waitNextFrame(page);
|
||||
await expect(linkPopoverLocator).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'Switch view' }).click();
|
||||
const linkToCardBtn = page.getByTestId('link-to-card');
|
||||
const linkToEmbedBtn = page.getByTestId('link-to-embed');
|
||||
await expect(linkToCardBtn).toBeVisible();
|
||||
await expect(linkToEmbedBtn).not.toBeVisible();
|
||||
|
||||
await page.mouse.move(0, 0);
|
||||
await waitNextFrame(page);
|
||||
await expect(linkPopoverLocator).not.toBeVisible();
|
||||
await focusRichText(page, 1);
|
||||
await pressTab(page);
|
||||
|
||||
await linkLocator.hover();
|
||||
await waitNextFrame(page);
|
||||
await expect(linkPopoverLocator).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Switch view' }).click();
|
||||
await expect(linkToCardBtn).toBeVisible();
|
||||
await expect(linkToEmbedBtn).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('convert link to embed', async ({ page }, testInfo) => {
|
||||
const linkText = 'alinkTexta';
|
||||
const link = 'https://www.youtube.com/watch?v=U6s2pdxebSo';
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'aaa');
|
||||
await pressEnter(page);
|
||||
await type(page, linkText);
|
||||
|
||||
// Create link
|
||||
await setSelection(page, 3, 1, 3, 9);
|
||||
await pressCreateLinkShortCut(page);
|
||||
await waitNextFrame(page);
|
||||
const linkPopoverLocator = page.locator('.affine-link-popover');
|
||||
await expect(linkPopoverLocator).toBeVisible();
|
||||
const linkPopoverInput = page.locator('.affine-link-popover-input');
|
||||
await expect(linkPopoverInput).toBeVisible();
|
||||
await type(page, link);
|
||||
await pressEnter(page);
|
||||
await expect(linkPopoverLocator).not.toBeVisible();
|
||||
await focusRichText(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
|
||||
const linkLocator = page.locator('affine-link a');
|
||||
|
||||
await linkLocator.hover();
|
||||
await waitNextFrame(page);
|
||||
await expect(linkPopoverLocator).toBeVisible();
|
||||
});
|
||||
1059
blocksuite/tests-legacy/e2e/linked-page.spec.ts
Normal file
1059
blocksuite/tests-legacy/e2e/linked-page.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
842
blocksuite/tests-legacy/e2e/list.spec.ts
Normal file
842
blocksuite/tests-legacy/e2e/list.spec.ts
Normal file
@@ -0,0 +1,842 @@
|
||||
import { expect, type Locator } from '@playwright/test';
|
||||
|
||||
import {
|
||||
dragBetweenIndices,
|
||||
enterPlaygroundRoom,
|
||||
enterPlaygroundWithList,
|
||||
focusRichText,
|
||||
getPageSnapshot,
|
||||
initEmptyEdgelessState,
|
||||
initEmptyParagraphState,
|
||||
initThreeLists,
|
||||
pressArrowLeft,
|
||||
pressArrowUp,
|
||||
pressBackspace,
|
||||
pressBackspaceWithShortKey,
|
||||
pressEnter,
|
||||
pressShiftEnter,
|
||||
pressShiftTab,
|
||||
pressSpace,
|
||||
pressTab,
|
||||
redoByClick,
|
||||
switchEditorMode,
|
||||
switchReadonly,
|
||||
type,
|
||||
undoByClick,
|
||||
undoByKeyboard,
|
||||
updateBlockType,
|
||||
waitNextFrame,
|
||||
} from './utils/actions/index.js';
|
||||
import {
|
||||
assertBlockChildrenFlavours,
|
||||
assertBlockChildrenIds,
|
||||
assertBlockCount,
|
||||
assertBlockType,
|
||||
assertListPrefix,
|
||||
assertRichTextInlineRange,
|
||||
assertRichTexts,
|
||||
assertTextContent,
|
||||
} from './utils/asserts.js';
|
||||
import { test } from './utils/playwright.js';
|
||||
import { getFormatBar } from './utils/query.js';
|
||||
|
||||
async function isToggleIconVisible(toggleIcon: Locator) {
|
||||
const connected = await toggleIcon.isVisible();
|
||||
if (!connected) return false;
|
||||
const element = await toggleIcon.elementHandle();
|
||||
if (!element) return false;
|
||||
const opacity = await element.evaluate(node => {
|
||||
// https://stackoverflow.com/questions/11365296/how-do-i-get-the-opacity-of-an-element-using-javascript
|
||||
return window.getComputedStyle(node).getPropertyValue('opacity');
|
||||
});
|
||||
if (!opacity || typeof opacity !== 'string') {
|
||||
throw new Error('opacity is not a string');
|
||||
}
|
||||
const isVisible = opacity !== '0';
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
test('add new bulleted list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page, 0);
|
||||
await updateBlockType(page, 'affine:list', 'bulleted');
|
||||
await focusRichText(page, 0);
|
||||
await type(page, 'aa');
|
||||
await pressEnter(page);
|
||||
await type(page, 'aa');
|
||||
await pressEnter(page);
|
||||
|
||||
await assertRichTexts(page, ['aa', 'aa', '']);
|
||||
await assertBlockCount(page, 'list', 3);
|
||||
});
|
||||
|
||||
test('add new todo list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page, 0);
|
||||
await updateBlockType(page, 'affine:list', 'todo');
|
||||
await focusRichText(page, 0);
|
||||
|
||||
await type(page, 'aa');
|
||||
await assertRichTexts(page, ['aa']);
|
||||
|
||||
const checkBox = page.locator('.affine-list-block__prefix');
|
||||
await expect(page.locator('.affine-list--checked')).toHaveCount(0);
|
||||
await checkBox.click();
|
||||
await expect(page.locator('.affine-list--checked')).toHaveCount(1);
|
||||
await checkBox.click();
|
||||
await expect(page.locator('.affine-list--checked')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('add new toggle list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page, 0);
|
||||
await updateBlockType(page, 'affine:list', 'toggle');
|
||||
await focusRichText(page, 0);
|
||||
await type(page, 'top');
|
||||
await pressTab(page);
|
||||
await pressEnter(page);
|
||||
await type(page, 'kid 1');
|
||||
await pressEnter(page);
|
||||
|
||||
await assertRichTexts(page, ['top', 'kid 1', '']);
|
||||
await assertBlockCount(page, 'list', 3);
|
||||
});
|
||||
|
||||
test('convert nested paragraph to list', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'aaa\nbbb');
|
||||
await pressTab(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await dragBetweenIndices(page, [0, 1], [1, 2]);
|
||||
const { openParagraphMenu, bulletedBtn } = getFormatBar(page);
|
||||
await openParagraphMenu();
|
||||
await bulletedBtn.click();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('convert to numbered list block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page, 0); // created 0, 1, 2
|
||||
await updateBlockType(page, 'affine:list', 'bulleted'); // replaced 2 to 3
|
||||
await waitNextFrame(page);
|
||||
await updateBlockType(page, 'affine:list', 'numbered');
|
||||
await focusRichText(page, 0);
|
||||
|
||||
const listSelector = '.affine-list-rich-text-wrapper';
|
||||
const bulletIconSelector = `${listSelector} > div`;
|
||||
await assertTextContent(page, bulletIconSelector, /1\./);
|
||||
|
||||
await undoByClick(page);
|
||||
// const numberIconSelector = `${listSelector} > svg`;
|
||||
// await expect(page.locator(numberIconSelector)).toHaveCount(1);
|
||||
|
||||
await redoByClick(page);
|
||||
await focusRichText(page, 0);
|
||||
await type(page, 'aa');
|
||||
await pressEnter(page); // created 4
|
||||
await assertBlockType(page, '4', 'numbered');
|
||||
|
||||
await type(page, 'aa');
|
||||
await pressEnter(page); // created 5
|
||||
await assertBlockType(page, '5', 'numbered');
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
await assertBlockType(page, '5', 'numbered');
|
||||
});
|
||||
|
||||
test('indent list block', async ({ page }) => {
|
||||
await enterPlaygroundWithList(page); // 0(1(2,3,4))
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['', 'hello', '']);
|
||||
|
||||
await page.keyboard.press('Tab'); // 0(1(2(3)4))
|
||||
await assertRichTexts(page, ['', 'hello', '']);
|
||||
await assertBlockChildrenIds(page, '1', ['2', '4']);
|
||||
await assertBlockChildrenIds(page, '2', ['3']);
|
||||
|
||||
await undoByKeyboard(page); // 0(1(2,3,4))
|
||||
await assertBlockChildrenIds(page, '1', ['2', '3', '4']);
|
||||
});
|
||||
|
||||
test('unindent list block', async ({ page }) => {
|
||||
await enterPlaygroundWithList(page); // 0(1(2,3,4))
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await page.keyboard.press('Tab', { delay: 50 }); // 0(1(2(3)4))
|
||||
|
||||
await assertBlockChildrenIds(page, '1', ['2', '4']);
|
||||
await assertBlockChildrenIds(page, '2', ['3']);
|
||||
|
||||
await pressShiftTab(page); // 0(1(2,3,4))
|
||||
await assertBlockChildrenIds(page, '1', ['2', '3', '4']);
|
||||
|
||||
await pressShiftTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['2', '3', '4']);
|
||||
});
|
||||
|
||||
test('remove all indent for a list block', async ({ page }) => {
|
||||
await enterPlaygroundWithList(page); // 0(1(2,3,4))
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await page.keyboard.press('Tab', { delay: 50 }); // 0(1(2(3)4))
|
||||
await focusRichText(page, 2);
|
||||
await page.keyboard.press('Tab', { delay: 50 });
|
||||
await page.keyboard.press('Tab', { delay: 50 }); // 0(1(2(3(4))))
|
||||
await assertBlockChildrenIds(page, '3', ['4']);
|
||||
await pressBackspaceWithShortKey(page); // 0(1(2(3)4))
|
||||
await assertBlockChildrenIds(page, '1', ['2', '4']);
|
||||
await assertBlockChildrenIds(page, '2', ['3']);
|
||||
});
|
||||
|
||||
test('insert new list block by enter', async ({ page }) => {
|
||||
await enterPlaygroundWithList(page);
|
||||
await assertRichTexts(page, ['', '', '']);
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['', 'hello', '']);
|
||||
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
await assertRichTexts(page, ['', 'hello', 'world', '']);
|
||||
await assertBlockChildrenFlavours(page, '1', [
|
||||
'affine:list',
|
||||
'affine:list',
|
||||
'affine:list',
|
||||
'affine:list',
|
||||
]);
|
||||
});
|
||||
|
||||
test('delete at start of list block', async ({ page }) => {
|
||||
await enterPlaygroundWithList(page);
|
||||
await focusRichText(page, 1);
|
||||
await page.keyboard.press('Backspace');
|
||||
await assertBlockChildrenFlavours(page, '1', [
|
||||
'affine:list',
|
||||
'affine:paragraph',
|
||||
'affine:list',
|
||||
]);
|
||||
await waitNextFrame(page, 200);
|
||||
await assertRichTextInlineRange(page, 1, 0, 0);
|
||||
|
||||
await undoByClick(page);
|
||||
await assertBlockChildrenFlavours(page, '1', [
|
||||
'affine:list',
|
||||
'affine:list',
|
||||
'affine:list',
|
||||
]);
|
||||
await waitNextFrame(page);
|
||||
//FIXME: it just failed in playwright
|
||||
// await assertSelection(page, 1, 0, 0);
|
||||
});
|
||||
|
||||
test('nested list blocks', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundWithList(page);
|
||||
|
||||
await focusRichText(page, 0);
|
||||
await type(page, '123');
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await pressTab(page);
|
||||
await type(page, '456');
|
||||
|
||||
await focusRichText(page, 2);
|
||||
await pressTab(page);
|
||||
await pressTab(page);
|
||||
await type(page, '789');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await pressShiftTab(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_finial.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('update numbered list block prefix', async ({ page }) => {
|
||||
await enterPlaygroundWithList(page, ['', '', ''], 'numbered'); // 0(1(2,3,4))
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await type(page, 'lunatic');
|
||||
await assertRichTexts(page, ['', 'lunatic', '']);
|
||||
await assertListPrefix(page, ['1', '2', '3']);
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
await assertListPrefix(page, ['1', 'a', '2']);
|
||||
|
||||
await page.keyboard.press('Shift+Tab');
|
||||
await assertListPrefix(page, ['1', '2', '3']);
|
||||
|
||||
await waitNextFrame(page, 200);
|
||||
await page.keyboard.press('Enter');
|
||||
await assertListPrefix(page, ['1', '2', '3', '4']);
|
||||
|
||||
await waitNextFrame(page, 200);
|
||||
await type(page, 'concorde');
|
||||
await assertRichTexts(page, ['', 'lunatic', 'concorde', '']);
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
await assertListPrefix(page, ['1', '2', 'a', '3']);
|
||||
});
|
||||
|
||||
test('basic indent and unindent', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'text1');
|
||||
await pressEnter(page);
|
||||
await type(page, 'text2');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_after_tab.json`
|
||||
);
|
||||
|
||||
await page.waitForTimeout(100);
|
||||
await pressShiftTab(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_after_shift_tab.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('should indent todo block preserve todo status', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'text1');
|
||||
await pressEnter(page);
|
||||
|
||||
await type(page, '[x]');
|
||||
await pressSpace(page);
|
||||
|
||||
await type(page, 'todo item');
|
||||
await pressTab(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
await pressShiftTab(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('enter list block with empty text', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundWithList(page); // 0(1(2,3,4))
|
||||
|
||||
/**
|
||||
* -
|
||||
* -
|
||||
* -
|
||||
*/
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await pressTab(page);
|
||||
await focusRichText(page, 2);
|
||||
await pressTab(page);
|
||||
|
||||
/**
|
||||
* -
|
||||
* -
|
||||
* -|
|
||||
*/
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_1.json`
|
||||
);
|
||||
|
||||
await pressEnter(page);
|
||||
|
||||
/**
|
||||
* -
|
||||
* -
|
||||
* -|
|
||||
*/
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_2.json`
|
||||
);
|
||||
|
||||
await pressEnter(page);
|
||||
|
||||
/**
|
||||
* -
|
||||
* -
|
||||
* |
|
||||
*/
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_3.json`
|
||||
);
|
||||
|
||||
await undoByClick(page);
|
||||
await undoByClick(page);
|
||||
|
||||
/**
|
||||
* -
|
||||
* -
|
||||
* -|
|
||||
*/
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_1.json`
|
||||
);
|
||||
|
||||
/**
|
||||
* -
|
||||
* -|
|
||||
* -
|
||||
*/
|
||||
await focusRichText(page, 1);
|
||||
await waitNextFrame(page);
|
||||
await pressEnter(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
/**
|
||||
* -
|
||||
* -|
|
||||
* -
|
||||
*/
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_4.json`
|
||||
);
|
||||
|
||||
await undoByClick(page);
|
||||
|
||||
/**
|
||||
* -
|
||||
* -
|
||||
* -|
|
||||
*/
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_1.json`
|
||||
);
|
||||
|
||||
/**
|
||||
* -|
|
||||
* -
|
||||
* -
|
||||
*/
|
||||
await focusRichText(page, 0);
|
||||
await waitNextFrame(page);
|
||||
await pressEnter(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
/**
|
||||
* |
|
||||
* -
|
||||
* -
|
||||
*/
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_5.json`
|
||||
);
|
||||
|
||||
await undoByClick(page);
|
||||
|
||||
/**
|
||||
* -
|
||||
* -
|
||||
* -|
|
||||
*/
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_1.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('enter list block with non-empty text', async ({ page }) => {
|
||||
await enterPlaygroundWithList(page); // 0(1(2,3,4))
|
||||
|
||||
await focusRichText(page, 0);
|
||||
await type(page, 'aa');
|
||||
await focusRichText(page, 1);
|
||||
await type(page, 'bb');
|
||||
await pressTab(page);
|
||||
await focusRichText(page, 2);
|
||||
await type(page, 'cc');
|
||||
await pressTab(page);
|
||||
await assertBlockChildrenIds(page, '2', ['3', '4']); // 0(1(2,(3,4)))
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await pressEnter(page);
|
||||
await assertBlockChildrenIds(page, '2', ['3', '5', '4']);
|
||||
await undoByClick(page);
|
||||
await assertBlockChildrenIds(page, '2', ['3', '4']); // 0(1(2,(3,4)))
|
||||
|
||||
await focusRichText(page, 0);
|
||||
await pressEnter(page);
|
||||
await assertBlockChildrenIds(page, '2', ['6', '3', '4']); // 0(1(2,(6,3,4)))
|
||||
await waitNextFrame(page);
|
||||
await undoByClick(page);
|
||||
await assertBlockChildrenIds(page, '2', ['3', '4']); // 0(1(2,(3,4)))
|
||||
});
|
||||
|
||||
test.describe('indent correctly when deleting list item', () => {
|
||||
test('delete the child item in the middle position', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page, 0);
|
||||
|
||||
await type(page, '- a');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'b');
|
||||
await pressEnter(page);
|
||||
await type(page, 'c');
|
||||
await pressEnter(page);
|
||||
await type(page, 'd');
|
||||
await pressArrowUp(page);
|
||||
await pressArrowLeft(page);
|
||||
await pressBackspace(page);
|
||||
await pressBackspace(page);
|
||||
|
||||
await assertBlockChildrenIds(page, '3', ['4', '6']);
|
||||
await assertRichTexts(page, ['a', 'bc', 'd']);
|
||||
await assertRichTextInlineRange(page, 1, 1);
|
||||
});
|
||||
|
||||
test('merge two lists', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page, 0);
|
||||
|
||||
await type(page, '- a');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'b');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'c');
|
||||
await pressEnter(page);
|
||||
await pressBackspace(page, 3);
|
||||
await assertRichTexts(page, ['a', 'b', 'c', '']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await pressEnter(page);
|
||||
await type(page, '- d');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'e');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'f');
|
||||
await pressArrowUp(page, 3);
|
||||
await pressBackspace(page, 2);
|
||||
|
||||
await waitNextFrame(page, 200);
|
||||
await assertRichTexts(page, ['a', 'b', '', 'd', 'e', 'f']);
|
||||
await assertBlockChildrenIds(page, '1', ['3', '9']);
|
||||
await assertBlockChildrenIds(page, '3', ['4']);
|
||||
await assertBlockChildrenIds(page, '4', ['5']);
|
||||
await assertBlockChildrenIds(page, '10', ['11']);
|
||||
});
|
||||
});
|
||||
|
||||
test('delete list item with nested children items', async ({ page }) => {
|
||||
await enterPlaygroundWithList(page);
|
||||
|
||||
await focusRichText(page, 0);
|
||||
await type(page, '1');
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await pressTab(page);
|
||||
await type(page, '2');
|
||||
|
||||
await focusRichText(page, 2);
|
||||
await pressTab(page);
|
||||
await pressTab(page);
|
||||
await type(page, '3');
|
||||
|
||||
await pressEnter(page);
|
||||
await type(page, '4');
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await pressArrowLeft(page);
|
||||
// 1
|
||||
// |2
|
||||
// 3
|
||||
// 4
|
||||
|
||||
await pressBackspace(page);
|
||||
await waitNextFrame(page);
|
||||
// 1
|
||||
// |2 (transformed to paragraph)
|
||||
// 3
|
||||
// 4
|
||||
|
||||
await pressBackspace(page);
|
||||
await waitNextFrame(page);
|
||||
// 1
|
||||
// |2
|
||||
// 3
|
||||
// 4
|
||||
|
||||
await pressBackspace(page);
|
||||
await waitNextFrame(page);
|
||||
// 1|2
|
||||
// 3
|
||||
// 4
|
||||
|
||||
await assertRichTextInlineRange(page, 0, 1);
|
||||
await assertRichTexts(page, ['12', '3', '4']);
|
||||
await assertBlockChildrenIds(page, '1', ['2', '4', '5']);
|
||||
});
|
||||
|
||||
test('add number prefix to a todo item should not forcefully change it into numbered list, vice versa', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page, 0);
|
||||
await type(page, '1. numberList');
|
||||
await assertListPrefix(page, ['1']);
|
||||
await focusRichText(page, 0, { clickPosition: { x: 0, y: 0 } });
|
||||
await type(page, '[] ');
|
||||
await assertListPrefix(page, ['1']);
|
||||
await pressBackspace(page, 14);
|
||||
await type(page, '[] todoList');
|
||||
await assertListPrefix(page, ['']);
|
||||
await focusRichText(page, 0, { clickPosition: { x: 0, y: 0 } });
|
||||
await type(page, '1. ');
|
||||
await assertListPrefix(page, ['']);
|
||||
});
|
||||
|
||||
test('should not convert to a list when pressing space at the second line', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'aaa');
|
||||
await pressShiftEnter(page);
|
||||
await type(page, '-');
|
||||
await pressSpace(page);
|
||||
await type(page, 'bbb');
|
||||
await assertRichTexts(page, ['aaa\n- bbb']);
|
||||
});
|
||||
|
||||
test.describe('toggle list', () => {
|
||||
test('click toggle icon should collapsed list', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeLists(page);
|
||||
const toggleIcon = page.locator('.toggle-icon');
|
||||
const prefixes = page.locator('.affine-list-block__prefix');
|
||||
const listChildren = page
|
||||
.locator('[data-block-id="4"] .affine-block-children-container')
|
||||
.nth(0);
|
||||
const parentPrefix = prefixes.nth(1);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await parentPrefix.hover();
|
||||
await waitNextFrame(page);
|
||||
expect(await isToggleIconVisible(toggleIcon)).toBe(true);
|
||||
|
||||
await expect(listChildren).toBeVisible();
|
||||
await toggleIcon.click();
|
||||
await expect(listChildren).not.toBeVisible();
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_toggle.json`
|
||||
);
|
||||
|
||||
// Collapsed toggle icon should be show always
|
||||
await page.mouse.move(0, 0);
|
||||
expect(await isToggleIconVisible(toggleIcon)).toBe(true);
|
||||
|
||||
await expect(listChildren).not.toBeVisible();
|
||||
await toggleIcon.click();
|
||||
await expect(listChildren).toBeVisible();
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await page.mouse.move(0, 0);
|
||||
await waitNextFrame(page, 200);
|
||||
expect(await isToggleIconVisible(toggleIcon)).toBe(false);
|
||||
});
|
||||
|
||||
test('indent item should expand toggle', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeLists(page);
|
||||
await focusRichText(page, 2);
|
||||
await pressEnter(page);
|
||||
await pressEnter(page);
|
||||
await type(page, '012');
|
||||
|
||||
const toggleIcon = page.locator('.toggle-icon');
|
||||
const listChildren = page
|
||||
.locator('[data-block-id="4"] .affine-block-children-container')
|
||||
.nth(0);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await expect(listChildren).toBeVisible();
|
||||
await toggleIcon.click();
|
||||
await expect(listChildren).not.toBeVisible();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_toggle.json`
|
||||
);
|
||||
|
||||
await focusRichText(page, 3);
|
||||
await pressTab(page);
|
||||
await waitNextFrame(page, 200);
|
||||
await expect(listChildren).not.toBeVisible();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_finial.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('toggle icon should be show when hover', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeLists(page);
|
||||
const toggleIcon = page.locator('.toggle-icon');
|
||||
|
||||
const prefixes = page.locator('.affine-list-block__prefix');
|
||||
const parentPrefix = prefixes.nth(1);
|
||||
|
||||
expect(await isToggleIconVisible(toggleIcon)).toBe(false);
|
||||
await parentPrefix.hover();
|
||||
await waitNextFrame(page, 200);
|
||||
expect(await isToggleIconVisible(toggleIcon)).toBe(true);
|
||||
|
||||
await page.mouse.move(0, 0);
|
||||
await waitNextFrame(page, 300);
|
||||
expect(await isToggleIconVisible(toggleIcon)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('readonly', () => {
|
||||
test('can expand toggle in readonly mode', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeLists(page);
|
||||
const toggleIcon = page.locator('.toggle-icon');
|
||||
const prefixes = page.locator('.affine-list-block__prefix');
|
||||
const listChildren = page
|
||||
.locator('[data-block-id="4"] .affine-block-children-container')
|
||||
.nth(0);
|
||||
const parentPrefix = prefixes.nth(1);
|
||||
|
||||
await parentPrefix.hover();
|
||||
await waitNextFrame(page, 200);
|
||||
expect(await isToggleIconVisible(toggleIcon)).toBe(true);
|
||||
|
||||
await expect(listChildren).toBeVisible();
|
||||
await toggleIcon.click();
|
||||
await expect(listChildren).not.toBeVisible();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_before_readonly.json`
|
||||
);
|
||||
|
||||
await waitNextFrame(page, 200);
|
||||
await switchReadonly(page);
|
||||
|
||||
await waitNextFrame(page, 200);
|
||||
expect(await isToggleIconVisible(toggleIcon)).toBe(true);
|
||||
|
||||
await expect(listChildren).not.toBeVisible();
|
||||
await toggleIcon.click();
|
||||
await expect(listChildren).toBeVisible();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_before_readonly.json`
|
||||
);
|
||||
|
||||
await toggleIcon.click();
|
||||
await expect(listChildren).not.toBeVisible();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_before_readonly.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('can not modify todo list in readonly mode', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const checkBox = page.locator('.affine-list-block__prefix');
|
||||
|
||||
{
|
||||
await type(page, '[] todo');
|
||||
await switchReadonly(page);
|
||||
await expect(page.locator('.affine-list--checked')).toHaveCount(0);
|
||||
await checkBox.click();
|
||||
await expect(page.locator('.affine-list--checked')).toHaveCount(0);
|
||||
}
|
||||
|
||||
{
|
||||
await switchReadonly(page, false);
|
||||
await checkBox.click();
|
||||
await switchReadonly(page);
|
||||
await expect(page.locator('.affine-list--checked')).toHaveCount(1);
|
||||
await checkBox.click();
|
||||
await expect(page.locator('.affine-list--checked')).toHaveCount(1);
|
||||
}
|
||||
});
|
||||
|
||||
test('should render collapsed list correctly', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
// await switchEditorMode(page);
|
||||
await initThreeLists(page);
|
||||
|
||||
const toggleIcon = page.locator('.toggle-icon');
|
||||
const listChildren = page
|
||||
.locator('[data-block-id="5"] .affine-block-children-container')
|
||||
.nth(0);
|
||||
|
||||
await expect(listChildren).toBeVisible();
|
||||
await toggleIcon.click();
|
||||
await expect(listChildren).not.toBeVisible();
|
||||
|
||||
await switchReadonly(page);
|
||||
// trick for render a readonly doc from scratch
|
||||
await switchEditorMode(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await expect(listChildren).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
565
blocksuite/tests-legacy/e2e/markdown.spec.ts
Normal file
565
blocksuite/tests-legacy/e2e/markdown.spec.ts
Normal file
@@ -0,0 +1,565 @@
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getCursorBlockIdAndHeight,
|
||||
initEmptyParagraphState,
|
||||
pressArrowLeft,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressSpace,
|
||||
redoByKeyboard,
|
||||
resetHistory,
|
||||
type,
|
||||
undoByClick,
|
||||
undoByKeyboard,
|
||||
waitNextFrame,
|
||||
} from './utils/actions/index.js';
|
||||
import {
|
||||
assertBlockType,
|
||||
assertRichTextInlineDeltas,
|
||||
assertRichTextInlineRange,
|
||||
assertRichTexts,
|
||||
assertText,
|
||||
} from './utils/asserts.js';
|
||||
import { test } from './utils/playwright.js';
|
||||
|
||||
test('markdown shortcut', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await resetHistory(page);
|
||||
|
||||
let id: string | null = null;
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '[] ');
|
||||
await waitNextFrame(page);
|
||||
[id] = await getCursorBlockIdAndHeight(page);
|
||||
await assertBlockType(page, id, 'todo');
|
||||
await undoByClick(page);
|
||||
await assertText(page, '[] ');
|
||||
await undoByClick(page);
|
||||
//FIXME: it just failed in playwright
|
||||
await focusRichText(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '[ ] ');
|
||||
await waitNextFrame(page);
|
||||
[id] = await getCursorBlockIdAndHeight(page);
|
||||
await assertBlockType(page, id, 'todo');
|
||||
await undoByClick(page);
|
||||
await assertText(page, '[ ] ');
|
||||
await undoByClick(page);
|
||||
//FIXME: it just failed in playwright
|
||||
await focusRichText(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '[x] ');
|
||||
await waitNextFrame(page);
|
||||
[id] = await getCursorBlockIdAndHeight(page);
|
||||
await assertBlockType(page, id, 'todo');
|
||||
await undoByClick(page);
|
||||
await assertText(page, '[x] ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '* ');
|
||||
await waitNextFrame(page);
|
||||
[id] = await getCursorBlockIdAndHeight(page);
|
||||
await assertBlockType(page, id, 'bulleted');
|
||||
await undoByClick(page);
|
||||
await assertText(page, '* ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '- ');
|
||||
await waitNextFrame(page);
|
||||
[id] = await getCursorBlockIdAndHeight(page);
|
||||
await assertBlockType(page, id, 'bulleted');
|
||||
await undoByClick(page);
|
||||
await assertText(page, '- ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '1. ');
|
||||
await waitNextFrame(page);
|
||||
[id] = await getCursorBlockIdAndHeight(page);
|
||||
await assertBlockType(page, id, 'numbered');
|
||||
await undoByClick(page);
|
||||
await assertText(page, '1. ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '20. ');
|
||||
await waitNextFrame(page);
|
||||
[id] = await getCursorBlockIdAndHeight(page);
|
||||
await assertBlockType(page, id, 'numbered');
|
||||
await undoByClick(page);
|
||||
await assertText(page, '20. ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '# ');
|
||||
await waitNextFrame(page);
|
||||
[id] = await getCursorBlockIdAndHeight(page);
|
||||
await assertBlockType(page, id, 'h1');
|
||||
await undoByClick(page);
|
||||
await assertText(page, '# ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '## ');
|
||||
await waitNextFrame(page);
|
||||
[id] = await getCursorBlockIdAndHeight(page);
|
||||
await assertBlockType(page, id, 'h2');
|
||||
await undoByClick(page);
|
||||
await assertText(page, '## ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '### ');
|
||||
await waitNextFrame(page);
|
||||
[id] = await getCursorBlockIdAndHeight(page);
|
||||
await assertBlockType(page, id, 'h3');
|
||||
await undoByClick(page);
|
||||
await assertText(page, '### ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '#### ');
|
||||
await waitNextFrame(page);
|
||||
[id] = await getCursorBlockIdAndHeight(page);
|
||||
await assertBlockType(page, id, 'h4');
|
||||
await undoByClick(page);
|
||||
await assertText(page, '#### ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '##### ');
|
||||
await waitNextFrame(page);
|
||||
[id] = await getCursorBlockIdAndHeight(page);
|
||||
await assertBlockType(page, id, 'h5');
|
||||
await undoByClick(page);
|
||||
await assertText(page, '##### ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '###### ');
|
||||
await waitNextFrame(page);
|
||||
[id] = await getCursorBlockIdAndHeight(page);
|
||||
await assertBlockType(page, id, 'h6');
|
||||
await undoByClick(page);
|
||||
await assertText(page, '###### ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '> ');
|
||||
await waitNextFrame(page);
|
||||
[id] = await getCursorBlockIdAndHeight(page);
|
||||
await assertBlockType(page, id, 'quote');
|
||||
await undoByClick(page);
|
||||
await assertText(page, '> ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
// testing various horizontal dividers
|
||||
await waitNextFrame(page);
|
||||
await type(page, '--- ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['--- ']);
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '*** ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['*** ']);
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '___ ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['___ ']);
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '------ ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['------ ']);
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '****** ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['****** ']);
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '______ ');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['______ ']);
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test.describe('markdown inline-text', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await resetHistory(page);
|
||||
});
|
||||
|
||||
test('bolditalic', async ({ page }) => {
|
||||
await type(page, 'aa***bb*** ');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa',
|
||||
},
|
||||
{
|
||||
insert: 'bb',
|
||||
attributes: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTextInlineRange(page, 0, 11);
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa***bb*** ',
|
||||
},
|
||||
]);
|
||||
await redoByKeyboard(page);
|
||||
await type(page, 'cc');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa',
|
||||
},
|
||||
{
|
||||
insert: 'bbcc',
|
||||
attributes: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
await undoByKeyboard(page);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '***test *** ');
|
||||
await assertRichTexts(page, ['***test *** ']);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
// *** + space will be converted to divider, so needn't test this case here
|
||||
// await waitNextFrame(page);
|
||||
// await type(page, '*** test*** ');
|
||||
// await assertRichTexts(page, ['*** test*** ']);
|
||||
// await undoByKeyboard(page);
|
||||
// await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test('bold', async ({ page }) => {
|
||||
await type(page, 'aa**bb** ');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa',
|
||||
},
|
||||
{
|
||||
insert: 'bb',
|
||||
attributes: {
|
||||
bold: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTextInlineRange(page, 0, 9);
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa**bb** ',
|
||||
},
|
||||
]);
|
||||
await redoByKeyboard(page);
|
||||
await type(page, 'cc');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa',
|
||||
},
|
||||
{
|
||||
insert: 'bbcc',
|
||||
attributes: {
|
||||
bold: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
await undoByKeyboard(page);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '**test ** ');
|
||||
await assertRichTexts(page, ['**test ** ']);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '** test** ');
|
||||
await assertRichTexts(page, ['** test** ']);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test('italic', async ({ page }) => {
|
||||
await type(page, 'aa*bb* ');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa',
|
||||
},
|
||||
{
|
||||
insert: 'bb',
|
||||
attributes: {
|
||||
italic: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTextInlineRange(page, 0, 7);
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa*bb* ',
|
||||
},
|
||||
]);
|
||||
await redoByKeyboard(page);
|
||||
await type(page, 'cc');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa',
|
||||
},
|
||||
{
|
||||
insert: 'bbcc',
|
||||
attributes: {
|
||||
italic: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
await undoByKeyboard(page);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '*test * ');
|
||||
await assertRichTexts(page, ['*test * ']);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
// * + space will be converted to bulleted list, so needn't test this case here
|
||||
// await waitNextFrame(page);
|
||||
// await type(page, '* test* ');
|
||||
// await assertRichTexts(page, ['* test* ']);
|
||||
// await undoByKeyboard(page);
|
||||
// await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test('strike', async ({ page }) => {
|
||||
await type(page, 'aa~~bb~~ ');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa',
|
||||
},
|
||||
{
|
||||
insert: 'bb',
|
||||
attributes: {
|
||||
strike: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTextInlineRange(page, 0, 9);
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa~~bb~~ ',
|
||||
},
|
||||
]);
|
||||
await redoByKeyboard(page);
|
||||
await type(page, 'cc');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa',
|
||||
},
|
||||
{
|
||||
insert: 'bbcc',
|
||||
attributes: {
|
||||
strike: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
await undoByKeyboard(page);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '~~test ~~ ');
|
||||
await assertRichTexts(page, ['~~test ~~ ']);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '~~ test~~ ');
|
||||
await assertRichTexts(page, ['~~ test~~ ']);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test('underline', async ({ page }) => {
|
||||
await type(page, 'aa~bb~ ');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa',
|
||||
},
|
||||
{
|
||||
insert: 'bb',
|
||||
attributes: {
|
||||
underline: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTextInlineRange(page, 0, 7);
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa~bb~ ',
|
||||
},
|
||||
]);
|
||||
await redoByKeyboard(page);
|
||||
await type(page, 'cc');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa',
|
||||
},
|
||||
{
|
||||
insert: 'bbcc',
|
||||
attributes: {
|
||||
underline: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
await undoByKeyboard(page);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '~test ~ ');
|
||||
await assertRichTexts(page, ['~test ~ ']);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '~ test~ ');
|
||||
await assertRichTexts(page, ['~ test~ ']);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test('code', async ({ page }) => {
|
||||
await type(page, 'aa`bb` ');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa',
|
||||
},
|
||||
{
|
||||
insert: 'bb',
|
||||
attributes: {
|
||||
code: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTextInlineRange(page, 0, 7);
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa`bb` ',
|
||||
},
|
||||
]);
|
||||
await redoByKeyboard(page);
|
||||
await type(page, 'cc');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aa',
|
||||
},
|
||||
{
|
||||
insert: 'bb',
|
||||
attributes: {
|
||||
code: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'cc',
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
await undoByKeyboard(page);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '`test ` ');
|
||||
await assertRichTexts(page, ['`test ` ']);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await type(page, '` test` ');
|
||||
await assertRichTexts(page, ['` test` ']);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
});
|
||||
});
|
||||
|
||||
test('inline code should work when pressing Enter followed by Backspace twice', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, '`test`');
|
||||
await pressSpace(page);
|
||||
await waitNextFrame(page);
|
||||
await pressArrowLeft(page);
|
||||
await waitNextFrame(page);
|
||||
await pressEnter(page);
|
||||
await waitNextFrame(page);
|
||||
await pressBackspace(page);
|
||||
await waitNextFrame(page);
|
||||
await pressEnter(page);
|
||||
await waitNextFrame(page);
|
||||
await pressBackspace(page);
|
||||
|
||||
await assertRichTexts(page, ['test']);
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
switchMultipleEditorsMode,
|
||||
toggleMultipleEditors,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
initThreeParagraphs,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('the shift pressing status should effect all editors', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await toggleMultipleEditors(page);
|
||||
await switchMultipleEditorsMode(page);
|
||||
|
||||
await waitNextFrame(page, 5000);
|
||||
|
||||
const getShiftPressedStatus = async () => {
|
||||
return page.evaluate(() => {
|
||||
const edgelessBlocks = document.querySelectorAll('affine-edgeless-root');
|
||||
|
||||
return Array.from(edgelessBlocks).map(edgelessRoot => {
|
||||
return edgelessRoot.gfx.keyboard.shiftKey$.peek();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
await page.keyboard.down('Shift');
|
||||
const pressed = await getShiftPressedStatus();
|
||||
expect(pressed).toEqual([true, true]);
|
||||
|
||||
await page.keyboard.up('Shift');
|
||||
const released = await getShiftPressedStatus();
|
||||
expect(released).toEqual([false, false]);
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { dragBetweenCoords } from '../utils/actions/drag.js';
|
||||
import { toggleMultipleEditors } from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
initEmptyParagraphState,
|
||||
initThreeParagraphs,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { getRichTextBoundingBox } from '../utils/actions/selection.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('should only show one format bar when multiple editors are toggled', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await toggleMultipleEditors(page);
|
||||
|
||||
// Select some text
|
||||
const box123 = await getRichTextBoundingBox(page, '2');
|
||||
const above123 = { x: box123.left + 10, y: box123.top + 2 };
|
||||
|
||||
const box789 = await getRichTextBoundingBox(page, '4');
|
||||
const bottomRight789 = { x: box789.right - 10, y: box789.bottom - 2 };
|
||||
|
||||
await dragBetweenCoords(page, above123, bottomRight789, { steps: 10 });
|
||||
|
||||
// should only show one format bar
|
||||
const formatBar = page.locator('.affine-format-bar-widget');
|
||||
await expect(formatBar).toHaveCount(1);
|
||||
});
|
||||
1323
blocksuite/tests-legacy/e2e/paragraph.spec.ts
Normal file
1323
blocksuite/tests-legacy/e2e/paragraph.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
102
blocksuite/tests-legacy/e2e/quote.spec.ts
Normal file
102
blocksuite/tests-legacy/e2e/quote.spec.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyParagraphState,
|
||||
pressArrowDown,
|
||||
pressArrowRight,
|
||||
pressArrowUp,
|
||||
pressEnter,
|
||||
type,
|
||||
} from './utils/actions/index.js';
|
||||
import {
|
||||
assertRichTextInlineRange,
|
||||
assertTextContain,
|
||||
} from './utils/asserts.js';
|
||||
import { test } from './utils/playwright.js';
|
||||
|
||||
test('prohibit creating divider within quote', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/995',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '>');
|
||||
await page.keyboard.press('Space', { delay: 50 });
|
||||
await focusRichText(page);
|
||||
await type(page, '123');
|
||||
await pressEnter(page);
|
||||
await type(page, '---');
|
||||
await page.keyboard.press('Space', { delay: 50 });
|
||||
await assertTextContain(page, '---');
|
||||
});
|
||||
|
||||
test('quote arrow up/down', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2834',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'aaaaa');
|
||||
await pressEnter(page);
|
||||
await type(page, 'aaaaa');
|
||||
await pressEnter(page);
|
||||
await type(page, 'aaa');
|
||||
await pressEnter(page);
|
||||
await type(page, '> aaaaaaaaa');
|
||||
await pressEnter(page);
|
||||
await type(page, 'aaa');
|
||||
await pressEnter(page);
|
||||
await type(page, 'aaaaaaaaa');
|
||||
await pressEnter(page);
|
||||
await pressEnter(page);
|
||||
await type(page, 'aaaaa');
|
||||
await pressEnter(page);
|
||||
await type(page, 'aaaaa');
|
||||
await pressEnter(page);
|
||||
await type(page, 'aaa');
|
||||
|
||||
await assertRichTextInlineRange(page, 6, 3, 0);
|
||||
await pressArrowUp(page);
|
||||
await assertRichTextInlineRange(page, 5, 3, 0);
|
||||
await pressArrowUp(page);
|
||||
await assertRichTextInlineRange(page, 4, 3, 0);
|
||||
await pressArrowUp(page);
|
||||
await assertRichTextInlineRange(page, 3, 15, 0);
|
||||
await pressArrowRight(page, 8);
|
||||
await assertRichTextInlineRange(page, 3, 23, 0);
|
||||
await pressArrowUp(page);
|
||||
await assertRichTextInlineRange(page, 3, 13, 0);
|
||||
await pressArrowUp(page);
|
||||
await assertRichTextInlineRange(page, 3, 9, 0);
|
||||
await pressArrowUp(page);
|
||||
await assertRichTextInlineRange(page, 2, 3, 0);
|
||||
await pressArrowUp(page);
|
||||
await assertRichTextInlineRange(page, 1, 5, 0);
|
||||
await pressArrowUp(page);
|
||||
await assertRichTextInlineRange(page, 0, 5, 0);
|
||||
await pressArrowUp(page);
|
||||
await assertRichTextInlineRange(page, 0, 0, 0);
|
||||
await pressArrowRight(page, 4);
|
||||
await assertRichTextInlineRange(page, 0, 4, 0);
|
||||
await pressArrowDown(page);
|
||||
await assertRichTextInlineRange(page, 1, 4, 0);
|
||||
await pressArrowDown(page);
|
||||
await assertRichTextInlineRange(page, 2, 3, 0);
|
||||
await pressArrowDown(page);
|
||||
await assertRichTextInlineRange(page, 3, 2, 0);
|
||||
await pressArrowRight(page, 8);
|
||||
await assertRichTextInlineRange(page, 3, 10, 0);
|
||||
await pressArrowDown(page);
|
||||
await assertRichTextInlineRange(page, 3, 14, 0);
|
||||
await pressArrowDown(page);
|
||||
await assertRichTextInlineRange(page, 4, 2, 0);
|
||||
await pressArrowDown(page);
|
||||
await assertRichTextInlineRange(page, 5, 2, 0);
|
||||
await pressArrowDown(page);
|
||||
await assertRichTextInlineRange(page, 6, 2, 0);
|
||||
});
|
||||
1421
blocksuite/tests-legacy/e2e/selection/block.spec.ts
Normal file
1421
blocksuite/tests-legacy/e2e/selection/block.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
1781
blocksuite/tests-legacy/e2e/selection/native.spec.ts
Normal file
1781
blocksuite/tests-legacy/e2e/selection/native.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
942
blocksuite/tests-legacy/e2e/slash-menu.spec.ts
Normal file
942
blocksuite/tests-legacy/e2e/slash-menu.spec.ts
Normal file
@@ -0,0 +1,942 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { addNote, switchEditorMode } from './utils/actions/edgeless.js';
|
||||
import {
|
||||
pressArrowDown,
|
||||
pressArrowLeft,
|
||||
pressArrowRight,
|
||||
pressArrowUp,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
pressShiftEnter,
|
||||
pressShiftTab,
|
||||
pressTab,
|
||||
redoByKeyboard,
|
||||
SHORT_KEY,
|
||||
type,
|
||||
undoByKeyboard,
|
||||
} from './utils/actions/keyboard.js';
|
||||
import {
|
||||
captureHistory,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getInlineSelectionText,
|
||||
getPageSnapshot,
|
||||
getSelectionRect,
|
||||
initEmptyEdgelessState,
|
||||
initEmptyParagraphState,
|
||||
insertThreeLevelLists,
|
||||
waitNextFrame,
|
||||
} from './utils/actions/misc.js';
|
||||
import {
|
||||
assertAlmostEqual,
|
||||
assertBlockCount,
|
||||
assertExists,
|
||||
assertRichTexts,
|
||||
} from './utils/asserts.js';
|
||||
import { test } from './utils/playwright.js';
|
||||
|
||||
test.describe('slash menu should show and hide correctly', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
});
|
||||
|
||||
test("slash menu should show when user input '/'", async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await focusRichText(page);
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
});
|
||||
|
||||
// Playwright dose not support IME
|
||||
// https://github.com/microsoft/playwright/issues/5777
|
||||
test.skip("slash menu should show when user input '、'", async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await focusRichText(page);
|
||||
await type(page, '、');
|
||||
|
||||
await expect(slashMenu).toBeVisible();
|
||||
});
|
||||
|
||||
test('slash menu should hide after click away', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await initEmptyParagraphState(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await focusRichText(page);
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
// Click outside should close slash menu
|
||||
await page.mouse.click(0, 50);
|
||||
await expect(slashMenu).toBeHidden();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('slash menu should hide after input whitespace', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await focusRichText(page);
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await type(page, ' ');
|
||||
await expect(slashMenu).toBeHidden();
|
||||
await assertRichTexts(page, ['/ ']);
|
||||
await pressBackspace(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
await type(page, 'head');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await type(page, ' ');
|
||||
await expect(slashMenu).toBeHidden();
|
||||
await pressBackspace(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
});
|
||||
|
||||
test('delete the slash symbol should close the slash menu', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await initEmptyParagraphState(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await focusRichText(page);
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
await pressBackspace(page);
|
||||
await expect(slashMenu).toBeHidden();
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('typing something that does not match should close the slash menu', async ({
|
||||
page,
|
||||
}) => {
|
||||
await initEmptyParagraphState(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await focusRichText(page);
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
await type(page, '_');
|
||||
await expect(slashMenu).toBeHidden();
|
||||
await assertRichTexts(page, ['/_']);
|
||||
|
||||
// And pressing backspace immediately should reappear the slash menu
|
||||
await pressBackspace(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
await type(page, '__');
|
||||
await pressBackspace(page);
|
||||
await expect(slashMenu).toBeHidden();
|
||||
});
|
||||
|
||||
test('pressing the slash key again should close the old slash menu and open new one', async ({
|
||||
page,
|
||||
}) => {
|
||||
await initEmptyParagraphState(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await focusRichText(page);
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashMenu).toHaveCount(1);
|
||||
await assertRichTexts(page, ['//']);
|
||||
});
|
||||
|
||||
test('should position slash menu correctly', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await focusRichText(page);
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const box = await slashMenu.boundingBox();
|
||||
if (!box) {
|
||||
throw new Error("slashMenu doesn't exist");
|
||||
}
|
||||
const rect = await getSelectionRect(page);
|
||||
const { x, y } = box;
|
||||
assertAlmostEqual(x - rect.x, 0, 10);
|
||||
assertAlmostEqual(y - rect.bottom, 5, 10);
|
||||
});
|
||||
|
||||
test('should move up down with arrow key', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await focusRichText(page);
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const slashItems = slashMenu.locator('icon-button');
|
||||
|
||||
await pressArrowDown(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems.nth(1)).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.nth(1).locator('.text')).toHaveText(['Heading 1']);
|
||||
await assertRichTexts(page, ['/']);
|
||||
|
||||
await pressArrowUp(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems.first()).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.first().locator('.text')).toHaveText(['Text']);
|
||||
await assertRichTexts(page, ['/']);
|
||||
|
||||
await pressArrowUp(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems.last()).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.last().locator('.text')).toHaveText(['Delete']);
|
||||
await assertRichTexts(page, ['/']);
|
||||
|
||||
await pressArrowDown(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems.first()).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.first().locator('.text')).toHaveText(['Text']);
|
||||
await assertRichTexts(page, ['/']);
|
||||
});
|
||||
|
||||
test('slash menu hover state', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await focusRichText(page);
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const slashItems = slashMenu.locator('icon-button');
|
||||
|
||||
await pressArrowDown(page);
|
||||
await expect(slashItems.nth(1)).toHaveAttribute('hover', 'true');
|
||||
|
||||
await pressArrowUp(page);
|
||||
await expect(slashItems.nth(1)).toHaveAttribute('hover', 'false');
|
||||
await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true');
|
||||
|
||||
await pressArrowDown(page);
|
||||
await pressArrowDown(page);
|
||||
await expect(slashItems.nth(2)).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.nth(1)).toHaveAttribute('hover', 'false');
|
||||
await expect(slashItems.nth(0)).toHaveAttribute('hover', 'false');
|
||||
|
||||
await slashItems.nth(0).hover();
|
||||
await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.nth(2)).toHaveAttribute('hover', 'false');
|
||||
await expect(slashItems.nth(1)).toHaveAttribute('hover', 'false');
|
||||
});
|
||||
|
||||
test('should open tooltip when hover on item', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '/');
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const slashItems = slashMenu.locator('icon-button');
|
||||
const tooltip = page.locator('.affine-tooltip');
|
||||
|
||||
await slashItems.nth(0).hover();
|
||||
await expect(tooltip).toBeVisible();
|
||||
await expect(tooltip.locator('.tooltip-caption')).toHaveText(['Text']);
|
||||
await page.mouse.move(0, 0);
|
||||
await expect(tooltip).toBeHidden();
|
||||
|
||||
await slashItems.nth(1).hover();
|
||||
await expect(tooltip).toBeVisible();
|
||||
await expect(tooltip.locator('.tooltip-caption')).toHaveText([
|
||||
'Heading #1',
|
||||
]);
|
||||
await page.mouse.move(0, 0);
|
||||
await expect(tooltip).toBeHidden();
|
||||
|
||||
await expect(slashItems.nth(4).locator('.text')).toHaveText([
|
||||
'Other Headings',
|
||||
]);
|
||||
await slashItems.nth(4).hover();
|
||||
await expect(tooltip).toBeHidden();
|
||||
});
|
||||
|
||||
test('press tab should move up and down', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await focusRichText(page);
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const slashItems = slashMenu.locator('icon-button');
|
||||
|
||||
await pressTab(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems.nth(1)).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.nth(1).locator('.text')).toHaveText(['Heading 1']);
|
||||
await assertRichTexts(page, ['/']);
|
||||
|
||||
await pressShiftTab(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems.first()).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.first().locator('.text')).toHaveText(['Text']);
|
||||
await assertRichTexts(page, ['/']);
|
||||
|
||||
await pressShiftTab(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems.last()).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.last().locator('.text')).toHaveText(['Delete']);
|
||||
await assertRichTexts(page, ['/']);
|
||||
|
||||
await pressTab(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems.first()).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.first().locator('.text')).toHaveText(['Text']);
|
||||
await assertRichTexts(page, ['/']);
|
||||
});
|
||||
|
||||
test('should move up down with ctrl/cmd+n and ctrl/cmd+p', async ({
|
||||
page,
|
||||
}) => {
|
||||
await initEmptyParagraphState(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await focusRichText(page);
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const slashItems = slashMenu.locator('icon-button');
|
||||
|
||||
await page.keyboard.press(`${SHORT_KEY}+n`);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems.nth(1)).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.nth(1).locator('.text')).toHaveText(['Heading 1']);
|
||||
await assertRichTexts(page, ['/']);
|
||||
|
||||
await page.keyboard.press(`${SHORT_KEY}+p`);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems.first()).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.first().locator('.text')).toHaveText(['Text']);
|
||||
await assertRichTexts(page, ['/']);
|
||||
|
||||
await page.keyboard.press(`${SHORT_KEY}+p`);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems.last()).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.last().locator('.text')).toHaveText(['Delete']);
|
||||
await assertRichTexts(page, ['/']);
|
||||
|
||||
await page.keyboard.press(`${SHORT_KEY}+n`);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems.first()).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.first().locator('.text')).toHaveText(['Text']);
|
||||
await assertRichTexts(page, ['/']);
|
||||
});
|
||||
|
||||
test('should open sub menu when hover on SubMenuItem', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, '/');
|
||||
const slashMenu = page.locator('.slash-menu[data-testid=sub-menu-0]');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const slashItems = slashMenu.locator('icon-button');
|
||||
|
||||
const subMenu = page.locator('.slash-menu[data-testid=sub-menu-1]');
|
||||
|
||||
let rect = await slashItems.nth(4).boundingBox();
|
||||
assertExists(rect);
|
||||
await page.mouse.move(rect.x + 10, rect.y + 10);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems.nth(4)).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.nth(4).locator('.text')).toHaveText([
|
||||
'Other Headings',
|
||||
]);
|
||||
await expect(subMenu).toBeVisible();
|
||||
|
||||
rect = await slashItems.nth(3).boundingBox();
|
||||
assertExists(rect);
|
||||
await page.mouse.move(rect.x + 10, rect.y + 10);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems.nth(3)).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.nth(3).locator('.text')).toHaveText(['Heading 3']);
|
||||
await expect(subMenu).toBeHidden();
|
||||
});
|
||||
|
||||
test('should open and close menu when using left right arrow, Enter, Esc keys', async ({
|
||||
page,
|
||||
}) => {
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const slashMenu = page.locator('.slash-menu[data-testid=sub-menu-0]');
|
||||
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await pressEscape(page);
|
||||
await expect(slashMenu).toBeHidden();
|
||||
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await pressArrowLeft(page);
|
||||
await expect(slashMenu).toBeHidden();
|
||||
|
||||
// Test sub menu case
|
||||
const slashItems = slashMenu.locator('icon-button');
|
||||
|
||||
await type(page, '/');
|
||||
await pressArrowDown(page, 4);
|
||||
await expect(slashItems.nth(4)).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.nth(4).locator('.text')).toHaveText([
|
||||
'Other Headings',
|
||||
]);
|
||||
|
||||
const subMenu = page.locator('.slash-menu[data-testid=sub-menu-1]');
|
||||
|
||||
await pressArrowRight(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(subMenu).toBeVisible();
|
||||
|
||||
await pressArrowLeft(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(subMenu).toBeHidden();
|
||||
|
||||
await pressEnter(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(subMenu).toBeVisible();
|
||||
|
||||
await pressEscape(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(subMenu).toBeHidden();
|
||||
});
|
||||
|
||||
test('show close current all submenu when typing', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const slashMenu = page.locator('.slash-menu[data-testid=sub-menu-0]');
|
||||
const subMenu = page.locator('.slash-menu[data-testid=sub-menu-1]');
|
||||
const slashItems = slashMenu.locator('icon-button');
|
||||
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await pressArrowDown(page, 4);
|
||||
await expect(slashItems.nth(4)).toHaveAttribute('hover', 'true');
|
||||
await expect(slashItems.nth(4).locator('.text')).toHaveText([
|
||||
'Other Headings',
|
||||
]);
|
||||
await pressEnter(page);
|
||||
await expect(subMenu).toBeVisible();
|
||||
|
||||
await type(page, 'h');
|
||||
await expect(subMenu).toBeHidden();
|
||||
});
|
||||
|
||||
test('should allow only pressing modifier key', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
await page.keyboard.press(SHORT_KEY);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
await page.keyboard.press('Shift');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
});
|
||||
|
||||
test('should allow other hotkey to passthrough', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
await page.keyboard.press(`${SHORT_KEY}+a`);
|
||||
await expect(slashMenu).toBeHidden();
|
||||
await assertRichTexts(page, ['hello', 'world/']);
|
||||
|
||||
const selected = await getInlineSelectionText(page);
|
||||
expect(selected).toBe('world/');
|
||||
});
|
||||
|
||||
test('can input search input after click menu', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await focusRichText(page);
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const box = await slashMenu.boundingBox();
|
||||
if (!box) {
|
||||
throw new Error("slashMenu doesn't exist");
|
||||
}
|
||||
const { x, y } = box;
|
||||
await page.mouse.click(x + 10, y + 10);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await type(page, 'a');
|
||||
await assertRichTexts(page, ['/a']);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('slash menu should not be shown in ignored blocks', () => {
|
||||
test('code block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, '```');
|
||||
await pressEnter(page);
|
||||
await type(page, '/');
|
||||
await expect(page.locator('.slash-menu')).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test('should slash menu works with fast type', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'a/text', 0);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
});
|
||||
|
||||
test('should clean slash string after soft enter', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/1126',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressShiftEnter(page);
|
||||
await waitNextFrame(page);
|
||||
await type(page, '/copy');
|
||||
await pressEnter(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
});
|
||||
|
||||
test.describe('slash search', () => {
|
||||
test('should slash menu search and keyboard works', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
const slashItems = slashMenu.locator('icon-button');
|
||||
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
// search should active the first item
|
||||
await type(page, 'co');
|
||||
await expect(slashItems).toHaveCount(3);
|
||||
await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']);
|
||||
await expect(slashItems.nth(1).locator('.text')).toHaveText(['Code Block']);
|
||||
await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true');
|
||||
|
||||
await type(page, 'p');
|
||||
await expect(slashItems).toHaveCount(1);
|
||||
await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']);
|
||||
|
||||
// assert backspace works
|
||||
await pressBackspace(page);
|
||||
await expect(slashItems).toHaveCount(3);
|
||||
await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']);
|
||||
await expect(slashItems.nth(1).locator('.text')).toHaveText(['Code Block']);
|
||||
await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true');
|
||||
});
|
||||
|
||||
test('slash menu supports fuzzy search', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
const slashItems = slashMenu.locator('icon-button');
|
||||
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
await type(page, 'c');
|
||||
await expect(slashItems).toHaveCount(8);
|
||||
await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']);
|
||||
await expect(slashItems.nth(1).locator('.text')).toHaveText(['Italic']);
|
||||
await expect(slashItems.nth(2).locator('.text')).toHaveText(['New Doc']);
|
||||
await expect(slashItems.nth(3).locator('.text')).toHaveText(['Duplicate']);
|
||||
await expect(slashItems.nth(4).locator('.text')).toHaveText(['Code Block']);
|
||||
await expect(slashItems.nth(5).locator('.text')).toHaveText(['Linked Doc']);
|
||||
await expect(slashItems.nth(6).locator('.text')).toHaveText(['Attachment']);
|
||||
await type(page, 'b');
|
||||
await expect(slashItems.nth(0).locator('.text')).toHaveText(['Code Block']);
|
||||
});
|
||||
|
||||
test('slash menu supports alias search', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, '/');
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const slashItems = slashMenu.locator('icon-button');
|
||||
await type(page, 'database');
|
||||
await expect(slashItems).toHaveCount(2);
|
||||
await expect(slashItems.nth(0).locator('.text')).toHaveText(['Table View']);
|
||||
await expect(slashItems.nth(1).locator('.text')).toHaveText([
|
||||
'Kanban View',
|
||||
]);
|
||||
await type(page, 'v');
|
||||
await expect(slashItems).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('should focus on code blocks created by the slash menu', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '000');
|
||||
|
||||
await type(page, '/code');
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const codeBlock = page.getByTestId('Code Block');
|
||||
await codeBlock.click();
|
||||
await expect(slashMenu).toBeHidden();
|
||||
|
||||
await focusRichText(page); // FIXME: flaky selection asserter
|
||||
await type(page, '111');
|
||||
await assertRichTexts(page, ['000111']);
|
||||
});
|
||||
|
||||
// Selection is not yet available in edgeless
|
||||
test('slash menu should work in edgeless mode', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addNote(page, '/', 30, 40);
|
||||
await assertRichTexts(page, ['', '/']);
|
||||
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
});
|
||||
|
||||
test.describe('slash menu with date & time', () => {
|
||||
test("should insert Today's time string", async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, '/');
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const todayBlock = page.getByTestId('Today');
|
||||
await todayBlock.click();
|
||||
await expect(slashMenu).toBeHidden();
|
||||
|
||||
const date = new Date();
|
||||
const strTime = date.toISOString().split('T')[0];
|
||||
|
||||
await assertRichTexts(page, [strTime]);
|
||||
});
|
||||
|
||||
test("should create Tomorrow's time string", async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, '/');
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const todayBlock = page.getByTestId('Tomorrow');
|
||||
await todayBlock.click();
|
||||
await expect(slashMenu).toBeHidden();
|
||||
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + 1);
|
||||
const strTime = date.toISOString().split('T')[0];
|
||||
|
||||
await assertRichTexts(page, [strTime]);
|
||||
});
|
||||
|
||||
test("should insert Yesterday's time string", async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, '/');
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const todayBlock = page.getByTestId('Yesterday');
|
||||
await todayBlock.click();
|
||||
await expect(slashMenu).toBeHidden();
|
||||
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 1);
|
||||
const strTime = date.toISOString().split('T')[0];
|
||||
|
||||
await assertRichTexts(page, [strTime]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('slash menu with style', () => {
|
||||
test('should style text line works', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'hello/');
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
const bold = page.getByTestId('Bold');
|
||||
await bold.click();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('should style empty line works', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, '/');
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
const bold = page.getByTestId('Bold');
|
||||
await bold.click();
|
||||
await page.waitForTimeout(50);
|
||||
await type(page, 'hello');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('should insert database', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await assertBlockCount(page, 'paragraph', 1);
|
||||
await type(page, '/');
|
||||
const tableBlock = page.getByTestId('Table View');
|
||||
await tableBlock.click();
|
||||
await assertBlockCount(page, 'paragraph', 0);
|
||||
await assertBlockCount(page, 'database', 1);
|
||||
|
||||
const database = page.locator('affine-database');
|
||||
await expect(database).toBeVisible();
|
||||
const titleColumn = page.locator('.affine-database-column').nth(0);
|
||||
expect(await titleColumn.innerText()).toBe('Title');
|
||||
const defaultRows = page.locator('.affine-database-block-row');
|
||||
expect(await defaultRows.count()).toBe(3);
|
||||
});
|
||||
|
||||
test.describe('slash menu with customize menu', () => {
|
||||
test('can remove specified menus', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await page.evaluate(async () => {
|
||||
// https://github.com/lit/lit/blob/84df6ef8c73fffec92384891b4b031d7efc01a64/packages/lit-html/src/static.ts#L93
|
||||
const fakeLiteral = (strings: TemplateStringsArray) =>
|
||||
({
|
||||
['_$litStatic$']: strings[0],
|
||||
r: Symbol.for(''),
|
||||
}) as const;
|
||||
|
||||
const editor = document.querySelector('affine-editor-container');
|
||||
if (!editor) throw new Error("Can't find affine-editor-container");
|
||||
|
||||
const SlashMenuWidget = window.$blocksuite.blocks.AffineSlashMenuWidget;
|
||||
class CustomSlashMenu extends SlashMenuWidget {
|
||||
override config = {
|
||||
...SlashMenuWidget.DEFAULT_CONFIG,
|
||||
items: [
|
||||
{ groupName: 'custom-group' },
|
||||
...SlashMenuWidget.DEFAULT_CONFIG.items
|
||||
.filter(item => 'action' in item)
|
||||
.slice(0, 5),
|
||||
],
|
||||
};
|
||||
}
|
||||
// Fix `Illegal constructor` error
|
||||
// see https://stackoverflow.com/questions/41521812/illegal-constructor-with-ecmascript-6
|
||||
customElements.define('affine-custom-slash-menu', CustomSlashMenu);
|
||||
|
||||
const pageSpecs = window.$blocksuite.blocks.PageEditorBlockSpecs;
|
||||
editor.pageSpecs = [
|
||||
...pageSpecs,
|
||||
{
|
||||
setup: di => {
|
||||
di.override(
|
||||
window.$blocksuite.blockStd.WidgetViewIdentifier(
|
||||
'affine:page|affine-slash-menu-widget'
|
||||
),
|
||||
// @ts-ignore
|
||||
fakeLiteral`affine-custom-slash-menu`
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
await editor.updateComplete;
|
||||
});
|
||||
|
||||
await focusRichText(page);
|
||||
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
const slashItems = slashMenu.locator('icon-button');
|
||||
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems).toHaveCount(5);
|
||||
});
|
||||
|
||||
test('can add some menus', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await page.evaluate(async () => {
|
||||
// https://github.com/lit/lit/blob/84df6ef8c73fffec92384891b4b031d7efc01a64/packages/lit-html/src/static.ts#L93
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
const fakeLiteral = (strings: TemplateStringsArray) =>
|
||||
({
|
||||
['_$litStatic$']: strings[0],
|
||||
r: Symbol.for(''),
|
||||
}) as const;
|
||||
|
||||
const editor = document.querySelector('affine-editor-container');
|
||||
if (!editor) throw new Error("Can't find affine-editor-container");
|
||||
const SlashMenuWidget = window.$blocksuite.blocks.AffineSlashMenuWidget;
|
||||
|
||||
class CustomSlashMenu extends SlashMenuWidget {
|
||||
override config = {
|
||||
...SlashMenuWidget.DEFAULT_CONFIG,
|
||||
items: [
|
||||
{ groupName: 'Custom Menu' },
|
||||
{
|
||||
name: 'Custom Menu Item',
|
||||
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
icon: '' as any,
|
||||
action: () => {
|
||||
// do nothing
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Custom Menu Item',
|
||||
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
icon: '' as any,
|
||||
action: () => {
|
||||
// do nothing
|
||||
},
|
||||
showWhen: () => false,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
// Fix `Illegal constructor` error
|
||||
// see https://stackoverflow.com/questions/41521812/illegal-constructor-with-ecmascript-6
|
||||
customElements.define('affine-custom-slash-menu', CustomSlashMenu);
|
||||
|
||||
const pageSpecs = window.$blocksuite.blocks.PageEditorBlockSpecs;
|
||||
editor.pageSpecs = [
|
||||
...pageSpecs,
|
||||
{
|
||||
setup: di =>
|
||||
di.override(
|
||||
window.$blocksuite.blockStd.WidgetViewIdentifier(
|
||||
'affine:page|affine-slash-menu-widget'
|
||||
),
|
||||
// @ts-ignore
|
||||
fakeLiteral`affine-custom-slash-menu`
|
||||
),
|
||||
},
|
||||
];
|
||||
await editor.updateComplete;
|
||||
});
|
||||
|
||||
await focusRichText(page);
|
||||
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
const slashItems = slashMenu.locator('icon-button');
|
||||
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await expect(slashItems).toHaveCount(1);
|
||||
});
|
||||
});
|
||||
|
||||
test('move block up and down by slash menu', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
await assertRichTexts(page, ['hello', 'world']);
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const moveUp = page.getByTestId('Move Up');
|
||||
await moveUp.click();
|
||||
await assertRichTexts(page, ['world', 'hello']);
|
||||
await type(page, '/');
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const moveDown = page.getByTestId('Move Down');
|
||||
await moveDown.click();
|
||||
await assertRichTexts(page, ['hello', 'world']);
|
||||
});
|
||||
|
||||
test('delete block by slash menu should remove children', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await insertThreeLevelLists(page);
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
const slashItems = slashMenu.locator('icon-button');
|
||||
|
||||
await captureHistory(page);
|
||||
await focusRichText(page, 1);
|
||||
await waitNextFrame(page);
|
||||
await type(page, '/');
|
||||
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await type(page, 'remove');
|
||||
await expect(slashItems).toHaveCount(1);
|
||||
await pressEnter(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
await redoByKeyboard(page);
|
||||
await assertRichTexts(page, ['123']);
|
||||
});
|
||||
22
blocksuite/tests-legacy/e2e/utils/actions/block.ts
Normal file
22
blocksuite/tests-legacy/e2e/utils/actions/block.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
import { waitNextFrame } from './misc.js';
|
||||
|
||||
export async function updateBlockType(
|
||||
page: Page,
|
||||
flavour: string,
|
||||
type?: string
|
||||
) {
|
||||
await page.evaluate(
|
||||
([flavour, type]) => {
|
||||
window.host.std.command.exec(window.$blocksuite.blocks.updateBlockType, {
|
||||
flavour,
|
||||
props: {
|
||||
type,
|
||||
},
|
||||
});
|
||||
},
|
||||
[flavour, type] as [string, string?]
|
||||
);
|
||||
await waitNextFrame(page, 400);
|
||||
}
|
||||
129
blocksuite/tests-legacy/e2e/utils/actions/click.ts
Normal file
129
blocksuite/tests-legacy/e2e/utils/actions/click.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import type { IPoint } from '@blocksuite/global/utils';
|
||||
import type { Store } from '@blocksuite/store';
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
import { toViewCoord } from './edgeless.js';
|
||||
import { waitNextFrame } from './misc.js';
|
||||
|
||||
export function getDebugMenu(page: Page) {
|
||||
const debugMenu = page.locator('starter-debug-menu');
|
||||
return {
|
||||
debugMenu,
|
||||
undoBtn: debugMenu.locator('sl-tooltip[content="Undo"]'),
|
||||
redoBtn: debugMenu.locator('sl-tooltip[content="Redo"]'),
|
||||
|
||||
blockTypeButton: debugMenu.getByRole('button', { name: 'Block Type' }),
|
||||
testOperationsButton: debugMenu.getByRole('button', {
|
||||
name: 'Test Operations',
|
||||
}),
|
||||
|
||||
pagesBtn: debugMenu.getByTestId('docs-button'),
|
||||
};
|
||||
}
|
||||
|
||||
export async function moveView(page: Page, point: [number, number]) {
|
||||
const [x, y] = await toViewCoord(page, point);
|
||||
await page.mouse.move(x, y);
|
||||
}
|
||||
|
||||
export async function click(page: Page, point: IPoint) {
|
||||
await page.mouse.click(point.x, point.y);
|
||||
}
|
||||
|
||||
export async function clickView(page: Page, point: [number, number]) {
|
||||
const [x, y] = await toViewCoord(page, point);
|
||||
await page.mouse.click(x, y);
|
||||
}
|
||||
|
||||
export async function dblclickView(page: Page, point: [number, number]) {
|
||||
const [x, y] = await toViewCoord(page, point);
|
||||
await page.mouse.dblclick(x, y);
|
||||
}
|
||||
|
||||
export async function undoByClick(page: Page) {
|
||||
await getDebugMenu(page).undoBtn.click();
|
||||
}
|
||||
|
||||
export async function redoByClick(page: Page) {
|
||||
await getDebugMenu(page).redoBtn.click();
|
||||
}
|
||||
|
||||
export async function clickBlockById(page: Page, id: string) {
|
||||
await page.click(`[data-block-id="${id}"]`);
|
||||
}
|
||||
|
||||
export async function doubleClickBlockById(page: Page, id: string) {
|
||||
await page.dblclick(`[data-block-id="${id}"]`);
|
||||
}
|
||||
|
||||
export async function disconnectByClick(page: Page) {
|
||||
await clickTestOperationsMenuItem(page, 'Disconnect');
|
||||
}
|
||||
|
||||
export async function connectByClick(page: Page) {
|
||||
await clickTestOperationsMenuItem(page, 'Connect');
|
||||
}
|
||||
|
||||
export async function addNoteByClick(page: Page) {
|
||||
await clickTestOperationsMenuItem(page, 'Add Note');
|
||||
}
|
||||
|
||||
export async function addNewPage(page: Page) {
|
||||
const { pagesBtn } = getDebugMenu(page);
|
||||
if (!(await page.locator('docs-panel').isVisible())) {
|
||||
await pagesBtn.click();
|
||||
}
|
||||
await page.locator('.new-doc-button').click();
|
||||
const docMetas = await page.evaluate(() => {
|
||||
const { collection } = window;
|
||||
return collection.meta.docMetas;
|
||||
});
|
||||
if (!docMetas.length) throw new Error('Add new doc failed');
|
||||
return docMetas[docMetas.length - 1];
|
||||
}
|
||||
|
||||
export async function switchToPage(page: Page, docId?: string) {
|
||||
await page.evaluate(docId => {
|
||||
const { collection, editor } = window;
|
||||
|
||||
if (!docId) {
|
||||
const docMetas = collection.meta.docMetas;
|
||||
if (!docMetas.length) return;
|
||||
docId = docMetas[0].id;
|
||||
}
|
||||
|
||||
const doc = collection.getDoc(docId);
|
||||
if (!doc) return;
|
||||
editor.doc = doc;
|
||||
}, docId);
|
||||
}
|
||||
|
||||
export async function clickTestOperationsMenuItem(page: Page, name: string) {
|
||||
const menuButton = getDebugMenu(page).testOperationsButton;
|
||||
await menuButton.click();
|
||||
await waitNextFrame(page); // wait for animation ended
|
||||
|
||||
const menuItem = page.getByRole('menuitem', { name });
|
||||
await menuItem.click();
|
||||
await menuItem.waitFor({ state: 'hidden' }); // wait for animation ended
|
||||
}
|
||||
|
||||
export async function switchReadonly(page: Page, value = true) {
|
||||
await page.evaluate(_value => {
|
||||
const defaultPage = document.querySelector(
|
||||
'affine-page-root,affine-edgeless-root'
|
||||
) as HTMLElement & {
|
||||
doc: Store;
|
||||
};
|
||||
const doc = defaultPage.doc;
|
||||
doc.readonly = _value;
|
||||
}, value);
|
||||
}
|
||||
|
||||
export async function activeEmbed(page: Page) {
|
||||
await page.click('.resizable-img');
|
||||
}
|
||||
|
||||
export async function toggleDarkMode(page: Page) {
|
||||
await page.click('sl-tooltip[content="Toggle Dark Mode"] sl-button');
|
||||
}
|
||||
271
blocksuite/tests-legacy/e2e/utils/actions/drag.ts
Normal file
271
blocksuite/tests-legacy/e2e/utils/actions/drag.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
import { assertImageOption } from '../asserts.js';
|
||||
import { getIndexCoordinate, waitNextFrame } from './misc.js';
|
||||
|
||||
export async function dragBetweenCoords(
|
||||
page: Page,
|
||||
from: { x: number; y: number },
|
||||
to: { x: number; y: number },
|
||||
options?: {
|
||||
beforeMouseUp?: () => Promise<void>;
|
||||
steps?: number;
|
||||
click?: boolean;
|
||||
button?: 'left' | 'right' | 'middle';
|
||||
}
|
||||
) {
|
||||
const steps = options?.steps ?? 20;
|
||||
const button: 'left' | 'right' | 'middle' = options?.button ?? 'left';
|
||||
|
||||
const { x: x1, y: y1 } = from;
|
||||
const { x: x2, y: y2 } = to;
|
||||
options?.click && (await page.mouse.click(x1, y1));
|
||||
await page.mouse.move(x1, y1);
|
||||
await page.mouse.down({ button });
|
||||
await page.mouse.move(x2, y2, { steps });
|
||||
await options?.beforeMouseUp?.();
|
||||
await page.mouse.up({ button });
|
||||
}
|
||||
|
||||
export async function dragBetweenIndices(
|
||||
page: Page,
|
||||
[startRichTextIndex, startVIndex]: [number, number],
|
||||
[endRichTextIndex, endVIndex]: [number, number],
|
||||
startCoordOffSet: { x: number; y: number } = { x: 0, y: 0 },
|
||||
endCoordOffSet: { x: number; y: number } = { x: 0, y: 0 },
|
||||
options?: {
|
||||
beforeMouseUp?: () => Promise<void>;
|
||||
steps?: number;
|
||||
click?: boolean;
|
||||
}
|
||||
) {
|
||||
const finalOptions = {
|
||||
steps: 50,
|
||||
...options,
|
||||
};
|
||||
const startCoord = await getIndexCoordinate(
|
||||
page,
|
||||
[startRichTextIndex, startVIndex],
|
||||
startCoordOffSet
|
||||
);
|
||||
const endCoord = await getIndexCoordinate(
|
||||
page,
|
||||
[endRichTextIndex, endVIndex],
|
||||
endCoordOffSet
|
||||
);
|
||||
|
||||
await dragBetweenCoords(page, startCoord, endCoord, finalOptions);
|
||||
}
|
||||
|
||||
export async function dragOverTitle(page: Page) {
|
||||
const { from, to } = await page.evaluate(() => {
|
||||
const titleInput = document.querySelector(
|
||||
'doc-title rich-text'
|
||||
) as HTMLTextAreaElement;
|
||||
const titleBound = titleInput.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
from: { x: titleBound.left + 1, y: titleBound.top + 1 },
|
||||
to: { x: titleBound.right - 1, y: titleBound.bottom - 1 },
|
||||
};
|
||||
});
|
||||
await dragBetweenCoords(page, from, to, {
|
||||
steps: 5,
|
||||
});
|
||||
}
|
||||
|
||||
export async function dragEmbedResizeByTopRight(page: Page) {
|
||||
const { from, to } = await page.evaluate(() => {
|
||||
const bottomRightButton = document.querySelector(
|
||||
'.top-right'
|
||||
) as HTMLInputElement;
|
||||
const bottomRightButtonBound = bottomRightButton.getBoundingClientRect();
|
||||
const y = bottomRightButtonBound.top;
|
||||
return {
|
||||
from: { x: bottomRightButtonBound.left + 5, y: y + 5 },
|
||||
to: { x: bottomRightButtonBound.left + 5 - 200, y },
|
||||
};
|
||||
});
|
||||
await dragBetweenCoords(page, from, to, {
|
||||
steps: 10,
|
||||
});
|
||||
}
|
||||
|
||||
export async function dragEmbedResizeByTopLeft(page: Page) {
|
||||
const { from, to } = await page.evaluate(() => {
|
||||
const bottomRightButton = document.querySelector(
|
||||
'.top-left'
|
||||
) as HTMLInputElement;
|
||||
const bottomRightButtonBound = bottomRightButton.getBoundingClientRect();
|
||||
const y = bottomRightButtonBound.top;
|
||||
return {
|
||||
from: { x: bottomRightButtonBound.left + 5, y: y + 5 },
|
||||
to: { x: bottomRightButtonBound.left + 5 + 200, y },
|
||||
};
|
||||
});
|
||||
await dragBetweenCoords(page, from, to, {
|
||||
steps: 10,
|
||||
});
|
||||
}
|
||||
|
||||
export async function dragHandleFromBlockToBlockBottomById(
|
||||
page: Page,
|
||||
sourceId: string,
|
||||
targetId: string,
|
||||
bottom = true,
|
||||
offset?: number,
|
||||
beforeMouseUp?: () => Promise<void>
|
||||
) {
|
||||
const sourceBlock = await page
|
||||
.locator(`[data-block-id="${sourceId}"]`)
|
||||
.boundingBox();
|
||||
const targetBlock = await page
|
||||
.locator(`[data-block-id="${targetId}"]`)
|
||||
.boundingBox();
|
||||
if (!sourceBlock || !targetBlock) {
|
||||
throw new Error();
|
||||
}
|
||||
await page.mouse.move(
|
||||
sourceBlock.x + sourceBlock.width / 2,
|
||||
sourceBlock.y + sourceBlock.height / 2
|
||||
);
|
||||
await waitNextFrame(page);
|
||||
const dragHandleContainer = page.locator('.affine-drag-handle-container');
|
||||
await dragHandleContainer.hover();
|
||||
const handle = await dragHandleContainer.boundingBox();
|
||||
if (!handle) {
|
||||
throw new Error();
|
||||
}
|
||||
await page.mouse.move(
|
||||
handle.x + handle.width / 2,
|
||||
handle.y + handle.height / 2,
|
||||
{ steps: 10 }
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(
|
||||
targetBlock.x,
|
||||
targetBlock.y + (bottom ? targetBlock.height - 1 : 1),
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
if (offset) {
|
||||
await page.mouse.move(
|
||||
targetBlock.x + offset,
|
||||
targetBlock.y + (bottom ? targetBlock.height - 1 : 1),
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (beforeMouseUp) {
|
||||
await beforeMouseUp();
|
||||
}
|
||||
|
||||
await page.mouse.up();
|
||||
}
|
||||
|
||||
export async function dragBlockToPoint(
|
||||
page: Page,
|
||||
sourceId: string,
|
||||
point: { x: number; y: number }
|
||||
) {
|
||||
const sourceBlock = await page
|
||||
.locator(`[data-block-id="${sourceId}"]`)
|
||||
.boundingBox();
|
||||
if (!sourceBlock) {
|
||||
throw new Error();
|
||||
}
|
||||
await page.mouse.move(
|
||||
sourceBlock.x + sourceBlock.width / 2,
|
||||
sourceBlock.y + sourceBlock.height / 2
|
||||
);
|
||||
const handle = await page
|
||||
.locator('.affine-drag-handle-container')
|
||||
.boundingBox();
|
||||
if (!handle) {
|
||||
throw new Error();
|
||||
}
|
||||
await page.mouse.move(
|
||||
handle.x + handle.width / 2,
|
||||
handle.y + handle.height / 2
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(point.x, point.y, {
|
||||
steps: 50,
|
||||
});
|
||||
|
||||
await page.mouse.up();
|
||||
}
|
||||
|
||||
export async function moveToImage(page: Page) {
|
||||
const { x, y } = await page.evaluate(() => {
|
||||
const bottomRightButton = document.querySelector('img') as HTMLElement;
|
||||
const imageClient = bottomRightButton.getBoundingClientRect();
|
||||
const y = imageClient.top;
|
||||
return {
|
||||
x: imageClient.left + 30,
|
||||
y: y + 30,
|
||||
};
|
||||
});
|
||||
await page.mouse.move(x, y);
|
||||
}
|
||||
|
||||
export async function popImageMoreMenu(page: Page) {
|
||||
await moveToImage(page);
|
||||
await assertImageOption(page);
|
||||
const moreButton = page.locator('.image-toolbar-button.more');
|
||||
await moreButton.click();
|
||||
const menu = page.locator('.image-more-popup-menu');
|
||||
|
||||
const turnIntoCardButton = page.locator('editor-menu-action', {
|
||||
hasText: 'Turn into card view',
|
||||
});
|
||||
|
||||
const copyButton = page.locator('editor-menu-action', {
|
||||
hasText: 'Copy',
|
||||
});
|
||||
|
||||
const duplicateButton = page.locator('editor-menu-action', {
|
||||
hasText: 'Duplicate',
|
||||
});
|
||||
|
||||
const deleteButton = page.locator('editor-menu-action', {
|
||||
hasText: 'Delete',
|
||||
});
|
||||
|
||||
return {
|
||||
menu,
|
||||
copyButton,
|
||||
turnIntoCardButton,
|
||||
duplicateButton,
|
||||
deleteButton,
|
||||
};
|
||||
}
|
||||
|
||||
export async function clickBlockDragHandle(page: Page, blockId: string) {
|
||||
const blockBox = await page
|
||||
.locator(`[data-block-id="${blockId}"]`)
|
||||
.boundingBox();
|
||||
|
||||
if (!blockBox) {
|
||||
throw new Error();
|
||||
}
|
||||
await page.mouse.move(
|
||||
blockBox.x + blockBox.width / 2,
|
||||
blockBox.y + blockBox.height / 2
|
||||
);
|
||||
|
||||
const handleBox = await page
|
||||
.locator('.affine-drag-handle-container')
|
||||
.boundingBox();
|
||||
if (!handleBox) {
|
||||
throw new Error();
|
||||
}
|
||||
await page.mouse.click(
|
||||
handleBox.x + handleBox.width / 2,
|
||||
handleBox.y + handleBox.height / 2
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user