mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-16 05:47:09 +08:00
Closes: [BS-1475](https://linear.app/affine-design/issue/BS-1475/颜色主题更新) [BS-1803](https://linear.app/affine-design/issue/BS-1803/fill-color色板影响的yuan素) [BS-1804](https://linear.app/affine-design/issue/BS-1804/border-color色板影响的yuan素) [BS-1815](https://linear.app/affine-design/issue/BS-1815/连线文字配色略瞎) ### What's Changed * refactor `EdgelessLineWidthPanel` component, the previous width is fixed and cannot be used in the new design * refactor `EdgelessColorPanel` and `EdgelessColorButton` components, make them simple and reusable * delete redundant `EdgelessOneRowColorPanel` component * unity and update color palette, if the previously set color is not in the latest color palette, the custom color button will be selected
532 lines
15 KiB
TypeScript
532 lines
15 KiB
TypeScript
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-v2-edgeless-note-white'
|
|
);
|
|
|
|
await selectNoteInEdgeless(page, noteId);
|
|
await triggerComponentToolbarAction(page, 'changeNoteColor');
|
|
const color = '--affine-v2-edgeless-note-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']);
|
|
});
|