mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-07 01:09:54 +08:00
chore: migrate blocksuite test (#9222)
This commit is contained in:
@@ -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, 225, 225]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoArrange');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 170, 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, 225, 225]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoResize');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 604.6, 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, 1421.5, 420]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,244 @@
|
||||
import { DEFAULT_NOTE_BACKGROUND_COLOR } from '@blocksuite/affine-model';
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
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, {
|
||||
flags: {
|
||||
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');
|
||||
const lineColor = '--affine-palette-line-red';
|
||||
await changeShapeStrokeColor(page, lineColor);
|
||||
await triggerComponentToolbarAction(page, 'changeShapeFillColor');
|
||||
const color = '--affine-palette-shape-green';
|
||||
await changeShapeFillColor(page, color);
|
||||
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,
|
||||
DEFAULT_NOTE_BACKGROUND_COLOR
|
||||
);
|
||||
|
||||
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, lineColor);
|
||||
|
||||
// select note block
|
||||
await page.mouse.click(rect.x + rect.width / 2, rect.y + rect.height / 2);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeNoteColor');
|
||||
const noteColor = '--affine-note-background-red';
|
||||
await changeEdgelessNoteBackground(page, noteColor);
|
||||
|
||||
// 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, noteColor);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,180 @@
|
||||
import { NoteDisplayMode } from '@blocksuite/affine-model';
|
||||
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 { 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
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,358 @@
|
||||
import {
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
} from '@blocksuite/affine-model';
|
||||
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,
|
||||
toggleEditorReadonly,
|
||||
ZOOM_BAR_RESPONSIVE_SCREEN_WIDTH,
|
||||
zoomByMouseWheel,
|
||||
zoomResetByKeyboard,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
addBasicBrushElement,
|
||||
addBasicRectShapeElement,
|
||||
captureHistory,
|
||||
clickView,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyEdgelessState,
|
||||
redoByClick,
|
||||
type,
|
||||
undoByClick,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessNonSelectedRect,
|
||||
assertEdgelessSelectedModelRect,
|
||||
assertEdgelessSelectedRect,
|
||||
assertNoteXYWH,
|
||||
assertRichTextInlineRange,
|
||||
assertRichTexts,
|
||||
assertSelectedBound,
|
||||
assertZoomLevel,
|
||||
} from '../utils/asserts.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 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 toggleEditorReadonly(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 toggleEditorReadonly(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();
|
||||
});
|
||||
@@ -0,0 +1,193 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
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');
|
||||
const color = '--affine-palette-line-blue';
|
||||
await selectBrushColor(page, color);
|
||||
|
||||
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]]);
|
||||
|
||||
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');
|
||||
const color = '--affine-palette-line-blue';
|
||||
await selectBrushColor(page, color);
|
||||
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]]);
|
||||
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');
|
||||
const color = '--affine-palette-line-blue';
|
||||
await selectBrushColor(page, color);
|
||||
|
||||
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],
|
||||
]);
|
||||
|
||||
await assertEdgelessColorSameWithHexColor(page, color, topEdge);
|
||||
await assertEdgelessColorSameWithHexColor(page, color, bottomEdge);
|
||||
assertSameColor(nearTopEdge, '#4f90ff');
|
||||
assertSameColor(nearBottomEdge, '#4f90ff');
|
||||
});
|
||||
|
||||
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]);
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,368 @@
|
||||
import { parseStringToRgba } from '@blocks/root-block/edgeless/components/color-picker/utils.js';
|
||||
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 { 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');
|
||||
}
|
||||
|
||||
function getCurrentColor(locator: Locator) {
|
||||
return locator.evaluate(ele =>
|
||||
getComputedStyle(ele).getPropertyValue('background-color')
|
||||
);
|
||||
}
|
||||
|
||||
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).toHaveCSS('background-color', 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 });
|
||||
});
|
||||
});
|
||||
@@ -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])
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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, '--affine-palette-line-teal');
|
||||
|
||||
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, '--affine-palette-line-teal');
|
||||
|
||||
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]);
|
||||
});
|
||||
});
|
||||
@@ -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],
|
||||
]);
|
||||
});
|
||||
@@ -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],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,596 @@
|
||||
import type { EdgelessTextBlockComponent } from '@blocks/edgeless-text-block/edgeless-text-block.js';
|
||||
import { Bound } from '@blocksuite/global/utils';
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
autoFit,
|
||||
captureHistory,
|
||||
cutByKeyboard,
|
||||
dragBetweenIndices,
|
||||
enterPlaygroundRoom,
|
||||
getEdgelessSelectedRect,
|
||||
getPageSnapshot,
|
||||
initEmptyEdgelessState,
|
||||
pasteByKeyboard,
|
||||
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, {
|
||||
flags: {
|
||||
enable_edgeless_text: true,
|
||||
},
|
||||
});
|
||||
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, 50, 26));
|
||||
|
||||
await type(page, 'aaaaaaaaaa');
|
||||
await waitNextFrame(page, 1000);
|
||||
// just width changed
|
||||
await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 83, 26));
|
||||
|
||||
await type(page, '\nbbb');
|
||||
// width not changed, height changed
|
||||
await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 83, 50));
|
||||
await type(page, '\ncccccccccccccccc');
|
||||
|
||||
// width and height changed
|
||||
await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 131, 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,
|
||||
});
|
||||
await type(page, 'dddddddddddddddddddd');
|
||||
// width not changed, height changed
|
||||
await assertEdgelessTextModelRect(page, '4', new Bound(-25, -25, 131, 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 model = await page.evaluate(() => {
|
||||
const block = window.host.view.getBlock(
|
||||
'4'
|
||||
) as EdgelessTextBlockComponent;
|
||||
return block.model;
|
||||
});
|
||||
const bound = Bound.deserialize(model.xywh);
|
||||
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);
|
||||
|
||||
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');
|
||||
await assertRichTextInlineDeltas(
|
||||
page,
|
||||
[
|
||||
{
|
||||
insert: ' ',
|
||||
attributes: {
|
||||
latex: 'bbbccc',
|
||||
},
|
||||
},
|
||||
],
|
||||
1
|
||||
);
|
||||
|
||||
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 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, {
|
||||
flags: {
|
||||
enable_edgeless_text: true,
|
||||
},
|
||||
});
|
||||
await page.evaluate(() => {
|
||||
const { doc } = window;
|
||||
const rootId = doc.addBlock('affine:page', {
|
||||
title: new doc.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);
|
||||
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`
|
||||
);
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,414 @@
|
||||
import {
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
} from '@blocksuite/affine-model';
|
||||
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 { 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']);
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -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]);
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (curController as any)['_isSelecting'];
|
||||
});
|
||||
|
||||
expect(isSelecting).toBe(false);
|
||||
});
|
||||
@@ -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
|
||||
.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
|
||||
.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.getElementsByType('group').map(s => ({
|
||||
type: s.type,
|
||||
// eslint-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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,559 @@
|
||||
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);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,126 @@
|
||||
import type { MindmapElementModel } from '@blocksuite/affine-model';
|
||||
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,
|
||||
zoomResetByKeyboard,
|
||||
} from 'utils/actions/edgeless.js';
|
||||
import {
|
||||
pressBackspace,
|
||||
selectAllByKeyboard,
|
||||
undoByKeyboard,
|
||||
} from 'utils/actions/keyboard.js';
|
||||
import { waitNextFrame } from 'utils/actions/misc.js';
|
||||
import {
|
||||
assertEdgelessSelectedRect,
|
||||
assertSelectedBound,
|
||||
} from 'utils/asserts.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);
|
||||
|
||||
await page.keyboard.press('m');
|
||||
await clickView(page, [0, 0]);
|
||||
await autoFit(page);
|
||||
|
||||
const { mindmapId, nodeId, nodeRect } = await page.evaluate(() => {
|
||||
const edgelessBlock = document.querySelector('affine-edgeless-root');
|
||||
if (!edgelessBlock) {
|
||||
throw new Error('edgeless block not found');
|
||||
}
|
||||
const mindmap = edgelessBlock.gfx.gfxElements.filter(
|
||||
el => 'type' in el && el.type === 'mindmap'
|
||||
)[0] as MindmapElementModel;
|
||||
const node = mindmap.tree.children[0].element;
|
||||
const rect = edgelessBlock.gfx.viewport.toViewBound(node.elementBound);
|
||||
|
||||
edgelessBlock.gfx.selection.set({ elements: [node.id] });
|
||||
|
||||
return {
|
||||
mindmapId: mindmap.id,
|
||||
nodeId: node.id,
|
||||
nodeRect: {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
w: rect.w,
|
||||
h: rect.h,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: nodeRect.x + nodeRect.w / 2, y: nodeRect.y + nodeRect.h / 2 },
|
||||
{ x: nodeRect.x + nodeRect.w / 2, y: nodeRect.y + nodeRect.h / 2 + 120 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const secondNodeId = await page.evaluate(
|
||||
({ mindmapId }) => {
|
||||
const edgelessBlock = document.querySelector('affine-edgeless-root');
|
||||
if (!edgelessBlock) {
|
||||
throw new Error('edgeless block not found');
|
||||
}
|
||||
const mindmap = edgelessBlock.gfx.getElementById(
|
||||
mindmapId
|
||||
) as MindmapElementModel;
|
||||
|
||||
return mindmap.tree.children[1].id;
|
||||
},
|
||||
{ mindmapId, nodeId }
|
||||
);
|
||||
|
||||
expect(secondNodeId).toEqual(nodeId);
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,80 @@
|
||||
import { NoteDisplayMode } from '@blocksuite/affine-model';
|
||||
|
||||
import {
|
||||
addNote,
|
||||
changeNoteDisplayModeWithId,
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
switchEditorMode,
|
||||
zoomResetByKeyboard,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertBlockCount } from '../../utils/asserts.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);
|
||||
});
|
||||
@@ -0,0 +1,531 @@
|
||||
import {
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
NoteDisplayMode,
|
||||
} from '@blocksuite/affine-model';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
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 { 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,
|
||||
'--affine-note-background-white'
|
||||
);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'changeNoteColor');
|
||||
const color = '--affine-note-background-green';
|
||||
await changeEdgelessNoteBackground(page, color);
|
||||
await assertEdgelessNoteBackground(page, noteId, color);
|
||||
});
|
||||
|
||||
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 assertTextSelection(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']);
|
||||
});
|
||||
@@ -0,0 +1,254 @@
|
||||
import { NOTE_MIN_HEIGHT, NOTE_MIN_WIDTH } from '@blocksuite/affine-model';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
activeNoteInEdgeless,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
getNoteRect,
|
||||
initEmptyEdgelessState,
|
||||
redoByClick,
|
||||
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 { 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);
|
||||
const leftHandle = page.locator('.handle[aria-label="left"] .resize');
|
||||
const box = await leftHandle.boundingBox();
|
||||
if (box === null) throw new Error();
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: box.x + 5, y: box.y + 5 },
|
||||
{ x: box.x - 95, y: box.y + 5 }
|
||||
);
|
||||
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);
|
||||
const leftHandle = page.locator('.handle[aria-label="left"] .resize');
|
||||
let box = await leftHandle.boundingBox();
|
||||
if (box === null) throw new Error();
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: box.x + 50, y: box.y + box.height },
|
||||
{ x: box.x + 50, y: box.y + box.height + 100 }
|
||||
);
|
||||
let noteRect = await getNoteRect(page, noteId);
|
||||
await expect(page.locator('.edgeless-note-collapse-button')).toBeVisible();
|
||||
assertRectEqual(noteRect, {
|
||||
x: initRect.x,
|
||||
y: initRect.y,
|
||||
w: initRect.w,
|
||||
h: initRect.h + 100,
|
||||
});
|
||||
|
||||
await page.locator('.edgeless-note-collapse-button')!.click();
|
||||
let domRect = await page.locator('affine-edgeless-note').boundingBox();
|
||||
expect(domRect!.height).toBeCloseTo(NOTE_MIN_HEIGHT);
|
||||
|
||||
await page.locator('.edgeless-note-collapse-button')!.click();
|
||||
domRect = await page.locator('affine-edgeless-note').boundingBox();
|
||||
expect(domRect!.height).toBeCloseTo(initRect.h + 100);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
box = await leftHandle.boundingBox();
|
||||
if (box === null) throw new Error();
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: box.x + 50, y: box.y + box.height },
|
||||
{ x: box.x + 50, y: box.y + box.height - 150 }
|
||||
);
|
||||
noteRect = await getNoteRect(page, noteId);
|
||||
await expect(
|
||||
page.locator('.edgeless-note-collapse-button')
|
||||
).not.toBeVisible();
|
||||
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);
|
||||
const bottomRightResize = page.locator(
|
||||
'.handle[aria-label="bottom-right"] .resize'
|
||||
);
|
||||
const box = await bottomRightResize.boundingBox();
|
||||
if (box === null) throw new Error();
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: box.x + 5, y: box.y + 5 },
|
||||
{ x: box.x + 5, y: box.y + 105 }
|
||||
);
|
||||
|
||||
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,
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,146 @@
|
||||
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.locator('.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');
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,127 @@
|
||||
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);
|
||||
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, {
|
||||
flags: {
|
||||
enable_edgeless_text: true,
|
||||
},
|
||||
});
|
||||
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 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');
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,448 @@
|
||||
import {
|
||||
DEFAULT_NOTE_HEIGHT,
|
||||
DEFAULT_NOTE_WIDTH,
|
||||
} from '@blocksuite/affine-model';
|
||||
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 { 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,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,265 @@
|
||||
import { NoteDisplayMode } from '@blocksuite/affine-model';
|
||||
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 { 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);
|
||||
});
|
||||
@@ -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]);
|
||||
});
|
||||
@@ -0,0 +1,727 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
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');
|
||||
const color = '--affine-palette-shape-teal';
|
||||
await changeShapeFillColor(page, color);
|
||||
await page.waitForTimeout(50);
|
||||
const [picked] = await pickColorAtPoints(page, [
|
||||
[rect.start.x + 20, rect.start.y + 20],
|
||||
]);
|
||||
|
||||
await assertEdgelessColorSameWithHexColor(page, color, 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');
|
||||
const color = '--affine-palette-line-teal';
|
||||
await changeShapeStrokeColor(page, color);
|
||||
await page.waitForTimeout(50);
|
||||
const [picked] = await pickColorAtPoints(page, [
|
||||
[rect.start.x + 1, rect.start.y + 1],
|
||||
]);
|
||||
|
||||
await assertEdgelessColorSameWithHexColor(page, color, 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, '--affine-palette-line-teal');
|
||||
|
||||
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, '--affine-palette-line-teal');
|
||||
|
||||
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 = '--affine-palette-line-teal';
|
||||
await changeShapeStrokeColor(page, color);
|
||||
await page.waitForTimeout(50);
|
||||
const [picked] = await pickColorAtPoints(page, [[start.x + 1, start.y + 1]]);
|
||||
|
||||
await assertEdgelessColorSameWithHexColor(page, color, 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, {
|
||||
flags: {
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,324 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addBasicRectShapeElement,
|
||||
assertEdgelessShapeType,
|
||||
createShapeElement,
|
||||
edgelessCommonSetup,
|
||||
getEdgelessSelectedRect,
|
||||
getZoomLevel,
|
||||
locatorEdgelessToolButton,
|
||||
setEdgelessTool,
|
||||
type ShapeName,
|
||||
switchEditorMode,
|
||||
zoomFitByKeyboard,
|
||||
zoomInByKeyboard,
|
||||
zoomOutByKeyboard,
|
||||
zoomResetByKeyboard,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
clickView,
|
||||
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 page.keyboard.press('Escape');
|
||||
await waitNextFrame(page);
|
||||
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 page.keyboard.press('Escape');
|
||||
await waitNextFrame(page);
|
||||
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('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]);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,316 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
import { getLinkedDocPopover } from 'utils/actions/linked-doc.js';
|
||||
|
||||
import {
|
||||
assertEdgelessTool,
|
||||
enterPlaygroundRoom,
|
||||
getEdgelessSelectedRect,
|
||||
initEmptyEdgelessState,
|
||||
pressArrowLeft,
|
||||
pressEnter,
|
||||
setEdgelessTool,
|
||||
SHORT_KEY,
|
||||
switchEditorMode,
|
||||
type,
|
||||
waitForInlineEditorStateUpdated,
|
||||
waitNextFrame,
|
||||
zoomResetByKeyboard,
|
||||
} from '../utils/actions/index.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, {
|
||||
flags: {
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user