Files
AFFiNE-Mirror/tests/blocksuite/e2e/edgeless/connector/connector.spec.ts
T
L-Sun 9ac1da9fc1 fix(editor): should record edgeless connector mode (#12426)
Close [BS-3355](https://linear.app/affine-design/issue/BS-3355/白板快捷键c没有记住上次用的connector形状)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added the ability to cycle through connector modes (Curve, Orthogonal, Straight) using the 'c' keyboard shortcut when the connector tool is active.
- **Bug Fixes**
  - Improved the logic for remembering and restoring the last used connector mode when switching between tools.
- **Tests**
  - Introduced a new end-to-end test to verify correct cycling and restoration of connector modes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-05-22 03:54:31 +00:00

469 lines
14 KiB
TypeScript

import { expect } from '@playwright/test';
import {
addBasicConnectorElement,
assertEdgelessConnectorToolMode,
changeConnectorStrokeColor,
changeConnectorStrokeStyle,
changeConnectorStrokeWidth,
ConnectorMode,
createConnectorElement,
createShapeElement,
dragBetweenViewCoords,
edgelessCommonSetup as commonSetup,
getConnectorPath,
getConnectorPathWithInOut,
locatorComponentToolbar,
pickColorAtPoints,
rotateElementByHandle,
selectElementInEdgeless,
setEdgelessTool,
Shape,
toModelCoord,
toViewCoord,
triggerComponentToolbarAction,
triggerShapeSwitch,
} from '../../utils/actions/edgeless.js';
import {
clickView,
pressBackspace,
waitNextFrame,
} from '../../utils/actions/index.js';
import {
assertConnectorPath,
assertEdgelessNonSelectedRect,
assertEdgelessSelectedRect,
getSelectedRect,
} 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, 'changeConnectorStrokeStyles');
await changeConnectorStrokeColor(page, 'MediumGrey');
await triggerComponentToolbarAction(page, 'changeConnectorStrokeStyles');
await changeConnectorStrokeWidth(page, 5);
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, 'changeConnectorStrokeStyles');
await changeConnectorStrokeColor(page, 'MediumGrey');
await triggerComponentToolbarAction(page, 'changeConnectorStrokeStyles');
await changeConnectorStrokeStyle(page, 'dash');
await waitNextFrame(page);
await triggerComponentToolbarAction(page, 'changeConnectorStrokeStyles');
const pickedColor = await pickColorAtPoints(page, [[start.x + 20, start.y]]);
expect(pickedColor[0]).toBe('#000000');
});
test('should record previous connector mode', async ({ page }) => {
await commonSetup(page);
await setEdgelessTool(page, 'connector');
await assertEdgelessConnectorToolMode(page, ConnectorMode.Curve);
await page.keyboard.press('c');
await assertEdgelessConnectorToolMode(page, ConnectorMode.Orthogonal);
await page.keyboard.press('c');
await assertEdgelessConnectorToolMode(page, ConnectorMode.Straight);
await dragBetweenViewCoords(page, [100, 100], [200, 200]);
await page.keyboard.press('c');
await assertEdgelessConnectorToolMode(page, ConnectorMode.Straight);
await setEdgelessTool(page, 'default');
await clickView(page, [150, 150]);
await triggerComponentToolbarAction(page, 'changeConnectorShape');
await locatorComponentToolbar(page).getByLabel('Curve').click();
await page.keyboard.press('c');
await assertEdgelessConnectorToolMode(page, ConnectorMode.Curve);
});
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();
if (!bounds) {
throw new Error('bounds is not found');
}
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();
if (!bounds) {
throw new Error('bounds is not found');
}
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]);
});
test('the triangle connectors should remain the same when switch to other shape', async ({
page,
}) => {
await commonSetup(page);
const shape1Id = await createShapeElement(
page,
[0, 0],
[100, 100],
Shape.Triangle
);
const shape2Id = await createShapeElement(
page,
[200, 0],
[300, 100],
Shape.Triangle
);
await setEdgelessTool(page, 'connector');
await dragBetweenViewCoords(page, [60, 50], [240, 50]);
{
const path = await getConnectorPath(page);
// make sure the connector is created
expect(path.length).toBeGreaterThan(0);
}
// switch to other shape
await selectElementInEdgeless(page, [shape1Id]);
await triggerShapeSwitch(page, 'Square');
await selectElementInEdgeless(page, [shape2Id]);
await triggerShapeSwitch(page, 'Square');
await dragBetweenViewCoords(page, [50, 50], [0, 0]);
await dragBetweenViewCoords(page, [250, 50], [300, 50]);
{
const path = await getConnectorPathWithInOut(page);
expect(path.length).toBeGreaterThan(0);
path.forEach(point => {
[0, 1].forEach(i => {
expect(point.in[i]).not.toBeNaN();
expect(point.out[i]).not.toBeNaN();
expect(point.point[i]).not.toBeNaN();
});
});
}
});
test('connector can not be moved directly if the source or target is not selected', async ({
page,
}) => {
await commonSetup(page);
const normalConnectorId = await createConnectorElement(
page,
[0, 0],
[100, 100]
);
await selectElementInEdgeless(page, [normalConnectorId]);
const normalRect1 = await getSelectedRect(page);
// connector with no source and target can be moved
await dragBetweenViewCoords(page, [50, 50], [100, 100]);
const normalRect2 = await getSelectedRect(page);
expect(normalRect2).toEqual({
x: normalRect1.x + 50,
y: normalRect1.y + 50,
width: normalRect1.width,
height: normalRect1.height,
});
const shape1 = await createShapeElement(
page,
[150, 150],
[200, 200],
Shape.Square
);
const shape2 = await createShapeElement(
page,
[250, 250],
[300, 300],
Shape.Square
);
const connectorWithShapes = await createConnectorElement(
page,
[190, 175],
[260, 275]
);
await selectElementInEdgeless(page, [connectorWithShapes]);
// cannot be moved because the source and target are not selected
const initialShapeConnectorRect = await getSelectedRect(page);
await dragBetweenViewCoords(page, [225, 200], [275, 250]);
const shapeConnectorRect1 = await getSelectedRect(page);
expect(shapeConnectorRect1).toEqual(initialShapeConnectorRect);
// can be moved because the source and target are selected
await selectElementInEdgeless(page, [shape1, shape2, connectorWithShapes]);
await dragBetweenViewCoords(page, [225, 200], [275, 250]);
await selectElementInEdgeless(page, [connectorWithShapes]);
const shapeConnectorRect2 = await getSelectedRect(page);
expect(shapeConnectorRect2).toEqual({
x: initialShapeConnectorRect.x + 50,
y: initialShapeConnectorRect.y + 50,
width: initialShapeConnectorRect.width,
height: initialShapeConnectorRect.height,
});
});
});