mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-03-22 23:30:36 +08:00
chore: migrate blocksuite test (#9222)
This commit is contained in:
32
.github/workflows/build-test.yml
vendored
32
.github/workflows/build-test.yml
vendored
@@ -112,6 +112,36 @@ jobs:
|
||||
yarn set version $(node -e "console.log(require('./package.json').packageManager.split('@')[1])")
|
||||
git diff --exit-code
|
||||
|
||||
e2e-legacy-blocksuite-test:
|
||||
name: Legacy Blocksuite E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
playwright-install: true
|
||||
electron-install: false
|
||||
full-cache: true
|
||||
|
||||
- name: Run playground build
|
||||
run: yarn workspace @blocksuite/playground build
|
||||
|
||||
- name: Run playwright tests
|
||||
run: yarn workspace @blocksuite/legacy-e2e test --forbid-only --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
||||
|
||||
- name: Upload test results
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results-e2e-legacy-bs-${{ matrix.shard }}
|
||||
path: ./test-results
|
||||
if-no-files-found: ignore
|
||||
|
||||
e2e-test:
|
||||
name: E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
@@ -185,6 +215,7 @@ jobs:
|
||||
uses: ./.github/actions/setup-node
|
||||
with:
|
||||
electron-install: true
|
||||
playwright-install: true
|
||||
full-cache: true
|
||||
|
||||
- name: Download affine.linux-x64-gnu.node
|
||||
@@ -767,6 +798,7 @@ jobs:
|
||||
- lint
|
||||
- check-yarn-binary
|
||||
- e2e-test
|
||||
- e2e-legacy-blocksuite-test
|
||||
- e2e-mobile-test
|
||||
- unit-test
|
||||
- build-native
|
||||
|
||||
@@ -25,4 +25,6 @@ packages/frontend/templates/onboarding
|
||||
packages/backend/native/index.d.ts
|
||||
packages/frontend/native/index.d.ts
|
||||
packages/frontend/native/index.js
|
||||
compose.yaml
|
||||
compose.yaml
|
||||
|
||||
blocksuite/tests-legacy/snapshots
|
||||
|
||||
@@ -5,7 +5,7 @@ export default defineConfig({
|
||||
target: 'es2018',
|
||||
},
|
||||
test: {
|
||||
globalSetup: '../../../scripts/vitest-global.ts',
|
||||
globalSetup: '../../../scripts/vitest-global.js',
|
||||
include: ['src/__tests__/**/*.unit.spec.ts'],
|
||||
testTimeout: 1000,
|
||||
coverage: {
|
||||
|
||||
@@ -5,7 +5,7 @@ export default defineConfig({
|
||||
target: 'es2018',
|
||||
},
|
||||
test: {
|
||||
globalSetup: '../../../scripts/vitest-global.ts',
|
||||
globalSetup: '../../../scripts/vitest-global.js',
|
||||
include: ['src/__tests__/**/*.unit.spec.ts'],
|
||||
testTimeout: 1000,
|
||||
coverage: {
|
||||
|
||||
@@ -5,7 +5,7 @@ export default defineConfig({
|
||||
target: 'es2018',
|
||||
},
|
||||
test: {
|
||||
globalSetup: '../../../scripts/vitest-global.ts',
|
||||
globalSetup: '../../../scripts/vitest-global.js',
|
||||
include: ['src/__tests__/**/*.unit.spec.ts'],
|
||||
testTimeout: 1000,
|
||||
coverage: {
|
||||
|
||||
@@ -5,7 +5,7 @@ export default defineConfig({
|
||||
target: 'es2018',
|
||||
},
|
||||
test: {
|
||||
globalSetup: '../../../scripts/vitest-global.ts',
|
||||
globalSetup: '../../../scripts/vitest-global.js',
|
||||
include: ['src/__tests__/**/*.unit.spec.ts'],
|
||||
testTimeout: 1000,
|
||||
coverage: {
|
||||
|
||||
@@ -5,7 +5,7 @@ export default defineConfig({
|
||||
target: 'es2018',
|
||||
},
|
||||
test: {
|
||||
globalSetup: '../../../scripts/vitest-global.ts',
|
||||
globalSetup: '../../../scripts/vitest-global.js',
|
||||
include: ['src/__tests__/**/*.unit.spec.ts'],
|
||||
testTimeout: 1000,
|
||||
coverage: {
|
||||
|
||||
@@ -5,7 +5,7 @@ export default defineConfig({
|
||||
target: 'es2018',
|
||||
},
|
||||
test: {
|
||||
globalSetup: '../../../scripts/vitest-global.ts',
|
||||
globalSetup: '../../../scripts/vitest-global.js',
|
||||
include: ['src/__tests__/**/*.unit.spec.ts'],
|
||||
testTimeout: 1000,
|
||||
coverage: {
|
||||
|
||||
@@ -5,7 +5,7 @@ export default defineConfig({
|
||||
target: 'es2018',
|
||||
},
|
||||
test: {
|
||||
globalSetup: '../../scripts/vitest-global.ts',
|
||||
globalSetup: '../../scripts/vitest-global.js',
|
||||
include: ['src/__tests__/**/*.unit.spec.ts'],
|
||||
testTimeout: 1000,
|
||||
coverage: {
|
||||
|
||||
@@ -5,7 +5,7 @@ export default defineConfig({
|
||||
target: 'es2018',
|
||||
},
|
||||
test: {
|
||||
globalSetup: '../../scripts/vitest-global.ts',
|
||||
globalSetup: '../../scripts/vitest-global.js',
|
||||
include: ['src/__tests__/**/*.unit.spec.ts'],
|
||||
testTimeout: 1000,
|
||||
coverage: {
|
||||
|
||||
@@ -5,7 +5,7 @@ export default defineConfig({
|
||||
target: 'es2018',
|
||||
},
|
||||
test: {
|
||||
globalSetup: '../../../scripts/vitest-global.ts',
|
||||
globalSetup: '../../../scripts/vitest-global.js',
|
||||
include: ['src/__tests__/**/*.unit.spec.ts'],
|
||||
testTimeout: 1000,
|
||||
coverage: {
|
||||
|
||||
@@ -5,7 +5,7 @@ export default defineConfig({
|
||||
target: 'es2018',
|
||||
},
|
||||
test: {
|
||||
globalSetup: '../../../scripts/vitest-global.ts',
|
||||
globalSetup: '../../../scripts/vitest-global.js',
|
||||
include: ['src/__tests__/**/*.unit.spec.ts'],
|
||||
testTimeout: 1000,
|
||||
coverage: {
|
||||
|
||||
@@ -5,7 +5,7 @@ export default defineConfig({
|
||||
target: 'es2018',
|
||||
},
|
||||
test: {
|
||||
globalSetup: '../../scripts/vitest-global.ts',
|
||||
globalSetup: '../../scripts/vitest-global.js',
|
||||
include: ['src/__tests__/**/*.unit.spec.ts'],
|
||||
testTimeout: 1000,
|
||||
coverage: {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"@lit/context": "^1.1.2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@types/hast": "^3.0.4",
|
||||
"dompurify": "^3.1.6",
|
||||
"fractional-indexing": "^3.2.0",
|
||||
"lib0": "^0.2.97",
|
||||
"lit": "^3.2.0",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
"dev:hmr": "WC_HMR=1 vite",
|
||||
"build": "tsc && nx vite:build",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -40,6 +40,8 @@
|
||||
"@types/micromatch": "^4.0.9",
|
||||
"graphql": "^16.9.0",
|
||||
"magic-string": "^0.30.11",
|
||||
"vite": "^6.0.3",
|
||||
"vite-plugin-istanbul": "^6.0.2",
|
||||
"vite-plugin-wasm": "^3.3.0",
|
||||
"vite-plugin-web-components-hmr": "^0.1.3"
|
||||
}
|
||||
|
||||
@@ -89,11 +89,13 @@ beforeEach(async () => {
|
||||
|
||||
const xywhPattern = /\[(\s*-?\d+(\.\d+)?\s*,){3}(\s*-?\d+(\.\d+)?\s*)\]/;
|
||||
|
||||
test('snapshot 1 importing', async () => {
|
||||
// FIXME: snapshot tests
|
||||
test.skip('snapshot 1 importing', async () => {
|
||||
await snapshotTest('https://test.affineassets.com/test-snapshot-1.zip', 25);
|
||||
});
|
||||
|
||||
test('snapshot 2 importing', async () => {
|
||||
// FIXME: snapshot tests
|
||||
test.skip('snapshot 2 importing', async () => {
|
||||
await snapshotTest(
|
||||
'https://test.affineassets.com/test-snapshot-2%20(onboarding).zip',
|
||||
174
|
||||
|
||||
769
blocksuite/tests-legacy/attachment.spec.ts
Normal file
769
blocksuite/tests-legacy/attachment.spec.ts
Normal file
@@ -0,0 +1,769 @@
|
||||
import { sleep } from '@blocksuite/global/utils';
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
import { switchEditorMode } from 'utils/actions/edgeless.js';
|
||||
|
||||
import { dragBlockToPoint, popImageMoreMenu } from './utils/actions/drag.js';
|
||||
import {
|
||||
pressArrowDown,
|
||||
pressArrowUp,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
pressShiftTab,
|
||||
pressTab,
|
||||
redoByKeyboard,
|
||||
SHORT_KEY,
|
||||
type,
|
||||
undoByKeyboard,
|
||||
} from './utils/actions/keyboard.js';
|
||||
import {
|
||||
captureHistory,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyEdgelessState,
|
||||
initEmptyParagraphState,
|
||||
resetHistory,
|
||||
waitNextFrame,
|
||||
} from './utils/actions/misc.js';
|
||||
import {
|
||||
assertBlockChildrenIds,
|
||||
assertBlockCount,
|
||||
assertBlockFlavour,
|
||||
assertBlockSelections,
|
||||
assertKeyboardWorkInInput,
|
||||
assertParentBlockFlavour,
|
||||
assertRichImage,
|
||||
assertRichTextInlineRange,
|
||||
assertStoreMatchJSX,
|
||||
} from './utils/asserts.js';
|
||||
import { test } from './utils/playwright.js';
|
||||
|
||||
const FILE_NAME = 'test-card-1.png';
|
||||
const FILE_PATH = `../playground/public/${FILE_NAME}`;
|
||||
const FILE_ID = 'ejImogf-Tb7AuKY-v94uz1zuOJbClqK-tWBxVr_ksGA=';
|
||||
const FILE_SIZE = 45801;
|
||||
|
||||
function getAttachment(page: Page) {
|
||||
const attachment = page.locator('affine-attachment');
|
||||
const loading = attachment.locator('.affine-attachment-card.loading');
|
||||
const toolbar = page.locator('.affine-attachment-toolbar');
|
||||
const switchViewButton = toolbar.getByRole('button', { name: 'Switch view' });
|
||||
const renameBtn = toolbar.getByRole('button', { name: 'Rename' });
|
||||
const renameInput = page.locator('.affine-attachment-rename-container input');
|
||||
|
||||
const insertAttachment = async () => {
|
||||
await page.evaluate(() => {
|
||||
// Force fallback to input[type=file] in tests
|
||||
// See https://github.com/microsoft/playwright/issues/8850
|
||||
window.showOpenFilePicker = undefined;
|
||||
});
|
||||
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await waitNextFrame(page);
|
||||
await type(page, '/');
|
||||
await resetHistory(page);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
await type(page, 'file', 100);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const fileChooser = page.waitForEvent('filechooser');
|
||||
await pressEnter(page);
|
||||
await sleep(100);
|
||||
await (await fileChooser).setFiles(FILE_PATH);
|
||||
|
||||
// Try to break the undo redo test
|
||||
await captureHistory(page);
|
||||
|
||||
await expect(attachment).toBeVisible();
|
||||
};
|
||||
|
||||
const getName = () =>
|
||||
attachment.locator('.affine-attachment-content-title-text').innerText();
|
||||
|
||||
return {
|
||||
// locators
|
||||
attachment,
|
||||
toolbar,
|
||||
switchViewButton,
|
||||
renameBtn,
|
||||
renameInput,
|
||||
|
||||
// actions
|
||||
insertAttachment,
|
||||
/**
|
||||
* Wait for the attachment upload to finish
|
||||
*/
|
||||
waitLoading: () => loading.waitFor({ state: 'hidden' }),
|
||||
getName,
|
||||
getSize: () =>
|
||||
attachment.locator('.affine-attachment-content-info').innerText(),
|
||||
|
||||
turnToEmbed: async () => {
|
||||
await expect(switchViewButton).toBeVisible();
|
||||
await switchViewButton.click();
|
||||
await page.getByRole('button', { name: 'Embed view' }).click();
|
||||
await assertRichImage(page, 1);
|
||||
},
|
||||
rename: async (newName: string) => {
|
||||
await attachment.hover();
|
||||
await expect(toolbar).toBeVisible();
|
||||
await renameBtn.click();
|
||||
await page.keyboard.press(`${SHORT_KEY}+a`, { delay: 50 });
|
||||
await pressBackspace(page);
|
||||
await type(page, newName);
|
||||
await pressEnter(page);
|
||||
expect(await getName()).toContain(newName);
|
||||
},
|
||||
|
||||
// external
|
||||
turnImageToCard: async () => {
|
||||
const { turnIntoCardButton } = await popImageMoreMenu(page);
|
||||
await turnIntoCardButton.click();
|
||||
await expect(attachment).toBeVisible();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test('can insert attachment from slash menu', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyParagraphState(page);
|
||||
|
||||
const { insertAttachment, waitLoading, getName, getSize } =
|
||||
getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await insertAttachment();
|
||||
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
expect(await getName()).toBe(FILE_NAME);
|
||||
expect(await getSize()).toBe('45.8 kB');
|
||||
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
`
|
||||
<affine:note
|
||||
prop:background="--affine-note-background-white"
|
||||
prop:displayMode="both"
|
||||
prop:edgeless={
|
||||
Object {
|
||||
"style": Object {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "none",
|
||||
"shadowType": "--affine-note-shadow-box",
|
||||
},
|
||||
}
|
||||
}
|
||||
prop:hidden={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
>
|
||||
<affine:attachment
|
||||
prop:embed={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
prop:name="${FILE_NAME}"
|
||||
prop:rotate={0}
|
||||
prop:size={${FILE_SIZE}}
|
||||
prop:sourceId="${FILE_ID}"
|
||||
prop:style="horizontalThin"
|
||||
prop:type="image/png"
|
||||
/>
|
||||
</affine:note>`,
|
||||
noteId
|
||||
);
|
||||
});
|
||||
|
||||
test('should undo/redo works for attachment', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyParagraphState(page);
|
||||
|
||||
const { insertAttachment, waitLoading } = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await insertAttachment();
|
||||
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
` <affine:note
|
||||
prop:background="--affine-note-background-white"
|
||||
prop:displayMode="both"
|
||||
prop:edgeless={
|
||||
Object {
|
||||
"style": Object {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "none",
|
||||
"shadowType": "--affine-note-shadow-box",
|
||||
},
|
||||
}
|
||||
}
|
||||
prop:hidden={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
>
|
||||
<affine:attachment
|
||||
prop:embed={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
prop:name="${FILE_NAME}"
|
||||
prop:rotate={0}
|
||||
prop:size={${FILE_SIZE}}
|
||||
prop:sourceId="${FILE_ID}"
|
||||
prop:style="horizontalThin"
|
||||
prop:type="image/png"
|
||||
/>
|
||||
</affine:note>`,
|
||||
noteId
|
||||
);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
// The loading/error state should not be restored after undo
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
`
|
||||
<affine:note
|
||||
prop:background="--affine-note-background-white"
|
||||
prop:displayMode="both"
|
||||
prop:edgeless={
|
||||
Object {
|
||||
"style": Object {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "none",
|
||||
"shadowType": "--affine-note-shadow-box",
|
||||
},
|
||||
}
|
||||
}
|
||||
prop:hidden={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
>
|
||||
<affine:paragraph
|
||||
prop:collapsed={false}
|
||||
prop:text="/"
|
||||
prop:type="text"
|
||||
/>
|
||||
</affine:note>`,
|
||||
noteId
|
||||
);
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
`
|
||||
<affine:note
|
||||
prop:background="--affine-note-background-white"
|
||||
prop:displayMode="both"
|
||||
prop:edgeless={
|
||||
Object {
|
||||
"style": Object {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "none",
|
||||
"shadowType": "--affine-note-shadow-box",
|
||||
},
|
||||
}
|
||||
}
|
||||
prop:hidden={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
>
|
||||
<affine:attachment
|
||||
prop:embed={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
prop:name="${FILE_NAME}"
|
||||
prop:rotate={0}
|
||||
prop:size={${FILE_SIZE}}
|
||||
prop:sourceId="${FILE_ID}"
|
||||
prop:style="horizontalThin"
|
||||
prop:type="image/png"
|
||||
/>
|
||||
</affine:note>`,
|
||||
noteId
|
||||
);
|
||||
});
|
||||
|
||||
test('should rename attachment works', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/4534',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const {
|
||||
attachment,
|
||||
renameBtn,
|
||||
renameInput,
|
||||
insertAttachment,
|
||||
waitLoading,
|
||||
getName,
|
||||
rename,
|
||||
} = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await insertAttachment();
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
expect(await getName()).toBe(FILE_NAME);
|
||||
|
||||
await attachment.hover();
|
||||
await expect(renameBtn).toBeVisible();
|
||||
await renameBtn.click();
|
||||
await assertKeyboardWorkInInput(page, renameInput);
|
||||
await pressEscape(page);
|
||||
await expect(renameInput).not.toBeVisible();
|
||||
|
||||
await rename('new-name');
|
||||
expect(await getName()).toBe('new-name.png');
|
||||
await rename('');
|
||||
expect(await getName()).toBe('.png');
|
||||
await rename('abc');
|
||||
expect(await getName()).toBe('abc');
|
||||
});
|
||||
|
||||
test('should turn attachment to image works', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyParagraphState(page);
|
||||
const { insertAttachment, waitLoading, turnToEmbed, turnImageToCard } =
|
||||
getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await insertAttachment();
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
await turnToEmbed();
|
||||
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
`
|
||||
<affine:note
|
||||
prop:background="--affine-note-background-white"
|
||||
prop:displayMode="both"
|
||||
prop:edgeless={
|
||||
Object {
|
||||
"style": Object {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "none",
|
||||
"shadowType": "--affine-note-shadow-box",
|
||||
},
|
||||
}
|
||||
}
|
||||
prop:hidden={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
>
|
||||
<affine:image
|
||||
prop:caption=""
|
||||
prop:height={0}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
prop:rotate={0}
|
||||
prop:size={${FILE_SIZE}}
|
||||
prop:sourceId="${FILE_ID}"
|
||||
prop:width={0}
|
||||
/>
|
||||
</affine:note>`,
|
||||
noteId
|
||||
);
|
||||
await turnImageToCard();
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
`
|
||||
<affine:note
|
||||
prop:background="--affine-note-background-white"
|
||||
prop:displayMode="both"
|
||||
prop:edgeless={
|
||||
Object {
|
||||
"style": Object {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "none",
|
||||
"shadowType": "--affine-note-shadow-box",
|
||||
},
|
||||
}
|
||||
}
|
||||
prop:hidden={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
>
|
||||
<affine:attachment
|
||||
prop:caption=""
|
||||
prop:embed={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
prop:name="${FILE_NAME}"
|
||||
prop:rotate={0}
|
||||
prop:size={${FILE_SIZE}}
|
||||
prop:sourceId="${FILE_ID}"
|
||||
prop:style="horizontalThin"
|
||||
prop:type="image/png"
|
||||
/>
|
||||
</affine:note>`,
|
||||
noteId
|
||||
);
|
||||
});
|
||||
|
||||
test('should attachment can be deleted', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyParagraphState(page);
|
||||
const { attachment, insertAttachment, waitLoading } = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await insertAttachment();
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
await attachment.click();
|
||||
await pressBackspace(page);
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
`
|
||||
<affine:note
|
||||
prop:background="--affine-note-background-white"
|
||||
prop:displayMode="both"
|
||||
prop:edgeless={
|
||||
Object {
|
||||
"style": Object {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "none",
|
||||
"shadowType": "--affine-note-shadow-box",
|
||||
},
|
||||
}
|
||||
}
|
||||
prop:hidden={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
>
|
||||
<affine:paragraph
|
||||
prop:collapsed={false}
|
||||
prop:type="text"
|
||||
/>
|
||||
</affine:note>`,
|
||||
noteId
|
||||
);
|
||||
});
|
||||
|
||||
test.fixme(`support dragging attachment block directly`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyParagraphState(page);
|
||||
|
||||
const { insertAttachment, waitLoading, getName, getSize } =
|
||||
getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await insertAttachment();
|
||||
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
expect(await getName()).toBe(FILE_NAME);
|
||||
expect(await getSize()).toBe('45.8 kB');
|
||||
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
` <affine:note
|
||||
prop:background="--affine-note-background-white"
|
||||
prop:displayMode="both"
|
||||
prop:edgeless={
|
||||
Object {
|
||||
"style": Object {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "none",
|
||||
"shadowType": "--affine-note-shadow-box",
|
||||
},
|
||||
}
|
||||
}
|
||||
prop:hidden={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
>
|
||||
<affine:attachment
|
||||
prop:embed={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
prop:name="${FILE_NAME}"
|
||||
prop:rotate={0}
|
||||
prop:size={${FILE_SIZE}}
|
||||
prop:sourceId="${FILE_ID}"
|
||||
prop:style="horizontalThin"
|
||||
prop:type="image/png"
|
||||
/>
|
||||
</affine:note>`,
|
||||
noteId
|
||||
);
|
||||
|
||||
const attachmentBlock = page.locator('affine-attachment');
|
||||
const rect = await attachmentBlock.boundingBox();
|
||||
if (!rect) {
|
||||
throw new Error('image not found');
|
||||
}
|
||||
|
||||
// add new paragraph blocks
|
||||
await page.mouse.click(rect.x + 20, rect.y + rect.height + 20);
|
||||
await focusRichText(page);
|
||||
await type(page, '111');
|
||||
await page.waitForTimeout(200);
|
||||
await pressEnter(page);
|
||||
|
||||
await type(page, '222');
|
||||
await page.waitForTimeout(200);
|
||||
await pressEnter(page);
|
||||
|
||||
await type(page, '333');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.waitForTimeout(200);
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
/*xml*/ `<affine:page>
|
||||
<affine:note
|
||||
prop:background="--affine-note-background-white"
|
||||
prop:displayMode="both"
|
||||
prop:edgeless={
|
||||
Object {
|
||||
"style": Object {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "none",
|
||||
"shadowType": "--affine-note-shadow-box",
|
||||
},
|
||||
}
|
||||
}
|
||||
prop:hidden={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
>
|
||||
<affine:attachment
|
||||
prop:embed={false}
|
||||
prop:index="a0"
|
||||
prop:name="${FILE_NAME}"
|
||||
prop:rotate={0}
|
||||
prop:size={${FILE_SIZE}}
|
||||
prop:sourceId="${FILE_ID}"
|
||||
prop:style="horizontalThin"
|
||||
prop:type="image/png"
|
||||
/>
|
||||
<affine:paragraph
|
||||
prop:collapsed={false}
|
||||
prop:text="111"
|
||||
prop:type="text"
|
||||
/>
|
||||
<affine:paragraph
|
||||
prop:collapsed={false}
|
||||
prop:text="222"
|
||||
prop:type="text"
|
||||
/>
|
||||
<affine:paragraph
|
||||
prop:collapsed={false}
|
||||
prop:text="333"
|
||||
prop:type="text"
|
||||
/>
|
||||
</affine:note>
|
||||
</affine:page>`
|
||||
);
|
||||
|
||||
// drag bookmark block
|
||||
await page.mouse.move(rect.x + 20, rect.y + 20);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(rect.x + 40, rect.y + rect.height + 80, { steps: 20 });
|
||||
await page.mouse.up();
|
||||
|
||||
const rects = page.locator('affine-block-selection').locator('visible=true');
|
||||
await expect(rects).toHaveCount(1);
|
||||
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
/*xml*/ `<affine:page>
|
||||
<affine:note
|
||||
prop:background="--affine-note-background-white"
|
||||
prop:displayMode="both"
|
||||
prop:edgeless={
|
||||
Object {
|
||||
"style": Object {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "none",
|
||||
"shadowType": "--affine-note-shadow-box",
|
||||
},
|
||||
}
|
||||
}
|
||||
prop:hidden={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
>
|
||||
<affine:paragraph
|
||||
prop:collapsed={false}
|
||||
prop:text="111"
|
||||
prop:type="text"
|
||||
/>
|
||||
<affine:paragraph
|
||||
prop:collapsed={false}
|
||||
prop:text="222"
|
||||
prop:type="text"
|
||||
/>
|
||||
<affine:attachment
|
||||
prop:embed={false}
|
||||
prop:index="a0"
|
||||
prop:name="${FILE_NAME}"
|
||||
prop:rotate={0}
|
||||
prop:size={${FILE_SIZE}}
|
||||
prop:sourceId="${FILE_ID}"
|
||||
prop:style="horizontalThin"
|
||||
prop:type="image/png"
|
||||
/>
|
||||
<affine:paragraph
|
||||
prop:collapsed={false}
|
||||
prop:text="333"
|
||||
prop:type="text"
|
||||
/>
|
||||
</affine:note>
|
||||
</affine:page>`
|
||||
);
|
||||
});
|
||||
|
||||
test('press backspace after bookmark block can select bookmark block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const { insertAttachment, waitLoading } = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await pressEnter(page);
|
||||
await pressArrowUp(page);
|
||||
await insertAttachment();
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
await focusRichText(page);
|
||||
await assertBlockCount(page, 'paragraph', 1);
|
||||
await assertRichTextInlineRange(page, 0, 0);
|
||||
await pressBackspace(page);
|
||||
await assertBlockSelections(page, ['4']);
|
||||
await assertBlockCount(page, 'paragraph', 0);
|
||||
});
|
||||
|
||||
test('cancel file picker with input element resolves', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
const { attachment } = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await pressEnter(page);
|
||||
await pressArrowUp(page);
|
||||
|
||||
await page.evaluate(() => {
|
||||
// Force fallback to input[type=file]
|
||||
window.showOpenFilePicker = undefined;
|
||||
});
|
||||
|
||||
const slashMenu = page.locator(`.slash-menu`);
|
||||
await waitNextFrame(page);
|
||||
await type(page, '/file', 100);
|
||||
await expect(slashMenu).toBeVisible();
|
||||
|
||||
const fileChooser = page.waitForEvent('filechooser');
|
||||
await pressEnter(page);
|
||||
const inputFile = page.locator("input[type='file']");
|
||||
await expect(inputFile).toHaveCount(1);
|
||||
|
||||
// This does not trigger `cancel` event and,
|
||||
// therefore, the test isn't representative.
|
||||
// Waiting for https://github.com/microsoft/playwright/issues/27524
|
||||
await (await fileChooser).setFiles([]);
|
||||
|
||||
await expect(attachment).toHaveCount(0);
|
||||
await expect(inputFile).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('indent attachment block to paragraph', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const { insertAttachment, waitLoading } = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await pressEnter(page);
|
||||
await insertAttachment();
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
await assertBlockChildrenIds(page, '1', ['2', '4']);
|
||||
await assertBlockFlavour(page, '1', 'affine:note');
|
||||
await assertBlockFlavour(page, '2', 'affine:paragraph');
|
||||
await assertBlockFlavour(page, '4', 'affine:attachment');
|
||||
|
||||
await focusRichText(page);
|
||||
await pressArrowDown(page);
|
||||
await assertBlockSelections(page, ['4']);
|
||||
await pressTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['2']);
|
||||
await assertBlockChildrenIds(page, '2', ['4']);
|
||||
|
||||
await pressShiftTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['2', '4']);
|
||||
});
|
||||
|
||||
test('indent attachment block to list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const { insertAttachment, waitLoading } = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '- a');
|
||||
await pressEnter(page);
|
||||
await insertAttachment();
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
await assertBlockChildrenIds(page, '1', ['3', '5']);
|
||||
await assertBlockFlavour(page, '1', 'affine:note');
|
||||
await assertBlockFlavour(page, '3', 'affine:list');
|
||||
await assertBlockFlavour(page, '5', 'affine:attachment');
|
||||
|
||||
await focusRichText(page);
|
||||
await pressArrowDown(page);
|
||||
await assertBlockSelections(page, ['5']);
|
||||
await pressTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['3']);
|
||||
await assertBlockChildrenIds(page, '3', ['5']);
|
||||
|
||||
await pressShiftTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['3', '5']);
|
||||
});
|
||||
|
||||
test('attachment can be dragged from note to surface top level block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
const { insertAttachment, waitLoading } = getAttachment(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await insertAttachment();
|
||||
|
||||
// Wait for the attachment to be uploaded
|
||||
await waitLoading();
|
||||
|
||||
await switchEditorMode(page);
|
||||
await page.mouse.dblclick(450, 450);
|
||||
|
||||
await dragBlockToPoint(page, '4', { x: 200, y: 200 });
|
||||
|
||||
await waitNextFrame(page);
|
||||
await assertParentBlockFlavour(page, '4', 'affine:surface');
|
||||
});
|
||||
590
blocksuite/tests-legacy/basic.spec.ts
Normal file
590
blocksuite/tests-legacy/basic.spec.ts
Normal file
@@ -0,0 +1,590 @@
|
||||
import type { DeltaInsert } from '@inline/types.js';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addNoteByClick,
|
||||
captureHistory,
|
||||
click,
|
||||
disconnectByClick,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
focusTitle,
|
||||
getCurrentEditorTheme,
|
||||
getCurrentHTMLTheme,
|
||||
getPageSnapshot,
|
||||
initEmptyEdgelessState,
|
||||
initEmptyParagraphState,
|
||||
pressArrowLeft,
|
||||
pressArrowRight,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressForwardDelete,
|
||||
pressForwardDeleteWord,
|
||||
pressShiftEnter,
|
||||
redoByClick,
|
||||
redoByKeyboard,
|
||||
setSelection,
|
||||
switchEditorMode,
|
||||
toggleDarkMode,
|
||||
type,
|
||||
undoByClick,
|
||||
undoByKeyboard,
|
||||
waitDefaultPageLoaded,
|
||||
waitNextFrame,
|
||||
} from './utils/actions/index.js';
|
||||
import {
|
||||
assertBlockChildrenIds,
|
||||
assertEmpty,
|
||||
assertRichTextInlineDeltas,
|
||||
assertRichTexts,
|
||||
assertText,
|
||||
assertTitle,
|
||||
} from './utils/asserts.js';
|
||||
import { scoped, test } from './utils/playwright.js';
|
||||
import { getFormatBar } from './utils/query.js';
|
||||
|
||||
const BASIC_DEFAULT_SNAPSHOT = 'basic test default';
|
||||
|
||||
test(scoped`basic input`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
|
||||
await test.expect(page).toHaveTitle(/BlockSuite/);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${BASIC_DEFAULT_SNAPSHOT}.json`
|
||||
);
|
||||
await assertText(page, 'hello');
|
||||
});
|
||||
|
||||
test(scoped`basic init with external text`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
|
||||
await page.evaluate(() => {
|
||||
const { doc } = window;
|
||||
const rootId = doc.addBlock('affine:page', {
|
||||
title: new doc.Text('hello'),
|
||||
});
|
||||
const note = doc.addBlock('affine:note', {}, rootId);
|
||||
|
||||
const text = new doc.Text('world');
|
||||
doc.addBlock('affine:paragraph', { text }, note);
|
||||
|
||||
const delta = [
|
||||
{ insert: 'foo ' },
|
||||
{ insert: 'bar', attributes: { bold: true } },
|
||||
];
|
||||
doc.addBlock(
|
||||
'affine:paragraph',
|
||||
{
|
||||
text: new doc.Text(delta as DeltaInsert[]),
|
||||
},
|
||||
note
|
||||
);
|
||||
});
|
||||
|
||||
await assertTitle(page, 'hello');
|
||||
await assertRichTexts(page, ['world', 'foo bar']);
|
||||
await focusRichText(page);
|
||||
});
|
||||
|
||||
test(scoped`basic multi user state`, async ({ context, page: pageA }) => {
|
||||
const room = await enterPlaygroundRoom(pageA);
|
||||
await initEmptyParagraphState(pageA);
|
||||
await waitNextFrame(pageA);
|
||||
await waitDefaultPageLoaded(pageA);
|
||||
await focusTitle(pageA);
|
||||
await type(pageA, 'hello');
|
||||
|
||||
const pageB = await context.newPage();
|
||||
await enterPlaygroundRoom(pageB, {
|
||||
flags: {},
|
||||
room,
|
||||
noInit: true,
|
||||
});
|
||||
await waitDefaultPageLoaded(pageB);
|
||||
await focusTitle(pageB);
|
||||
await assertTitle(pageB, 'hello');
|
||||
|
||||
await type(pageB, ' world');
|
||||
await assertTitle(pageA, 'hello world');
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`A open and edit, then joins B`,
|
||||
async ({ context, page: pageA }) => {
|
||||
const room = await enterPlaygroundRoom(pageA);
|
||||
await initEmptyParagraphState(pageA);
|
||||
await waitNextFrame(pageA);
|
||||
await focusRichText(pageA);
|
||||
await type(pageA, 'hello');
|
||||
|
||||
const pageB = await context.newPage();
|
||||
await enterPlaygroundRoom(pageB, {
|
||||
flags: {},
|
||||
room,
|
||||
noInit: true,
|
||||
});
|
||||
|
||||
// wait until pageB content updated
|
||||
await assertText(pageB, 'hello');
|
||||
await Promise.all([
|
||||
assertText(pageA, 'hello'),
|
||||
expect(await getPageSnapshot(pageA, true)).toMatchSnapshot(
|
||||
`${BASIC_DEFAULT_SNAPSHOT}.json`
|
||||
),
|
||||
expect(await getPageSnapshot(pageB, true)).toMatchSnapshot(
|
||||
`${BASIC_DEFAULT_SNAPSHOT}.json`
|
||||
),
|
||||
assertBlockChildrenIds(pageA, '0', ['1']),
|
||||
assertBlockChildrenIds(pageB, '0', ['1']),
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
test(scoped`A first open, B first edit`, async ({ context, page: pageA }) => {
|
||||
const room = await enterPlaygroundRoom(pageA);
|
||||
await initEmptyParagraphState(pageA);
|
||||
await waitNextFrame(pageA);
|
||||
await focusRichText(pageA);
|
||||
|
||||
const pageB = await context.newPage();
|
||||
await enterPlaygroundRoom(pageB, {
|
||||
room,
|
||||
noInit: true,
|
||||
});
|
||||
await pageB.waitForTimeout(500);
|
||||
await focusRichText(pageB);
|
||||
|
||||
await waitNextFrame(pageA);
|
||||
await waitNextFrame(pageB);
|
||||
await type(pageB, 'hello');
|
||||
await pageA.waitForTimeout(500);
|
||||
|
||||
// wait until pageA content updated
|
||||
await assertText(pageA, 'hello');
|
||||
await assertText(pageB, 'hello');
|
||||
await Promise.all([
|
||||
expect(await getPageSnapshot(pageA, true)).toMatchSnapshot(
|
||||
`${BASIC_DEFAULT_SNAPSHOT}.json`
|
||||
),
|
||||
expect(await getPageSnapshot(pageB, true)).toMatchSnapshot(
|
||||
`${BASIC_DEFAULT_SNAPSHOT}.json`
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`does not sync when disconnected`,
|
||||
async ({ browser, page: pageA }) => {
|
||||
test.fail();
|
||||
|
||||
const room = await enterPlaygroundRoom(pageA);
|
||||
const pageB = await browser.newPage();
|
||||
await enterPlaygroundRoom(pageB, { flags: {}, room });
|
||||
|
||||
await disconnectByClick(pageA);
|
||||
await disconnectByClick(pageB);
|
||||
|
||||
// click together, both init with default id should lead to conflicts
|
||||
await initEmptyParagraphState(pageA);
|
||||
await initEmptyParagraphState(pageB);
|
||||
|
||||
await waitNextFrame(pageA);
|
||||
await focusRichText(pageA);
|
||||
await waitNextFrame(pageB);
|
||||
await focusRichText(pageB);
|
||||
await waitNextFrame(pageA);
|
||||
|
||||
await type(pageA, '');
|
||||
await waitNextFrame(pageB);
|
||||
await type(pageB, '');
|
||||
await waitNextFrame(pageA);
|
||||
await type(pageA, 'hello');
|
||||
await waitNextFrame(pageB);
|
||||
|
||||
await assertText(pageB, 'hello');
|
||||
await assertText(pageA, 'hello'); // actually '\n'
|
||||
}
|
||||
);
|
||||
|
||||
test(scoped`basic paired undo/redo`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
|
||||
await assertText(page, 'hello');
|
||||
await undoByClick(page);
|
||||
await assertEmpty(page);
|
||||
await redoByClick(page);
|
||||
await assertText(page, 'hello');
|
||||
|
||||
await undoByClick(page);
|
||||
await assertEmpty(page);
|
||||
await redoByClick(page);
|
||||
await assertText(page, 'hello');
|
||||
});
|
||||
|
||||
test(scoped`undo/redo with keyboard`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
|
||||
await assertText(page, 'hello');
|
||||
await undoByKeyboard(page);
|
||||
await assertEmpty(page);
|
||||
await redoByClick(page);
|
||||
await assertText(page, 'hello');
|
||||
});
|
||||
|
||||
test(scoped`undo after adding block twice`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['hello']);
|
||||
await redoByKeyboard(page);
|
||||
await assertRichTexts(page, ['hello', 'world']);
|
||||
});
|
||||
|
||||
test(scoped`undo/redo twice after adding block twice`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
await assertRichTexts(page, ['hello', 'world']);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await redoByClick(page);
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await assertRichTexts(page, ['hello', 'world']);
|
||||
});
|
||||
|
||||
test(scoped`should undo/redo works on title`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await waitNextFrame(page);
|
||||
await focusTitle(page);
|
||||
await type(page, 'title');
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello world');
|
||||
|
||||
await assertTitle(page, 'title');
|
||||
await assertRichTexts(page, ['hello world']);
|
||||
|
||||
await captureHistory(page);
|
||||
await pressBackspace(page, 5);
|
||||
await captureHistory(page);
|
||||
await focusTitle(page);
|
||||
await type(page, ' something');
|
||||
|
||||
await assertTitle(page, 'title something');
|
||||
await assertRichTexts(page, ['hello ']);
|
||||
|
||||
await focusRichText(page);
|
||||
await undoByKeyboard(page);
|
||||
await assertTitle(page, 'title');
|
||||
await assertRichTexts(page, ['hello ']);
|
||||
await undoByKeyboard(page);
|
||||
await assertTitle(page, 'title');
|
||||
await assertRichTexts(page, ['hello world']);
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await assertTitle(page, 'title');
|
||||
await assertRichTexts(page, ['hello ']);
|
||||
await redoByKeyboard(page);
|
||||
await assertTitle(page, 'title something');
|
||||
await assertRichTexts(page, ['hello ']);
|
||||
});
|
||||
|
||||
test(scoped`undo multi notes`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await addNoteByClick(page);
|
||||
await assertRichTexts(page, ['', '']);
|
||||
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await redoByClick(page);
|
||||
await assertRichTexts(page, ['', '']);
|
||||
});
|
||||
|
||||
test(scoped`change theme`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const currentTheme = await getCurrentHTMLTheme(page);
|
||||
await toggleDarkMode(page);
|
||||
const expectNextTheme = currentTheme === 'light' ? 'dark' : 'light';
|
||||
const nextHTMLTheme = await getCurrentHTMLTheme(page);
|
||||
expect(nextHTMLTheme).toBe(expectNextTheme);
|
||||
|
||||
const nextEditorTheme = await getCurrentEditorTheme(page);
|
||||
expect(nextEditorTheme).toBe(expectNextTheme);
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`should be able to delete an emoji completely by pressing backspace once`,
|
||||
async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2138',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '🌷🙅♂️🏳️🌈');
|
||||
await pressBackspace(page);
|
||||
await pressBackspace(page);
|
||||
await pressBackspace(page);
|
||||
await assertText(page, '');
|
||||
}
|
||||
);
|
||||
|
||||
test(scoped`delete emoji in the middle of the text`, async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2138',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '1🌷1🙅♂️1🏳️🌈1👨👩👧👦1');
|
||||
await pressArrowLeft(page, 1);
|
||||
await pressBackspace(page);
|
||||
await pressArrowLeft(page, 1);
|
||||
await pressBackspace(page);
|
||||
await pressArrowLeft(page, 1);
|
||||
await pressBackspace(page);
|
||||
await pressArrowLeft(page, 1);
|
||||
await pressBackspace(page);
|
||||
await assertText(page, '11111');
|
||||
});
|
||||
|
||||
test(scoped`delete emoji forward`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '1🌷1🙅♂️1🏳️🌈1👨👩👧👦1');
|
||||
await pressArrowLeft(page, 8);
|
||||
await pressForwardDelete(page);
|
||||
await pressArrowRight(page, 1);
|
||||
await pressForwardDelete(page);
|
||||
await pressArrowRight(page, 1);
|
||||
await pressForwardDelete(page);
|
||||
await pressArrowRight(page, 1);
|
||||
await pressForwardDelete(page);
|
||||
await assertText(page, '11111');
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`ZERO_WIDTH_SPACE should be counted by one cursor position`,
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await pressShiftEnter(page);
|
||||
await type(page, 'asdfg');
|
||||
await pressEnter(page);
|
||||
await undoByKeyboard(page);
|
||||
await page.waitForTimeout(300);
|
||||
await pressBackspace(page);
|
||||
await assertRichTexts(page, ['\nasdf']);
|
||||
}
|
||||
);
|
||||
|
||||
test('when no note block, click editing area auto add a new note block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await page.locator('affine-edgeless-note').click({ force: true });
|
||||
await pressBackspace(page);
|
||||
await switchEditorMode(page);
|
||||
const edgelessNote = await page.evaluate(() => {
|
||||
return document.querySelector('affine-edgeless-note');
|
||||
});
|
||||
expect(edgelessNote).toBeNull();
|
||||
await click(page, { x: 200, y: 280 });
|
||||
|
||||
const pageNote = await page.evaluate(() => {
|
||||
return document.querySelector('affine-note');
|
||||
});
|
||||
expect(pageNote).not.toBeNull();
|
||||
});
|
||||
|
||||
test(scoped`automatic identify url text`, async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'abc https://google.com ');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('ctrl+delete to delete one word forward', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'aaa bbb ccc');
|
||||
await pressArrowLeft(page, 8);
|
||||
await pressForwardDeleteWord(page);
|
||||
await assertText(page, 'aaa ccc');
|
||||
});
|
||||
|
||||
test('extended inline format', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'aaabbbaaa');
|
||||
|
||||
const { boldBtn, italicBtn, underlineBtn, strikeBtn, codeBtn } =
|
||||
getFormatBar(page);
|
||||
await setSelection(page, 0, 3, 0, 6);
|
||||
await boldBtn.click();
|
||||
await italicBtn.click();
|
||||
await underlineBtn.click();
|
||||
await strikeBtn.click();
|
||||
await codeBtn.click();
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
insert: 'bbb',
|
||||
attributes: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
underline: true,
|
||||
strike: true,
|
||||
code: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
]);
|
||||
|
||||
// aaa|bbbccc
|
||||
await setSelection(page, 2, 3, 2, 3);
|
||||
await captureHistory(page);
|
||||
await type(page, 'c');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aaac',
|
||||
},
|
||||
{
|
||||
insert: 'bbb',
|
||||
attributes: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
underline: true,
|
||||
strike: true,
|
||||
code: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
|
||||
// aaab|bbccc
|
||||
await setSelection(page, 2, 4, 2, 4);
|
||||
await type(page, 'c');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
insert: 'bcbb',
|
||||
attributes: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
underline: true,
|
||||
strike: true,
|
||||
code: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
|
||||
// aaab|b|bccc
|
||||
await setSelection(page, 2, 4, 2, 5);
|
||||
await type(page, 'c');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
insert: 'bcb',
|
||||
attributes: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
underline: true,
|
||||
strike: true,
|
||||
code: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
]);
|
||||
await undoByKeyboard(page);
|
||||
|
||||
// aaabbb|ccc
|
||||
await setSelection(page, 2, 6, 2, 6);
|
||||
await type(page, 'c');
|
||||
await assertRichTextInlineDeltas(page, [
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
{
|
||||
insert: 'bbb',
|
||||
attributes: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
underline: true,
|
||||
strike: true,
|
||||
code: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'c',
|
||||
attributes: {
|
||||
bold: true,
|
||||
italic: true,
|
||||
underline: true,
|
||||
strike: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'aaa',
|
||||
},
|
||||
]);
|
||||
});
|
||||
461
blocksuite/tests-legacy/bookmark.spec.ts
Normal file
461
blocksuite/tests-legacy/bookmark.spec.ts
Normal file
@@ -0,0 +1,461 @@
|
||||
import './utils/declare-test-window.js';
|
||||
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect } from '@playwright/test';
|
||||
import type { BlockSnapshot } from '@store/index.js';
|
||||
import { ignoreSnapshotId } from 'utils/ignore.js';
|
||||
import { getEmbedCardToolbar } from 'utils/query.js';
|
||||
|
||||
import {
|
||||
activeNoteInEdgeless,
|
||||
copyByKeyboard,
|
||||
dragBlockToPoint,
|
||||
enterPlaygroundRoom,
|
||||
expectConsoleMessage,
|
||||
focusRichText,
|
||||
getPageSnapshot,
|
||||
initEmptyEdgelessState,
|
||||
initEmptyParagraphState,
|
||||
pasteByKeyboard,
|
||||
pressArrowDown,
|
||||
pressArrowRight,
|
||||
pressArrowUp,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressShiftTab,
|
||||
pressTab,
|
||||
selectAllByKeyboard,
|
||||
setInlineRangeInSelectedRichText,
|
||||
SHORT_KEY,
|
||||
switchEditorMode,
|
||||
type,
|
||||
waitForInlineEditorStateUpdated,
|
||||
waitNextFrame,
|
||||
} from './utils/actions/index.js';
|
||||
import {
|
||||
assertAlmostEqual,
|
||||
assertBlockChildrenIds,
|
||||
assertBlockCount,
|
||||
assertBlockFlavour,
|
||||
assertBlockSelections,
|
||||
assertExists,
|
||||
assertParentBlockFlavour,
|
||||
assertRichTextInlineRange,
|
||||
} from './utils/asserts.js';
|
||||
import { scoped, test } from './utils/playwright.js';
|
||||
|
||||
const LOCAL_HOST_URL = 'http://localhost';
|
||||
|
||||
const YOUTUBE_URL = 'https://www.youtube.com/watch?v=fakeid';
|
||||
|
||||
const FIGMA_URL = 'https://www.figma.com/design/JuXs6uOAICwf4I4tps0xKZ123';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.route(
|
||||
'https://affine-worker.toeverything.workers.dev/api/worker/link-preview',
|
||||
async route => {
|
||||
await route.fulfill({
|
||||
json: {},
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
const createBookmarkBlockBySlashMenu = async (
|
||||
page: Page,
|
||||
url = LOCAL_HOST_URL
|
||||
) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await page.waitForTimeout(100);
|
||||
await type(page, '/link', 100);
|
||||
await pressEnter(page);
|
||||
await page.waitForTimeout(100);
|
||||
await type(page, url);
|
||||
await pressEnter(page);
|
||||
};
|
||||
|
||||
test(scoped`create bookmark by slash menu`, async ({ page }, testInfo) => {
|
||||
await createBookmarkBlockBySlashMenu(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test(scoped`covert bookmark block to link text`, async ({ page }, testInfo) => {
|
||||
await createBookmarkBlockBySlashMenu(page);
|
||||
const bookmark = page.locator('affine-bookmark');
|
||||
await bookmark.click();
|
||||
await page.waitForTimeout(100);
|
||||
await page.getByRole('button', { name: 'Switch view' }).click();
|
||||
await page.getByRole('button', { name: 'Inline view' }).click();
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`copy url to create bookmark in page mode`,
|
||||
async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, LOCAL_HOST_URL);
|
||||
await setInlineRangeInSelectedRichText(page, 0, LOCAL_HOST_URL.length);
|
||||
await copyByKeyboard(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '/link');
|
||||
await pressEnter(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+v`);
|
||||
await pressEnter(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test(
|
||||
scoped`copy url to create bookmark in edgeless mode`,
|
||||
async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const ids = await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, LOCAL_HOST_URL);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
await activeNoteInEdgeless(page, ids.noteId);
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
await pressArrowRight(page);
|
||||
await waitNextFrame(page);
|
||||
await type(page, '/link', 100);
|
||||
await pressEnter(page);
|
||||
await page.waitForTimeout(100);
|
||||
await waitNextFrame(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+v`);
|
||||
await pressEnter(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test.fixme(
|
||||
scoped`support dragging bookmark block directly`,
|
||||
async ({ page }, testInfo) => {
|
||||
await createBookmarkBlockBySlashMenu(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
const bookmark = page.locator('affine-bookmark');
|
||||
const rect = await bookmark.boundingBox();
|
||||
if (!rect) {
|
||||
throw new Error('image not found');
|
||||
}
|
||||
|
||||
// add new paragraph blocks
|
||||
await page.mouse.click(rect.x + 20, rect.y + rect.height + 20);
|
||||
await focusRichText(page);
|
||||
await type(page, '111');
|
||||
await page.waitForTimeout(200);
|
||||
await pressEnter(page);
|
||||
|
||||
await type(page, '222');
|
||||
await page.waitForTimeout(200);
|
||||
await pressEnter(page);
|
||||
|
||||
await type(page, '333');
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.waitForTimeout(200);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_after_add_paragraph.json`
|
||||
);
|
||||
|
||||
// drag bookmark block
|
||||
await page.mouse.move(rect.x + 20, rect.y + 20);
|
||||
await page.mouse.down();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.mouse.move(rect.x + 40, rect.y + rect.height + 80, {
|
||||
steps: 5,
|
||||
});
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
await page.mouse.up();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
const rects = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(rects).toHaveCount(1);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_after_drag.json`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test('press backspace after bookmark block can select bookmark block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await pressEnter(page);
|
||||
await pressArrowUp(page);
|
||||
await type(page, '/link');
|
||||
await pressEnter(page);
|
||||
await page.waitForTimeout(100);
|
||||
await type(page, LOCAL_HOST_URL);
|
||||
await pressEnter(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await assertBlockCount(page, 'paragraph', 1);
|
||||
await assertRichTextInlineRange(page, 0, 0);
|
||||
await pressBackspace(page);
|
||||
await assertBlockSelections(page, ['4']);
|
||||
await assertBlockCount(page, 'paragraph', 0);
|
||||
});
|
||||
|
||||
test.describe('embed card toolbar', () => {
|
||||
async function showEmbedCardToolbar(page: Page) {
|
||||
await createBookmarkBlockBySlashMenu(page);
|
||||
const bookmark = page.locator('affine-bookmark');
|
||||
await bookmark.click();
|
||||
await page.waitForTimeout(100);
|
||||
const { embedCardToolbar } = getEmbedCardToolbar(page);
|
||||
await expect(embedCardToolbar).toBeVisible();
|
||||
}
|
||||
|
||||
test('show toolbar when bookmark selected', async ({ page }) => {
|
||||
await showEmbedCardToolbar(page);
|
||||
});
|
||||
|
||||
test('copy bookmark url by copy button', async ({ page }, testInfo) => {
|
||||
await showEmbedCardToolbar(page);
|
||||
const { copyButton } = getEmbedCardToolbar(page);
|
||||
await copyButton.click();
|
||||
await page.mouse.click(600, 600);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await pasteByKeyboard(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('change card style', async ({ page }) => {
|
||||
await showEmbedCardToolbar(page);
|
||||
const bookmark = page.locator('affine-bookmark');
|
||||
const { openCardStyleMenu } = getEmbedCardToolbar(page);
|
||||
await openCardStyleMenu();
|
||||
const { cardStyleHorizontalButton, cardStyleListButton } =
|
||||
getEmbedCardToolbar(page);
|
||||
await cardStyleListButton.click();
|
||||
await waitNextFrame(page);
|
||||
const listStyleBookmarkBox = await bookmark.boundingBox();
|
||||
assertExists(listStyleBookmarkBox);
|
||||
assertAlmostEqual(listStyleBookmarkBox.width, 752, 2);
|
||||
assertAlmostEqual(listStyleBookmarkBox.height, 46, 2);
|
||||
|
||||
await openCardStyleMenu();
|
||||
await cardStyleHorizontalButton.click();
|
||||
await waitNextFrame(page);
|
||||
const horizontalStyleBookmarkBox = await bookmark.boundingBox();
|
||||
assertExists(horizontalStyleBookmarkBox);
|
||||
assertAlmostEqual(horizontalStyleBookmarkBox.width, 752, 2);
|
||||
assertAlmostEqual(horizontalStyleBookmarkBox.height, 116, 2);
|
||||
});
|
||||
});
|
||||
|
||||
test('indent bookmark block to paragraph', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await pressEnter(page);
|
||||
await type(page, '/link', 100);
|
||||
await pressEnter(page);
|
||||
await type(page, LOCAL_HOST_URL);
|
||||
await pressEnter(page);
|
||||
|
||||
await assertBlockChildrenIds(page, '1', ['2', '4']);
|
||||
await assertBlockFlavour(page, '1', 'affine:note');
|
||||
await assertBlockFlavour(page, '2', 'affine:paragraph');
|
||||
await assertBlockFlavour(page, '4', 'affine:bookmark');
|
||||
|
||||
await focusRichText(page);
|
||||
await pressArrowDown(page);
|
||||
await assertBlockSelections(page, ['4']);
|
||||
await pressTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['2']);
|
||||
await assertBlockChildrenIds(page, '2', ['4']);
|
||||
|
||||
await pressShiftTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['2', '4']);
|
||||
});
|
||||
|
||||
test('indent bookmark block to list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '- a');
|
||||
await pressEnter(page);
|
||||
await type(page, '/link', 100);
|
||||
await pressEnter(page);
|
||||
await type(page, LOCAL_HOST_URL);
|
||||
await pressEnter(page);
|
||||
|
||||
await assertBlockChildrenIds(page, '1', ['3', '5']);
|
||||
await assertBlockFlavour(page, '1', 'affine:note');
|
||||
await assertBlockFlavour(page, '3', 'affine:list');
|
||||
await assertBlockFlavour(page, '5', 'affine:bookmark');
|
||||
|
||||
await focusRichText(page);
|
||||
await pressArrowDown(page);
|
||||
await assertBlockSelections(page, ['5']);
|
||||
await pressTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['3']);
|
||||
await assertBlockChildrenIds(page, '3', ['5']);
|
||||
|
||||
await pressShiftTab(page);
|
||||
await assertBlockChildrenIds(page, '1', ['3', '5']);
|
||||
});
|
||||
|
||||
test('bookmark can be dragged from note to surface top level block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await page.waitForTimeout(100);
|
||||
await type(page, '/link', 100);
|
||||
await pressEnter(page);
|
||||
await page.waitForTimeout(100);
|
||||
await type(page, LOCAL_HOST_URL);
|
||||
await pressEnter(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await page.mouse.dblclick(450, 450);
|
||||
|
||||
await dragBlockToPoint(page, '4', { x: 200, y: 200 });
|
||||
|
||||
await waitNextFrame(page);
|
||||
await assertParentBlockFlavour(page, '4', 'affine:surface');
|
||||
});
|
||||
|
||||
test.describe('embed youtube card', () => {
|
||||
test(scoped`create youtube card by slash menu`, async ({ page }) => {
|
||||
expectConsoleMessage(page, /Unrecognized feature/, 'warning');
|
||||
expectConsoleMessage(page, /Failed to load resource/);
|
||||
await createBookmarkBlockBySlashMenu(page, YOUTUBE_URL);
|
||||
const snapshot = (await getPageSnapshot(page)) as BlockSnapshot;
|
||||
expect(ignoreSnapshotId(snapshot)).toMatchSnapshot('embed-youtube.json');
|
||||
});
|
||||
|
||||
test(scoped`change youtube card style`, async ({ page }) => {
|
||||
expectConsoleMessage(page, /Unrecognized feature/, 'warning');
|
||||
expectConsoleMessage(page, /Failed to load resource/);
|
||||
|
||||
await createBookmarkBlockBySlashMenu(page, YOUTUBE_URL);
|
||||
const youtube = page.locator('affine-embed-youtube-block');
|
||||
await youtube.click();
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
// change to card view
|
||||
const embedToolbar = page.locator('affine-embed-card-toolbar');
|
||||
await expect(embedToolbar).toBeVisible();
|
||||
const embedView = page.locator('editor-menu-button', {
|
||||
hasText: 'embed view',
|
||||
});
|
||||
await expect(embedView).toBeVisible();
|
||||
await embedView.click();
|
||||
const cardView = page.locator('editor-menu-action', {
|
||||
hasText: 'card view',
|
||||
});
|
||||
await expect(cardView).toBeVisible();
|
||||
await cardView.click();
|
||||
const snapshot = (await getPageSnapshot(page)) as BlockSnapshot;
|
||||
expect(ignoreSnapshotId(snapshot)).toMatchSnapshot(
|
||||
'horizontal-youtube.json'
|
||||
);
|
||||
|
||||
// change to embed view
|
||||
const bookmark = page.locator('affine-bookmark');
|
||||
await bookmark.click();
|
||||
await page.waitForTimeout(100);
|
||||
const cardView2 = page.locator('editor-icon-button', {
|
||||
hasText: 'card view',
|
||||
});
|
||||
await expect(cardView2).toBeVisible();
|
||||
await cardView2.click();
|
||||
const embedView2 = page.locator('editor-menu-action', {
|
||||
hasText: 'embed view',
|
||||
});
|
||||
await expect(embedView2).toBeVisible();
|
||||
await embedView2.click();
|
||||
const snapshot2 = (await getPageSnapshot(page)) as BlockSnapshot;
|
||||
expect(ignoreSnapshotId(snapshot2)).toMatchSnapshot('embed-youtube.json');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('embed figma card', () => {
|
||||
test(scoped`create figma card by slash menu`, async ({ page }) => {
|
||||
expectConsoleMessage(page, /Failed to load resource/);
|
||||
expectConsoleMessage(page, /Refused to frame/);
|
||||
await createBookmarkBlockBySlashMenu(page, FIGMA_URL);
|
||||
const snapshot = (await getPageSnapshot(page)) as BlockSnapshot;
|
||||
expect(ignoreSnapshotId(snapshot)).toMatchSnapshot('embed-figma.json');
|
||||
});
|
||||
|
||||
test(scoped`change figma card style`, async ({ page }) => {
|
||||
expectConsoleMessage(page, /Failed to load resource/);
|
||||
expectConsoleMessage(page, /Refused to frame/);
|
||||
expectConsoleMessage(page, /Running frontend commit/, 'log');
|
||||
await createBookmarkBlockBySlashMenu(page, FIGMA_URL);
|
||||
const youtube = page.locator('affine-embed-figma-block');
|
||||
await youtube.click();
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
// change to card view
|
||||
const embedToolbar = page.locator('affine-embed-card-toolbar');
|
||||
await expect(embedToolbar).toBeVisible();
|
||||
const embedView = page.locator('editor-menu-button', {
|
||||
hasText: 'embed view',
|
||||
});
|
||||
await expect(embedView).toBeVisible();
|
||||
await embedView.click();
|
||||
const cardView = page.locator('editor-menu-action', {
|
||||
hasText: 'card view',
|
||||
});
|
||||
await expect(cardView).toBeVisible();
|
||||
await cardView.click();
|
||||
const snapshot = (await getPageSnapshot(page)) as BlockSnapshot;
|
||||
expect(ignoreSnapshotId(snapshot)).toMatchSnapshot('horizontal-figma.json');
|
||||
|
||||
// change to embed view
|
||||
const bookmark = page.locator('affine-bookmark');
|
||||
await bookmark.click();
|
||||
await page.waitForTimeout(100);
|
||||
const cardView2 = page.locator('editor-icon-button', {
|
||||
hasText: 'card view',
|
||||
});
|
||||
await expect(cardView2).toBeVisible();
|
||||
await cardView2.click();
|
||||
const embedView2 = page.locator('editor-menu-action', {
|
||||
hasText: 'embed view',
|
||||
});
|
||||
await expect(embedView2).toBeVisible();
|
||||
await embedView2.click();
|
||||
const snapshot2 = (await getPageSnapshot(page)) as BlockSnapshot;
|
||||
expect(ignoreSnapshotId(snapshot2)).toMatchSnapshot('embed-figma.json');
|
||||
});
|
||||
});
|
||||
409
blocksuite/tests-legacy/clipboard/clipboard.spec.ts
Normal file
409
blocksuite/tests-legacy/clipboard/clipboard.spec.ts
Normal file
@@ -0,0 +1,409 @@
|
||||
import '../utils/declare-test-window.js';
|
||||
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
captureHistory,
|
||||
copyByKeyboard,
|
||||
dragBetweenCoords,
|
||||
dragOverTitle,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
focusTitle,
|
||||
getClipboardHTML,
|
||||
getClipboardSnapshot,
|
||||
getClipboardText,
|
||||
getCurrentEditorDocId,
|
||||
getEditorLocator,
|
||||
getPageSnapshot,
|
||||
initEmptyParagraphState,
|
||||
mockParseDocUrlService,
|
||||
pasteByKeyboard,
|
||||
pasteContent,
|
||||
pressEnter,
|
||||
pressShiftTab,
|
||||
pressTab,
|
||||
resetHistory,
|
||||
setInlineRangeInSelectedRichText,
|
||||
setSelection,
|
||||
SHORT_KEY,
|
||||
type,
|
||||
undoByClick,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockTypes,
|
||||
assertClipItems,
|
||||
assertExists,
|
||||
assertRichTexts,
|
||||
assertText,
|
||||
assertTitle,
|
||||
} from '../utils/asserts.js';
|
||||
import { scoped, test } from '../utils/playwright.js';
|
||||
|
||||
test(scoped`clipboard copy paste`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'test');
|
||||
await setInlineRangeInSelectedRichText(page, 0, 3);
|
||||
await waitNextFrame(page);
|
||||
await copyByKeyboard(page);
|
||||
await focusRichText(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+v`);
|
||||
await assertText(page, 'testtes');
|
||||
});
|
||||
|
||||
test(scoped`clipboard copy paste title`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusTitle(page);
|
||||
|
||||
await type(page, 'test');
|
||||
await dragOverTitle(page);
|
||||
await waitNextFrame(page);
|
||||
await copyByKeyboard(page);
|
||||
await focusTitle(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+v`);
|
||||
await assertTitle(page, 'testtest');
|
||||
});
|
||||
|
||||
test(scoped`clipboard paste html`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
// set up clipboard data using html
|
||||
const clipData = {
|
||||
'text/html': `<span>aaa</span><span>bbb</span><span>ccc</span><bdi>ddd</bdi>`,
|
||||
};
|
||||
await waitNextFrame(page);
|
||||
await page.evaluate(
|
||||
({ clipData }) => {
|
||||
const dT = new DataTransfer();
|
||||
const e = new ClipboardEvent('paste', { clipboardData: dT });
|
||||
Object.defineProperty(e, 'target', {
|
||||
writable: false,
|
||||
value: document,
|
||||
});
|
||||
e.clipboardData?.setData('text/html', clipData['text/html']);
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
{ clipData }
|
||||
);
|
||||
await assertText(page, 'aaabbbcccddd');
|
||||
});
|
||||
|
||||
test(scoped`split block when paste`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await resetHistory(page);
|
||||
|
||||
const clipData = {
|
||||
'text/plain': `# text
|
||||
# h1
|
||||
`,
|
||||
};
|
||||
await type(page, 'abc');
|
||||
await captureHistory(page);
|
||||
|
||||
await setInlineRangeInSelectedRichText(page, 1, 1);
|
||||
await pasteContent(page, clipData);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertRichTexts(page, ['atext', 'h1c']);
|
||||
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['abc']);
|
||||
|
||||
await type(page, 'aa');
|
||||
await pressEnter(page);
|
||||
await type(page, 'bb');
|
||||
const topLeft123 = await getEditorLocator(page)
|
||||
.locator('[data-block-id="2"] .inline-editor')
|
||||
.boundingBox();
|
||||
const bottomRight789 = await getEditorLocator(page)
|
||||
.locator('[data-block-id="4"] .inline-editor')
|
||||
.boundingBox();
|
||||
assertExists(topLeft123);
|
||||
assertExists(bottomRight789);
|
||||
await dragBetweenCoords(page, topLeft123, bottomRight789);
|
||||
|
||||
// FIXME see https://github.com/toeverything/blocksuite/pull/878
|
||||
// await pasteContent(page, clipData);
|
||||
// await assertRichTexts(page, ['aaa', 'bbc', 'text', 'h1']);
|
||||
});
|
||||
|
||||
test(scoped`copy clipItems format`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await captureHistory(page);
|
||||
|
||||
const clipData = `
|
||||
- aa
|
||||
- bb
|
||||
- cc
|
||||
- dd
|
||||
`;
|
||||
|
||||
await pasteContent(page, { 'text/plain': clipData });
|
||||
await page.waitForTimeout(100);
|
||||
await setSelection(page, 4, 1, 5, 1);
|
||||
assertClipItems(page, 'text/plain', 'bc');
|
||||
assertClipItems(page, 'text/html', '<ul><li>b<ul><li>c</li></ul></li></ul>');
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test(scoped`copy partially selected text`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, '123 456 789');
|
||||
|
||||
// select 456
|
||||
await setInlineRangeInSelectedRichText(page, 4, 3);
|
||||
await copyByKeyboard(page);
|
||||
assertClipItems(page, 'text/plain', '456');
|
||||
|
||||
// move to line end
|
||||
await setInlineRangeInSelectedRichText(page, 11, 0);
|
||||
await pressEnter(page);
|
||||
await pasteByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertRichTexts(page, ['123 456 789', '456']);
|
||||
});
|
||||
|
||||
test(scoped`copy & paste outside editor`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await page.evaluate(() => {
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('id', 'input-test');
|
||||
input.value = '123';
|
||||
document.body.querySelector('#app')?.append(input);
|
||||
});
|
||||
await page.focus('#input-test');
|
||||
await page.dblclick('#input-test');
|
||||
await copyByKeyboard(page);
|
||||
await focusRichText(page);
|
||||
await pasteByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['123']);
|
||||
});
|
||||
|
||||
test('should keep first line format when pasted into a new line', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const clipData = `
|
||||
- [ ] aaa
|
||||
`;
|
||||
|
||||
await pasteContent(page, { 'text/plain': clipData });
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['aaa']);
|
||||
await assertBlockTypes(page, ['todo']);
|
||||
});
|
||||
|
||||
test(scoped`auto identify url`, async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
// set up clipboard data using html
|
||||
const clipData = {
|
||||
'text/plain': `test https://www.google.com`,
|
||||
};
|
||||
await waitNextFrame(page);
|
||||
await page.evaluate(
|
||||
({ clipData }) => {
|
||||
const dT = new DataTransfer();
|
||||
const e = new ClipboardEvent('paste', { clipboardData: dT });
|
||||
Object.defineProperty(e, 'target', {
|
||||
writable: false,
|
||||
value: document,
|
||||
});
|
||||
e.clipboardData?.setData('text/plain', clipData['text/plain']);
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
{ clipData }
|
||||
);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test(scoped`pasting internal url`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusTitle(page);
|
||||
await type(page, 'test page');
|
||||
|
||||
await focusRichText(page);
|
||||
const docId = await getCurrentEditorDocId(page);
|
||||
await mockParseDocUrlService(page, {
|
||||
'http://workspace/doc-id': docId,
|
||||
});
|
||||
await pasteContent(page, {
|
||||
'text/plain': 'http://workspace/doc-id',
|
||||
});
|
||||
await expect(page.locator('affine-reference')).toContainText('test page');
|
||||
});
|
||||
|
||||
test(scoped`pasting internal url with params`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusTitle(page);
|
||||
await type(page, 'test page');
|
||||
|
||||
await focusRichText(page);
|
||||
const docId = await getCurrentEditorDocId(page);
|
||||
await mockParseDocUrlService(page, {
|
||||
'http://workspace/doc-id?mode=page&blockIds=rL2_GXbtLU2SsJVfCSmh_': docId,
|
||||
});
|
||||
await pasteContent(page, {
|
||||
'text/plain':
|
||||
'http://workspace/doc-id?mode=page&blockIds=rL2_GXbtLU2SsJVfCSmh_',
|
||||
});
|
||||
await expect(page.locator('affine-reference')).toContainText('test page');
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`pasting an external URL from clipboard to automatically creating a link from selection`,
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusTitle(page);
|
||||
await type(page, 'test page');
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, 'title alias');
|
||||
await setSelection(page, 1, 6, 1, 11);
|
||||
|
||||
await pasteContent(page, {
|
||||
'text/plain': 'https://affine.pro/',
|
||||
});
|
||||
await expect(page.locator('affine-link')).toContainText('alias');
|
||||
}
|
||||
);
|
||||
|
||||
test(
|
||||
scoped`pasting an internal URL from clipboard to automatically creating a link from selection`,
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusTitle(page);
|
||||
await type(page, 'test page');
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, 'title alias');
|
||||
await setSelection(page, 1, 6, 1, 11);
|
||||
|
||||
const docId = await getCurrentEditorDocId(page);
|
||||
await mockParseDocUrlService(page, {
|
||||
'http://workspace/doc-id': docId,
|
||||
});
|
||||
await pasteContent(page, {
|
||||
'text/plain': 'http://workspace/doc-id',
|
||||
});
|
||||
await expect(page.locator('affine-reference')).toContainText('alias');
|
||||
}
|
||||
);
|
||||
|
||||
test(scoped`paste parent block`, async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/3153',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'This is parent');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.keyboard.press('Tab');
|
||||
await type(page, 'This is child 1');
|
||||
await page.keyboard.press('Enter');
|
||||
await page.keyboard.press('Tab');
|
||||
await type(page, 'This is child 2');
|
||||
await setInlineRangeInSelectedRichText(page, 0, 3);
|
||||
await copyByKeyboard(page);
|
||||
await focusRichText(page, 2);
|
||||
await page.keyboard.press(`${SHORT_KEY}+v`);
|
||||
await assertRichTexts(page, [
|
||||
'This is parent',
|
||||
'This is child 1',
|
||||
'This is child 2Thi',
|
||||
]);
|
||||
});
|
||||
|
||||
test(scoped`clipboard copy multi selection`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'abc');
|
||||
await pressEnter(page);
|
||||
await type(page, 'def');
|
||||
await setSelection(page, 2, 1, 3, 1);
|
||||
await waitNextFrame(page);
|
||||
await copyByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await focusRichText(page, 1);
|
||||
await pasteByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'cursor');
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['abc', 'defbc', 'dcursor']);
|
||||
});
|
||||
|
||||
test(scoped`clipboard copy nested items`, async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'abc');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'def');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'ghi');
|
||||
await pressEnter(page);
|
||||
await pressShiftTab(page);
|
||||
await pressShiftTab(page);
|
||||
await type(page, 'jkl');
|
||||
await setSelection(page, 2, 1, 3, 1);
|
||||
await waitNextFrame(page);
|
||||
await copyByKeyboard(page);
|
||||
|
||||
const text = await getClipboardText(page);
|
||||
const html = await getClipboardHTML(page);
|
||||
const snapshot = await getClipboardSnapshot(page);
|
||||
expect(text).toMatchSnapshot(`${testInfo.title}-clipboard.md`);
|
||||
expect(JSON.stringify(snapshot.snapshot.content, null, 2)).toMatchSnapshot(
|
||||
`${testInfo.title}-clipboard.json`
|
||||
);
|
||||
expect(html).toMatchSnapshot(`${testInfo.title}-clipboard.html`);
|
||||
|
||||
await setSelection(page, 4, 1, 5, 1);
|
||||
await waitNextFrame(page);
|
||||
await copyByKeyboard(page);
|
||||
|
||||
const text2 = await getClipboardText(page);
|
||||
const html2 = await getClipboardHTML(page);
|
||||
const snapshot2 = await getClipboardSnapshot(page);
|
||||
expect(text2).toMatchSnapshot(`${testInfo.title}-clipboard2.md`);
|
||||
expect(JSON.stringify(snapshot2.snapshot.content, null, 2)).toMatchSnapshot(
|
||||
`${testInfo.title}-clipboard2.json`
|
||||
);
|
||||
expect(html2).toMatchSnapshot(`${testInfo.title}-clipboard2.html`);
|
||||
});
|
||||
58
blocksuite/tests-legacy/clipboard/image.spec.ts
Normal file
58
blocksuite/tests-legacy/clipboard/image.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyParagraphState,
|
||||
pasteContent,
|
||||
pressArrowDown,
|
||||
pressArrowUp,
|
||||
pressEscape,
|
||||
waitEmbedLoaded,
|
||||
} from '../utils/actions/index.js';
|
||||
import { assertRichImage, assertText } from '../utils/asserts.js';
|
||||
import { scoped, test } from '../utils/playwright.js';
|
||||
|
||||
test(
|
||||
scoped`clipboard paste end with image, the cursor should be controlled by up/down keys`,
|
||||
async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/3639',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
// set up clipboard data using html
|
||||
const clipData = {
|
||||
'text/html': `<p>Lorem Ipsum placeholder text.</p>
|
||||
<figure ><img src='https://placehold.co/600x400' /></figure>
|
||||
`,
|
||||
};
|
||||
await page.evaluate(
|
||||
({ clipData }) => {
|
||||
const dT = new DataTransfer();
|
||||
const e = new ClipboardEvent('paste', { clipboardData: dT });
|
||||
Object.defineProperty(e, 'target', {
|
||||
writable: false,
|
||||
value: document,
|
||||
});
|
||||
e.clipboardData?.setData('text/html', clipData['text/html']);
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
{ clipData }
|
||||
);
|
||||
const str = 'Lorem Ipsum placeholder text.';
|
||||
await waitEmbedLoaded(page);
|
||||
await assertRichImage(page, 1);
|
||||
await pressEscape(page);
|
||||
await pressArrowUp(page, 1);
|
||||
await pasteContent(page, clipData);
|
||||
await assertRichImage(page, 2);
|
||||
await assertText(page, str + str);
|
||||
await pressArrowDown(page, 1);
|
||||
await pressEscape(page);
|
||||
await pasteContent(page, clipData);
|
||||
await assertRichImage(page, 3);
|
||||
await assertText(page, 'Lorem Ipsum placeholder text.', 1);
|
||||
}
|
||||
);
|
||||
714
blocksuite/tests-legacy/clipboard/list.spec.ts
Normal file
714
blocksuite/tests-legacy/clipboard/list.spec.ts
Normal file
@@ -0,0 +1,714 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { initDatabaseColumn } from '../database/actions.js';
|
||||
import {
|
||||
activeNoteInEdgeless,
|
||||
changeEdgelessNoteBackground,
|
||||
copyByKeyboard,
|
||||
createShapeElement,
|
||||
cutByKeyboard,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getAllNoteIds,
|
||||
getClipboardHTML,
|
||||
getClipboardSnapshot,
|
||||
getClipboardText,
|
||||
getEdgelessSelectedRectModel,
|
||||
getInlineSelectionIndex,
|
||||
getInlineSelectionText,
|
||||
getPageSnapshot,
|
||||
getRichTextBoundingBox,
|
||||
initDatabaseDynamicRowWithData,
|
||||
initEmptyDatabaseWithParagraphState,
|
||||
initEmptyEdgelessState,
|
||||
initEmptyParagraphState,
|
||||
initThreeParagraphs,
|
||||
pasteByKeyboard,
|
||||
pasteContent,
|
||||
pressArrowLeft,
|
||||
pressArrowRight,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
pressShiftTab,
|
||||
pressSpace,
|
||||
pressTab,
|
||||
selectAllByKeyboard,
|
||||
selectNoteInEdgeless,
|
||||
setInlineRangeInSelectedRichText,
|
||||
SHORT_KEY,
|
||||
switchEditorMode,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
type,
|
||||
undoByKeyboard,
|
||||
waitForInlineEditorStateUpdated,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockTypes,
|
||||
assertEdgelessNoteBackground,
|
||||
assertEdgelessSelectedModelRect,
|
||||
assertExists,
|
||||
assertRichTextModelType,
|
||||
assertRichTexts,
|
||||
assertStoreMatchJSX,
|
||||
assertText,
|
||||
} from '../utils/asserts.js';
|
||||
import { scoped, test } from '../utils/playwright.js';
|
||||
|
||||
test('paste a non-nested list to a non-nested list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const clipData = {
|
||||
'text/plain': `
|
||||
- a
|
||||
`,
|
||||
};
|
||||
await type(page, '-');
|
||||
await pressSpace(page);
|
||||
await type(page, '123');
|
||||
await page.keyboard.press('Control+ArrowLeft');
|
||||
|
||||
// paste on start
|
||||
await waitNextFrame(page);
|
||||
await pasteContent(page, clipData);
|
||||
await pressArrowLeft(page);
|
||||
await assertRichTexts(page, ['a123']);
|
||||
|
||||
// paste in middle
|
||||
await pressArrowRight(page, 2);
|
||||
await pasteContent(page, clipData);
|
||||
await pressArrowRight(page);
|
||||
await assertRichTexts(page, ['a1a23']);
|
||||
|
||||
// paste on end
|
||||
await pressArrowRight(page);
|
||||
await pasteContent(page, clipData);
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['a1a23a']);
|
||||
|
||||
await assertBlockTypes(page, ['bulleted']);
|
||||
});
|
||||
|
||||
test('copy a nested list by clicking button, the clipboard data should be complete', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const clipData = {
|
||||
'text/plain': `
|
||||
- aaa
|
||||
- bbb
|
||||
- ccc
|
||||
`,
|
||||
};
|
||||
await pasteContent(page, clipData);
|
||||
|
||||
const rootListBound = await page.locator('affine-list').first().boundingBox();
|
||||
assertExists(rootListBound);
|
||||
|
||||
// use drag element to test.
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: rootListBound.x + 1, y: rootListBound.y - 1 },
|
||||
{ x: rootListBound.x + 1, y: rootListBound.y + rootListBound.height - 1 }
|
||||
);
|
||||
await copyByKeyboard(page);
|
||||
|
||||
const text = await getClipboardText(page);
|
||||
const html = await getClipboardHTML(page);
|
||||
const snapshot = await getClipboardSnapshot(page);
|
||||
expect(text).toMatchSnapshot(`${testInfo.title}-clipboard.md`);
|
||||
expect(JSON.stringify(snapshot.snapshot.content, null, 2)).toMatchSnapshot(
|
||||
`${testInfo.title}-clipboard.json`
|
||||
);
|
||||
expect(html).toMatchSnapshot(`${testInfo.title}-clipboard.html`);
|
||||
});
|
||||
|
||||
test('paste a nested list to a nested list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const clipData = {
|
||||
'text/plain': `
|
||||
- aaa
|
||||
- bbb
|
||||
- ccc
|
||||
`,
|
||||
};
|
||||
await pasteContent(page, clipData);
|
||||
await focusRichText(page, 1);
|
||||
|
||||
// paste on start
|
||||
await page.keyboard.press('Control+ArrowLeft');
|
||||
|
||||
/**
|
||||
* - aaa
|
||||
* - |bbb
|
||||
* - ccc
|
||||
*/
|
||||
await pasteContent(page, clipData);
|
||||
/**
|
||||
* - aaa
|
||||
* - aaa
|
||||
* - bbb
|
||||
* - ccc|bbb
|
||||
* -ccc
|
||||
*/
|
||||
|
||||
await assertRichTexts(page, ['aaa', 'aaa', 'bbb', 'cccbbb', 'ccc']);
|
||||
expect(await getInlineSelectionText(page)).toEqual('cccbbb');
|
||||
expect(await getInlineSelectionIndex(page)).toEqual(3);
|
||||
|
||||
// paste in middle
|
||||
await undoByKeyboard(page);
|
||||
await pressArrowRight(page);
|
||||
|
||||
/**
|
||||
* - aaa
|
||||
* - b|bb
|
||||
* - ccc
|
||||
*/
|
||||
await pasteContent(page, clipData);
|
||||
/**
|
||||
* - aaa
|
||||
* - baaa
|
||||
* - bbb
|
||||
* - ccc|bb
|
||||
* - ccc
|
||||
*/
|
||||
|
||||
await assertRichTexts(page, ['aaa', 'baaa', 'bbb', 'cccbb', 'ccc']);
|
||||
expect(await getInlineSelectionText(page)).toEqual('cccbb');
|
||||
expect(await getInlineSelectionIndex(page)).toEqual(3);
|
||||
|
||||
// paste on end
|
||||
await undoByKeyboard(page);
|
||||
await page.keyboard.press('Control+ArrowRight');
|
||||
|
||||
/**
|
||||
* - aaa
|
||||
* - bbb|
|
||||
* - ccc
|
||||
*/
|
||||
await pasteContent(page, clipData);
|
||||
/**
|
||||
* - aaa
|
||||
* - bbbaaa
|
||||
* - bbb
|
||||
* - ccc|
|
||||
* - ccc
|
||||
*/
|
||||
|
||||
await assertRichTexts(page, ['aaa', 'bbbaaa', 'bbb', 'ccc', 'ccc']);
|
||||
expect(await getInlineSelectionText(page)).toEqual('ccc');
|
||||
expect(await getInlineSelectionIndex(page)).toEqual(3);
|
||||
});
|
||||
|
||||
test('paste nested lists to a nested list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const clipData = {
|
||||
'text/plain': `
|
||||
- aaa
|
||||
- bbb
|
||||
- ccc
|
||||
`,
|
||||
};
|
||||
await pasteContent(page, clipData);
|
||||
await focusRichText(page, 1);
|
||||
|
||||
const clipData2 = {
|
||||
'text/plain': `
|
||||
- 111
|
||||
- 222
|
||||
- 111
|
||||
- 222
|
||||
`,
|
||||
};
|
||||
|
||||
// paste on start
|
||||
await page.keyboard.press('Control+ArrowLeft');
|
||||
|
||||
/**
|
||||
* - aaa
|
||||
* - |bbb
|
||||
* - ccc
|
||||
*/
|
||||
await pasteContent(page, clipData2);
|
||||
/**
|
||||
* - aaa
|
||||
* - 111
|
||||
* - 222
|
||||
* - 111
|
||||
* - 222|bbb
|
||||
* - ccc
|
||||
*/
|
||||
|
||||
await assertRichTexts(page, ['aaa', '111', '222', '111', '222bbb', 'ccc']);
|
||||
expect(await getInlineSelectionText(page)).toEqual('222bbb');
|
||||
expect(await getInlineSelectionIndex(page)).toEqual(3);
|
||||
|
||||
// paste in middle
|
||||
await undoByKeyboard(page);
|
||||
await pressArrowRight(page);
|
||||
|
||||
/**
|
||||
* - aaa
|
||||
* - b|bb
|
||||
* - ccc
|
||||
*/
|
||||
await pasteContent(page, clipData2);
|
||||
/**
|
||||
* - aaa
|
||||
* - b111
|
||||
* - 222
|
||||
* - 111
|
||||
* - 222|bb
|
||||
* - ccc
|
||||
*/
|
||||
|
||||
await assertRichTexts(page, ['aaa', 'b111', '222', '111', '222bb', 'ccc']);
|
||||
expect(await getInlineSelectionText(page)).toEqual('222bb');
|
||||
expect(await getInlineSelectionIndex(page)).toEqual(3);
|
||||
|
||||
// paste on end
|
||||
await undoByKeyboard(page);
|
||||
await page.keyboard.press('Control+ArrowRight');
|
||||
|
||||
/**
|
||||
* - aaa
|
||||
* - bbb|
|
||||
* - ccc
|
||||
*/
|
||||
await pasteContent(page, clipData2);
|
||||
/**
|
||||
* - aaa
|
||||
* - bbb111
|
||||
* - 222
|
||||
* - 111
|
||||
* - 222|
|
||||
* - ccc
|
||||
*/
|
||||
|
||||
await assertRichTexts(page, ['aaa', 'bbb111', '222', '111', '222', 'ccc']);
|
||||
expect(await getInlineSelectionText(page)).toEqual('222');
|
||||
expect(await getInlineSelectionIndex(page)).toEqual(3);
|
||||
});
|
||||
|
||||
test('paste non-nested lists to a nested list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const clipData = {
|
||||
'text/plain': `
|
||||
- aaa
|
||||
- bbb
|
||||
`,
|
||||
};
|
||||
await pasteContent(page, clipData);
|
||||
await focusRichText(page, 0);
|
||||
|
||||
const clipData2 = {
|
||||
'text/plain': `
|
||||
- 123
|
||||
- 456
|
||||
`,
|
||||
};
|
||||
|
||||
// paste on start
|
||||
await page.keyboard.press('Control+ArrowLeft');
|
||||
|
||||
/**
|
||||
* - |aaa
|
||||
* - bbb
|
||||
*/
|
||||
await pasteContent(page, clipData2);
|
||||
/**
|
||||
* - 123
|
||||
* - 456|aaa
|
||||
* - bbb
|
||||
*/
|
||||
|
||||
await assertRichTexts(page, ['123', '456aaa', 'bbb']);
|
||||
expect(await getInlineSelectionText(page)).toEqual('456aaa');
|
||||
expect(await getInlineSelectionIndex(page)).toEqual(3);
|
||||
});
|
||||
|
||||
test(scoped`cut should work for multi-block selection`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'a');
|
||||
await pressEnter(page);
|
||||
await type(page, 'b');
|
||||
await pressEnter(page);
|
||||
await type(page, 'c');
|
||||
await selectAllByKeyboard(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await cutByKeyboard(page);
|
||||
await page.locator('.affine-page-viewport').click();
|
||||
await waitNextFrame(page);
|
||||
await assertText(page, '');
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`pasting into empty list should not convert the list into paragraph`,
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'test');
|
||||
await setInlineRangeInSelectedRichText(page, 0, 4);
|
||||
await copyByKeyboard(page);
|
||||
await type(page, '- ');
|
||||
await page.keyboard.press(`${SHORT_KEY}+v`);
|
||||
await assertRichTexts(page, ['test']);
|
||||
await assertRichTextModelType(page, 'bulleted');
|
||||
}
|
||||
);
|
||||
|
||||
test('cut will delete all content, and copy will reappear content', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '-');
|
||||
await pressSpace(page);
|
||||
await type(page, '1');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, '2');
|
||||
await pressEnter(page);
|
||||
await type(page, '3');
|
||||
await pressEnter(page);
|
||||
await pressShiftTab(page);
|
||||
await type(page, '4');
|
||||
|
||||
const box123 = await getRichTextBoundingBox(page, '1');
|
||||
const inside123 = { x: box123.left + 1, y: box123.top + 1 };
|
||||
|
||||
const box789 = await getRichTextBoundingBox(page, '6');
|
||||
const inside789 = { x: box789.right - 1, y: box789.bottom - 1 };
|
||||
// from top to bottom
|
||||
await dragBetweenCoords(page, inside123, inside789);
|
||||
|
||||
await cutByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_after-cut.json`
|
||||
);
|
||||
await waitNextFrame(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await pasteByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_after-paste.json`
|
||||
);
|
||||
});
|
||||
|
||||
test(scoped`should copy and paste of database work`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseWithParagraphState(page);
|
||||
|
||||
// init database columns and rows
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, 'abc', true);
|
||||
await pressEscape(page);
|
||||
await focusRichText(page, 1);
|
||||
await selectAllByKeyboard(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
await pressEnter(page);
|
||||
await pasteByKeyboard(page);
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
/*xml*/ `
|
||||
<affine:page>
|
||||
<affine:note
|
||||
prop:background="--affine-note-background-white"
|
||||
prop:displayMode="both"
|
||||
prop:edgeless={
|
||||
Object {
|
||||
"style": Object {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "none",
|
||||
"shadowType": "--affine-note-shadow-box",
|
||||
},
|
||||
}
|
||||
}
|
||||
prop:hidden={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
>
|
||||
<affine:database
|
||||
prop:columns="Array [2]"
|
||||
prop:title="Database 1"
|
||||
prop:views="Array [1]"
|
||||
>
|
||||
<affine:paragraph
|
||||
prop:collapsed={false}
|
||||
prop:type="text"
|
||||
/>
|
||||
</affine:database>
|
||||
<affine:database
|
||||
prop:columns="Array [2]"
|
||||
prop:title="Database 1"
|
||||
prop:views="Array [1]"
|
||||
>
|
||||
<affine:paragraph
|
||||
prop:collapsed={false}
|
||||
prop:type="text"
|
||||
/>
|
||||
</affine:database>
|
||||
<affine:paragraph
|
||||
prop:collapsed={false}
|
||||
prop:type="text"
|
||||
/>
|
||||
</affine:note>
|
||||
</affine:page>`
|
||||
);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
/*xml*/ `
|
||||
<affine:page>
|
||||
<affine:note
|
||||
prop:background="--affine-note-background-white"
|
||||
prop:displayMode="both"
|
||||
prop:edgeless={
|
||||
Object {
|
||||
"style": Object {
|
||||
"borderRadius": 8,
|
||||
"borderSize": 4,
|
||||
"borderStyle": "none",
|
||||
"shadowType": "--affine-note-shadow-box",
|
||||
},
|
||||
}
|
||||
}
|
||||
prop:hidden={false}
|
||||
prop:index="a0"
|
||||
prop:lockedBySelf={false}
|
||||
>
|
||||
<affine:database
|
||||
prop:columns="Array [2]"
|
||||
prop:title="Database 1"
|
||||
prop:views="Array [1]"
|
||||
>
|
||||
<affine:paragraph
|
||||
prop:collapsed={false}
|
||||
prop:type="text"
|
||||
/>
|
||||
</affine:database>
|
||||
<affine:paragraph
|
||||
prop:collapsed={false}
|
||||
prop:type="text"
|
||||
/>
|
||||
</affine:note>
|
||||
</affine:page>`
|
||||
);
|
||||
});
|
||||
|
||||
test(`copy canvas element and text note in edgeless mode`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await initThreeParagraphs(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100]);
|
||||
await selectAllByKeyboard(page);
|
||||
const bound = await getEdgelessSelectedRectModel(page);
|
||||
await copyByKeyboard(page);
|
||||
const coord = await toViewCoord(page, [
|
||||
bound[0] + bound[2] / 2,
|
||||
bound[1] + bound[3] / 2 + 200,
|
||||
]);
|
||||
await page.mouse.move(coord[0], coord[1]);
|
||||
await page.waitForTimeout(300);
|
||||
await pasteByKeyboard(page, false);
|
||||
bound[1] = bound[1] + 200;
|
||||
await assertEdgelessSelectedModelRect(page, bound);
|
||||
});
|
||||
|
||||
test(scoped`copy when text note active in edgeless`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const ids = await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '1234');
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
await activeNoteInEdgeless(page, ids.noteId);
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
await setInlineRangeInSelectedRichText(page, 0, 4);
|
||||
await copyByKeyboard(page);
|
||||
await pressArrowRight(page);
|
||||
await type(page, '555');
|
||||
await pasteByKeyboard(page, false);
|
||||
await assertText(page, '12345551234');
|
||||
});
|
||||
|
||||
test(scoped`paste note block with background`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const ids = await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '1234');
|
||||
|
||||
await switchEditorMode(page);
|
||||
await selectNoteInEdgeless(page, ids.noteId);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeNoteColor');
|
||||
const color = '--affine-note-background-grey';
|
||||
await changeEdgelessNoteBackground(page, color);
|
||||
await assertEdgelessNoteBackground(page, ids.noteId, color);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
|
||||
await page.mouse.move(0, 0);
|
||||
await pasteByKeyboard(page, false);
|
||||
const noteIds = await getAllNoteIds(page);
|
||||
for (const noteId of noteIds) {
|
||||
await assertEdgelessNoteBackground(page, noteId, color);
|
||||
}
|
||||
});
|
||||
|
||||
test(scoped`copy and paste to selection block selection`, async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2265',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '1234');
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
await pressArrowRight(page);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['12341234']);
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`should keep paragraph block's type when pasting at the start of empty paragraph block except type text`,
|
||||
async ({ page }, testInfo) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2336',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '>');
|
||||
await page.keyboard.press('Space', { delay: 50 });
|
||||
|
||||
await page.evaluate(() => {
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('id', 'input-test');
|
||||
input.value = '123';
|
||||
document.body.querySelector('#app')?.append(input);
|
||||
});
|
||||
await page.focus('#input-test');
|
||||
await page.dblclick('#input-test');
|
||||
await copyByKeyboard(page);
|
||||
await focusRichText(page);
|
||||
await pasteByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_after-paste-1.json`
|
||||
);
|
||||
|
||||
await pressEnter(page);
|
||||
await waitNextFrame(page);
|
||||
await pressEnter(page);
|
||||
await waitNextFrame(page);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_after-paste-2.json`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test(scoped`paste from FeiShu list format`, async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2438',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
// set up clipboard data using html
|
||||
const clipData = {
|
||||
'text/html': `<div><li><div><span>aaaa</span></div></li></div>`,
|
||||
};
|
||||
await waitNextFrame(page);
|
||||
await page.evaluate(
|
||||
({ clipData }) => {
|
||||
const dT = new DataTransfer();
|
||||
const e = new ClipboardEvent('paste', { clipboardData: dT });
|
||||
Object.defineProperty(e, 'target', {
|
||||
writable: false,
|
||||
value: document,
|
||||
});
|
||||
e.clipboardData?.setData('text/html', clipData['text/html']);
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
{ clipData }
|
||||
);
|
||||
await assertText(page, 'aaaa');
|
||||
await assertBlockTypes(page, ['bulleted']);
|
||||
});
|
||||
|
||||
test(scoped`paste in list format`, async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2281',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, '- test');
|
||||
await focusRichText(page);
|
||||
|
||||
const clipData = {
|
||||
'text/html': `<ul><li>111<ul><li>222</li></ul></li></ul>`,
|
||||
};
|
||||
await waitNextFrame(page);
|
||||
await page.evaluate(
|
||||
({ clipData }) => {
|
||||
const dT = new DataTransfer();
|
||||
const e = new ClipboardEvent('paste', { clipboardData: dT });
|
||||
Object.defineProperty(e, 'target', {
|
||||
writable: false,
|
||||
value: document,
|
||||
});
|
||||
e.clipboardData?.setData('text/html', clipData['text/html']);
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
{ clipData }
|
||||
);
|
||||
await assertRichTexts(page, ['test111', '222']);
|
||||
});
|
||||
170
blocksuite/tests-legacy/clipboard/markdown.spec.ts
Normal file
170
blocksuite/tests-legacy/clipboard/markdown.spec.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyParagraphState,
|
||||
pasteContent,
|
||||
resetHistory,
|
||||
undoByClick,
|
||||
waitEmbedLoaded,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockTypes,
|
||||
assertRichImage,
|
||||
assertRichTexts,
|
||||
assertTextFormats,
|
||||
} from '../utils/asserts.js';
|
||||
import { scoped, test } from '../utils/playwright.js';
|
||||
|
||||
test(scoped`markdown format parse`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await resetHistory(page);
|
||||
|
||||
let clipData = {
|
||||
'text/plain': `# h1
|
||||
|
||||
## h2
|
||||
|
||||
### h3
|
||||
|
||||
#### h4
|
||||
|
||||
##### h5
|
||||
|
||||
###### h6
|
||||
|
||||
- [ ] todo
|
||||
|
||||
- [ ] todo
|
||||
|
||||
- [x] todo
|
||||
|
||||
* bulleted
|
||||
|
||||
- bulleted
|
||||
|
||||
1. numbered
|
||||
|
||||
> quote
|
||||
`,
|
||||
};
|
||||
await waitNextFrame(page);
|
||||
await pasteContent(page, clipData);
|
||||
await page.waitForTimeout(200);
|
||||
await assertBlockTypes(page, [
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'todo',
|
||||
'todo',
|
||||
'todo',
|
||||
'bulleted',
|
||||
'bulleted',
|
||||
'numbered',
|
||||
'quote',
|
||||
]);
|
||||
await assertRichTexts(page, [
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'todo',
|
||||
'todo',
|
||||
'todo',
|
||||
'bulleted',
|
||||
'bulleted',
|
||||
'numbered',
|
||||
'quote',
|
||||
]);
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
await focusRichText(page);
|
||||
|
||||
clipData = {
|
||||
'text/plain': `# ***bolditalic***
|
||||
# **bold**
|
||||
|
||||
*italic*
|
||||
|
||||
~~strikethrough~~
|
||||
|
||||
[link](linktest)
|
||||
|
||||
\`code\`
|
||||
`,
|
||||
};
|
||||
await waitNextFrame(page);
|
||||
await pasteContent(page, clipData);
|
||||
await page.waitForTimeout(200);
|
||||
await assertTextFormats(page, [
|
||||
{ bold: true, italic: true },
|
||||
{ bold: true },
|
||||
{ italic: true },
|
||||
{ strike: true },
|
||||
{ link: 'linktest' },
|
||||
{ code: true },
|
||||
]);
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test(scoped`import markdown`, async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await resetHistory(page);
|
||||
const clipData = `# text
|
||||
# h1
|
||||
`;
|
||||
await pasteContent(page, { 'text/plain': clipData });
|
||||
await page.waitForTimeout(100);
|
||||
await assertRichTexts(page, ['text', 'h1']);
|
||||
await undoByClick(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test(
|
||||
scoped`clipboard paste HTML containing markdown syntax code and image `,
|
||||
async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2855',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
// set up clipboard data using html
|
||||
const clipData = {
|
||||
'text/html': `<p>符合 Markdown 格式的 URL 放到笔记中,此时需要的格式如下:</p>
|
||||
<pre><code>md [任务管理这件事 - 少数派](https://sspai.com/post/61092)</code></pre>
|
||||
<p>(将一段文字包裹在<code >[[]]</code>中)此时需要的格式如下:</p>
|
||||
<figure ><img src="https://placehold.co/600x400"></figure>
|
||||
<p>上图中,当我们处在 Obsidian 的「预览模式」时,点击这个「双向链接」</p>
|
||||
`,
|
||||
};
|
||||
await page.evaluate(
|
||||
({ clipData }) => {
|
||||
const dT = new DataTransfer();
|
||||
const e = new ClipboardEvent('paste', { clipboardData: dT });
|
||||
Object.defineProperty(e, 'target', {
|
||||
writable: false,
|
||||
value: document,
|
||||
});
|
||||
e.clipboardData?.setData('text/html', clipData['text/html']);
|
||||
document.dispatchEvent(e);
|
||||
},
|
||||
{ clipData }
|
||||
);
|
||||
await waitEmbedLoaded(page);
|
||||
// await page.waitForTimeout(500);
|
||||
await assertRichImage(page, 1);
|
||||
}
|
||||
);
|
||||
149
blocksuite/tests-legacy/code/copy-paste.spec.ts
Normal file
149
blocksuite/tests-legacy/code/copy-paste.spec.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
copyByKeyboard,
|
||||
pasteByKeyboard,
|
||||
pressArrowLeft,
|
||||
pressEnter,
|
||||
pressEnterWithShortkey,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getInlineSelectionText,
|
||||
getPageSnapshot,
|
||||
initEmptyCodeBlockState,
|
||||
setSelection,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { assertRichTextInlineRange } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import { getCodeBlock } from './utils.js';
|
||||
|
||||
test('keyboard selection and copy paste', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'use');
|
||||
await page.keyboard.down('Shift');
|
||||
await pressArrowLeft(page, 'use'.length);
|
||||
await page.keyboard.up('Shift');
|
||||
await copyByKeyboard(page);
|
||||
await pressArrowLeft(page, 1);
|
||||
await pasteByKeyboard(page);
|
||||
|
||||
const content = await getInlineSelectionText(page);
|
||||
expect(content).toBe('useuse');
|
||||
|
||||
await assertRichTextInlineRange(page, 0, 3, 0);
|
||||
});
|
||||
|
||||
test('paste with more than one continuous breakline should remain in code block, ', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.setContent(`<div contenteditable>use super::*;
|
||||
use fern::{
|
||||
colors::{Color, ColoredLevelConfig},
|
||||
Dispatch,
|
||||
};
|
||||
<br><br>
|
||||
#[inline]</div>`);
|
||||
await page.focus('div');
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
await pasteByKeyboard(page);
|
||||
|
||||
const locator = page.locator('affine-paragraph');
|
||||
await expect(locator).toBeHidden();
|
||||
});
|
||||
|
||||
test('drag copy paste', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'use');
|
||||
|
||||
await setSelection(page, 2, 0, 2, 3);
|
||||
await copyByKeyboard(page);
|
||||
await pressArrowLeft(page);
|
||||
await pasteByKeyboard(page);
|
||||
|
||||
const content = await getInlineSelectionText(page);
|
||||
expect(content).toBe('useuse');
|
||||
|
||||
await assertRichTextInlineRange(page, 0, 3, 0);
|
||||
});
|
||||
|
||||
test.skip('use keyboard copy inside code block copy', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'use');
|
||||
await page.keyboard.down('Shift');
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
||||
for (let i = 0; i < 'use'.length; i++) {
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
}
|
||||
await page.keyboard.up('Shift');
|
||||
await copyByKeyboard(page);
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await pressEnter(page);
|
||||
await pressEnter(page);
|
||||
await pasteByKeyboard(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_pasted.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('code block has content, click code block copy menu, copy whole code block', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page, { language: 'javascript' });
|
||||
await focusRichText(page);
|
||||
await page.keyboard.type('use');
|
||||
await pressEnterWithShortkey(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
|
||||
await expect(codeBlockController.copyButton).toBeVisible();
|
||||
await codeBlockController.copyButton.click();
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await pasteByKeyboard(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_pasted.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('code block is empty, click code block copy menu, copy the empty code block', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page, { language: 'javascript' });
|
||||
await focusRichText(page);
|
||||
|
||||
await pressEnterWithShortkey(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await expect(codeBlockController.copyButton).toBeVisible();
|
||||
await codeBlockController.copyButton.click();
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await pasteByKeyboard(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_pasted.json`
|
||||
);
|
||||
});
|
||||
668
blocksuite/tests-legacy/code/crud.spec.ts
Normal file
668
blocksuite/tests-legacy/code/crud.spec.ts
Normal file
@@ -0,0 +1,668 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { dragBetweenIndices } from 'utils/actions/drag.js';
|
||||
import { getFormatBar } from 'utils/query.js';
|
||||
|
||||
import { updateBlockType } from '../utils/actions/block.js';
|
||||
import {
|
||||
createCodeBlock,
|
||||
pressArrowLeft,
|
||||
pressArrowUp,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
pressShiftTab,
|
||||
pressTab,
|
||||
redoByKeyboard,
|
||||
type,
|
||||
undoByKeyboard,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
focusRichTextEnd,
|
||||
getPageSnapshot,
|
||||
initEmptyCodeBlockState,
|
||||
initEmptyParagraphState,
|
||||
setSelection,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/misc.js';
|
||||
import {
|
||||
assertBlockCount,
|
||||
assertRichTexts,
|
||||
assertStoreMatchJSX,
|
||||
assertTitle,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import { getCodeBlock } from './utils.js';
|
||||
|
||||
test('use debug menu can create code block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await updateBlockType(page, 'affine:code');
|
||||
|
||||
const locator = page.locator('affine-code');
|
||||
await expect(locator).toBeVisible();
|
||||
});
|
||||
|
||||
test('use markdown syntax can create code block', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'aaa');
|
||||
await pressEnter(page);
|
||||
await type(page, 'bbb');
|
||||
await pressTab(page);
|
||||
await pressEnter(page);
|
||||
await type(page, 'ccc');
|
||||
await pressTab(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await setSelection(page, 2, 0, 2, 0);
|
||||
// |aaa
|
||||
// bbb
|
||||
// ccc
|
||||
|
||||
await type(page, '``` ');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_markdown_syntax.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('use markdown syntax with trailing characters can create code block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '```JavaScript');
|
||||
await type(page, ' ');
|
||||
|
||||
const locator = page.locator('affine-code');
|
||||
await expect(locator).toBeVisible();
|
||||
});
|
||||
|
||||
test('support ```[lang] to add code block with language', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/1314',
|
||||
});
|
||||
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '```ts');
|
||||
await type(page, ' ');
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
const codeLocator = codeBlockController.codeBlock;
|
||||
await expect(codeLocator).toBeVisible();
|
||||
|
||||
const codeRect = await codeLocator.boundingBox();
|
||||
if (!codeRect) {
|
||||
throw new Error('Failed to get bounding box of code block.');
|
||||
}
|
||||
const position = {
|
||||
x: codeRect.x + codeRect.width / 2,
|
||||
y: codeRect.y + codeRect.height / 2,
|
||||
};
|
||||
await page.mouse.move(position.x, position.y);
|
||||
|
||||
const languageButton = codeBlockController.languageButton;
|
||||
await expect(languageButton).toBeVisible();
|
||||
await expect(languageButton).toHaveText('TypeScript');
|
||||
});
|
||||
|
||||
test('use more than three backticks can not create code block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '`````');
|
||||
await type(page, ' ');
|
||||
|
||||
const codeBlockLocator = page.locator('affine-code');
|
||||
await expect(codeBlockLocator).toBeHidden();
|
||||
const inlineCodelocator = page.getByText('```');
|
||||
await expect(inlineCodelocator).toBeVisible();
|
||||
expect(await inlineCodelocator.count()).toEqual(1);
|
||||
});
|
||||
|
||||
test('use shortcut can create code block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await createCodeBlock(page);
|
||||
|
||||
const locator = page.locator('affine-code');
|
||||
await expect(locator).toBeVisible();
|
||||
});
|
||||
|
||||
test('change code language can work', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { codeBlockId } = await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await codeBlockController.clickLanguageButton();
|
||||
const locator = codeBlockController.langList;
|
||||
await expect(locator).toBeVisible();
|
||||
|
||||
await type(page, 'rust');
|
||||
await page.click(
|
||||
'.affine-filterable-list > .items-container > icon-button:nth-child(1)'
|
||||
);
|
||||
await expect(locator).toBeHidden();
|
||||
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await expect(codeBlockController.languageButton).toHaveText('Rust');
|
||||
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
/*xml*/ `
|
||||
<affine:code
|
||||
prop:caption=""
|
||||
prop:language="rust"
|
||||
prop:wrap={false}
|
||||
/>`,
|
||||
codeBlockId
|
||||
);
|
||||
await undoByKeyboard(page);
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
/*xml*/ `
|
||||
<affine:code
|
||||
prop:caption=""
|
||||
prop:language={null}
|
||||
prop:wrap={false}
|
||||
/>`,
|
||||
codeBlockId
|
||||
);
|
||||
|
||||
// Can switch to another language
|
||||
await codeBlockController.clickLanguageButton();
|
||||
await type(page, 'ty');
|
||||
await pressEnter(page);
|
||||
await expect(locator).toBeHidden();
|
||||
await expect(codeBlockController.languageButton).toHaveText('TypeScript');
|
||||
});
|
||||
|
||||
test('duplicate code block', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page, { language: 'javascript' });
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
|
||||
// change language
|
||||
await codeBlockController.clickLanguageButton();
|
||||
const langLocator = codeBlockController.langList;
|
||||
await expect(langLocator).toBeVisible();
|
||||
await type(page, 'rust');
|
||||
await page.click(
|
||||
'.affine-filterable-list > .items-container > icon-button:nth-child(1)'
|
||||
);
|
||||
|
||||
// add text
|
||||
await focusRichTextEnd(page);
|
||||
await type(page, 'let a: u8 = 7');
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
// add a caption
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await codeBlockController.captionButton.click();
|
||||
await type(page, 'BlockSuite');
|
||||
await pressEnter(page);
|
||||
await pressBackspace(page); // remove paragraph
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
// turn on wrap
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await (await codeBlockController.openMore()).wrapButton.click();
|
||||
|
||||
// duplicate
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await (await codeBlockController.openMore()).duplicateButton.click();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('delete code block in more menu', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page, { language: 'javascript' });
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
const moreMenu = await codeBlockController.openMore();
|
||||
|
||||
await expect(moreMenu.menu).toBeVisible();
|
||||
await moreMenu.deleteButton.click();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_final.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('undo and redo works in code block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'const a = 10;');
|
||||
await assertRichTexts(page, ['const a = 10;']);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['']);
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await assertRichTexts(page, ['const a = 10;']);
|
||||
});
|
||||
|
||||
test('toggle code block wrap can work', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { codeBlockId } = await initEmptyCodeBlockState(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
/*xml*/ `
|
||||
<affine:code
|
||||
prop:caption=""
|
||||
prop:language={null}
|
||||
prop:wrap={false}
|
||||
/>`,
|
||||
codeBlockId
|
||||
);
|
||||
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await (await codeBlockController.openMore()).wrapButton.click();
|
||||
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
/*xml*/ `
|
||||
<affine:code
|
||||
prop:caption=""
|
||||
prop:language={null}
|
||||
prop:wrap={true}
|
||||
/>`,
|
||||
codeBlockId
|
||||
);
|
||||
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await (await codeBlockController.openMore()).cancelWrapButton.click();
|
||||
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
/*xml*/ `
|
||||
<affine:code
|
||||
prop:caption=""
|
||||
prop:language={null}
|
||||
prop:wrap={false}
|
||||
/>`,
|
||||
codeBlockId
|
||||
);
|
||||
});
|
||||
|
||||
test('add caption works', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { codeBlockId } = await initEmptyCodeBlockState(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await codeBlockController.captionButton.click();
|
||||
await type(page, 'BlockSuite');
|
||||
await pressEnter(page);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
/*xml*/ `
|
||||
<affine:code
|
||||
prop:caption="BlockSuite"
|
||||
prop:language={null}
|
||||
prop:wrap={false}
|
||||
/>`,
|
||||
codeBlockId
|
||||
);
|
||||
});
|
||||
|
||||
test('undo code block wrap can work', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { codeBlockId } = await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
/*xml*/ `
|
||||
<affine:code
|
||||
prop:caption=""
|
||||
prop:language={null}
|
||||
prop:wrap={false}
|
||||
/>`,
|
||||
codeBlockId
|
||||
);
|
||||
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await (await codeBlockController.openMore()).wrapButton.click();
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
/*xml*/ `
|
||||
<affine:code
|
||||
prop:caption=""
|
||||
prop:language={null}
|
||||
prop:wrap={true}
|
||||
/>`,
|
||||
codeBlockId
|
||||
);
|
||||
|
||||
await focusRichText(page);
|
||||
await undoByKeyboard(page);
|
||||
await assertStoreMatchJSX(
|
||||
page,
|
||||
/*xml*/ `
|
||||
<affine:code
|
||||
prop:caption=""
|
||||
prop:language={null}
|
||||
prop:wrap={false}
|
||||
/>`,
|
||||
codeBlockId
|
||||
);
|
||||
});
|
||||
|
||||
test('code block toolbar widget can appear and disappear during mousemove', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const position = await page.locator('affine-code').boundingBox();
|
||||
if (!position) throw new Error('Failed to get affine code position');
|
||||
await page.mouse.move(position.x, position.y);
|
||||
|
||||
const locator = page.locator('.code-toolbar-container');
|
||||
const toolbarPosition = await locator.boundingBox();
|
||||
if (!toolbarPosition) throw new Error('Failed to get option position');
|
||||
await page.mouse.move(toolbarPosition.x, toolbarPosition.y);
|
||||
await expect(locator).toBeVisible();
|
||||
await page.mouse.move(position.x - 10, position.y - 10);
|
||||
await expect(locator).toBeHidden();
|
||||
});
|
||||
|
||||
test('should tab works in code block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'const a = 10;');
|
||||
await assertRichTexts(page, ['const a = 10;']);
|
||||
await page.keyboard.press('Tab', { delay: 50 });
|
||||
await assertRichTexts(page, [' const a = 10;']);
|
||||
await page.keyboard.press(`Shift+Tab`, { delay: 50 });
|
||||
await assertRichTexts(page, ['const a = 10;']);
|
||||
|
||||
await page.keyboard.press('Enter', { delay: 50 });
|
||||
await type(page, 'const b = "NothingToSay');
|
||||
await page.keyboard.press('ArrowUp', { delay: 50 });
|
||||
await page.keyboard.press('Enter', { delay: 50 });
|
||||
await page.keyboard.press('Tab', { delay: 50 });
|
||||
await assertRichTexts(page, ['const a = 10;\n \nconst b = "NothingToSay"']);
|
||||
});
|
||||
|
||||
test('should open more menu and close on selecting', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await expect(codeBlockController.codeToolbar).toBeVisible();
|
||||
const moreMenu = await codeBlockController.openMore();
|
||||
|
||||
await expect(moreMenu.menu).toBeVisible();
|
||||
await moreMenu.wrapButton.click();
|
||||
await expect(moreMenu.menu).toBeHidden();
|
||||
});
|
||||
|
||||
test('should code block lang input supports alias', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
const codeBlock = codeBlockController.codeBlock;
|
||||
await codeBlock.hover();
|
||||
await codeBlockController.clickLanguageButton();
|
||||
await expect(codeBlockController.langList).toBeVisible();
|
||||
await type(page, '文言');
|
||||
await pressEnter(page);
|
||||
await expect(codeBlockController.languageButton).toHaveText('Wenyan');
|
||||
});
|
||||
|
||||
test('multi-line indent', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'aaa');
|
||||
await pressEnter(page);
|
||||
|
||||
await type(page, 'bbb');
|
||||
await pressEnter(page);
|
||||
|
||||
await type(page, 'ccc');
|
||||
|
||||
await page.keyboard.down('Shift');
|
||||
await pressArrowUp(page, 2);
|
||||
await page.keyboard.up('Shift');
|
||||
|
||||
await pressTab(page);
|
||||
|
||||
await assertRichTexts(page, [' aaa\n bbb\n ccc']);
|
||||
|
||||
await pressShiftTab(page);
|
||||
|
||||
await assertRichTexts(page, ['aaa\nbbb\nccc']);
|
||||
|
||||
await pressShiftTab(page);
|
||||
|
||||
await assertRichTexts(page, ['aaa\nbbb\nccc']);
|
||||
});
|
||||
|
||||
test('should bracket complete works in code block', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/1800',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'const a = "');
|
||||
await assertRichTexts(page, ['const a = ""']);
|
||||
|
||||
await type(page, 'str');
|
||||
await assertRichTexts(page, ['const a = "str"']);
|
||||
await type(page, '(');
|
||||
await assertRichTexts(page, ['const a = "str()"']);
|
||||
await type(page, ']');
|
||||
await assertRichTexts(page, ['const a = "str(])"']);
|
||||
});
|
||||
|
||||
test('auto scroll horizontally when typing', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '``` ');
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
await type(page, String(i));
|
||||
}
|
||||
|
||||
const richTextScrollLeft1 = await page.evaluate(() => {
|
||||
const richText = document.querySelector('affine-code rich-text');
|
||||
if (!richText) {
|
||||
throw new Error('Failed to get rich text');
|
||||
}
|
||||
|
||||
return richText.scrollLeft;
|
||||
});
|
||||
expect(richTextScrollLeft1).toBeGreaterThan(200);
|
||||
|
||||
await pressArrowLeft(page, 5);
|
||||
await type(page, 'aa');
|
||||
|
||||
const richTextScrollLeft2 = await page.evaluate(() => {
|
||||
const richText = document.querySelector('affine-code rich-text');
|
||||
if (!richText) {
|
||||
throw new Error('Failed to get rich text');
|
||||
}
|
||||
|
||||
return richText.scrollLeft;
|
||||
});
|
||||
|
||||
expect(richTextScrollLeft2).toEqual(richTextScrollLeft1);
|
||||
});
|
||||
|
||||
test('code hotkey should not effect in global', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await pressEnter(page);
|
||||
await type(page, '``` ');
|
||||
|
||||
await assertTitle(page, '');
|
||||
await assertBlockCount(page, 'paragraph', 1);
|
||||
await assertBlockCount(page, 'code', 1);
|
||||
|
||||
await pressArrowUp(page);
|
||||
await pressBackspace(page);
|
||||
await type(page, 'aaa');
|
||||
|
||||
await assertTitle(page, 'aaa');
|
||||
await assertBlockCount(page, 'paragraph', 0);
|
||||
await assertBlockCount(page, 'code', 1);
|
||||
});
|
||||
|
||||
test('language selection list should not close when hovering out of code block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page, { language: 'javascript' });
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
|
||||
await codeBlockController.clickLanguageButton();
|
||||
const langLocator = codeBlockController.langList;
|
||||
await expect(langLocator).toBeVisible();
|
||||
|
||||
const bBox = await codeBlockController.codeBlock.boundingBox();
|
||||
if (!bBox) throw new Error('Expected bounding box');
|
||||
|
||||
const { x, y, width, height } = bBox;
|
||||
|
||||
// hovering inside the code block should keep the list open
|
||||
await page.mouse.move(x + width / 2, y + height / 2);
|
||||
await expect(langLocator).toBeVisible();
|
||||
|
||||
// hovering out should not close the list
|
||||
await page.mouse.move(x - 10, y - 10);
|
||||
await waitNextFrame(page);
|
||||
await expect(langLocator).toBeVisible();
|
||||
});
|
||||
|
||||
test('language selection list should not change when hovering over its elements', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
await codeBlockController.codeBlock.hover();
|
||||
await codeBlockController.clickLanguageButton();
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
const langListLocator = codeBlockController.langList;
|
||||
const langItemsLocator = langListLocator.locator('icon-button');
|
||||
|
||||
// checking first 4 language list items
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const item = langItemsLocator.nth(i); // current item in language list
|
||||
const nextItem = langItemsLocator.nth(i + 1); // next item in language list
|
||||
|
||||
await item.hover();
|
||||
|
||||
const initialItemText = await item.textContent();
|
||||
const initialNextItemText = await nextItem.textContent();
|
||||
|
||||
await nextItem.hover();
|
||||
|
||||
const currentItemText = await item.textContent();
|
||||
const currentNextItemText = await nextItem.textContent();
|
||||
|
||||
// text content should remain unchanged after next item receives focus
|
||||
expect(initialItemText).toBe(currentItemText);
|
||||
expect(initialNextItemText).toBe(currentNextItemText);
|
||||
}
|
||||
});
|
||||
|
||||
test('format text in code block', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '```ts ');
|
||||
await waitNextFrame(page, 100);
|
||||
await type(page, 'const aaa = 1000;');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
const line = page.locator('affine-code rich-text v-line > div');
|
||||
expect(await line.innerText()).toBe('const aaa = 1000;');
|
||||
|
||||
const { boldBtn, linkBtn } = getFormatBar(page);
|
||||
|
||||
await dragBetweenIndices(page, [0, 1], [0, 2]);
|
||||
await boldBtn.click();
|
||||
expect(await line.innerText()).toBe('const aaa = 1000;');
|
||||
await dragBetweenIndices(page, [0, 4], [0, 7]);
|
||||
await boldBtn.click();
|
||||
expect(await line.innerText()).toBe('const aaa = 1000;');
|
||||
await dragBetweenIndices(page, [0, 8], [0, 16]);
|
||||
await boldBtn.click();
|
||||
expect(await line.innerText()).toBe('const aaa = 1000;');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_format.json`
|
||||
);
|
||||
|
||||
await dragBetweenIndices(page, [0, 4], [0, 10]);
|
||||
await linkBtn.click();
|
||||
await type(page, 'https://www.baidu.com');
|
||||
await pressEnter(page);
|
||||
|
||||
expect(await line.innerText()).toBe('const aaa = 1000;');
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_link.json`
|
||||
);
|
||||
});
|
||||
59
blocksuite/tests-legacy/code/readonly.spec.ts
Normal file
59
blocksuite/tests-legacy/code/readonly.spec.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { switchReadonly } from '../utils/actions/click.js';
|
||||
import {
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressTab,
|
||||
type,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
focusRichTextEnd,
|
||||
initEmptyCodeBlockState,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { assertRichTexts } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import { getCodeBlock } from './utils.js';
|
||||
|
||||
test('should code block widget be disabled in read only mode', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichTextEnd(page);
|
||||
|
||||
await page.waitForTimeout(300);
|
||||
await switchReadonly(page);
|
||||
|
||||
const codeBlockController = getCodeBlock(page);
|
||||
const codeBlock = codeBlockController.codeBlock;
|
||||
await codeBlock.hover();
|
||||
await codeBlockController.clickLanguageButton();
|
||||
await expect(codeBlockController.langList).toBeHidden();
|
||||
|
||||
await codeBlock.hover();
|
||||
await expect(codeBlockController.codeToolbar).toBeVisible();
|
||||
await expect(codeBlockController.moreButton).toHaveAttribute('disabled');
|
||||
|
||||
await expect(codeBlockController.copyButton).toBeVisible();
|
||||
await expect(codeBlockController.moreMenu).toBeHidden();
|
||||
});
|
||||
|
||||
test('should not be able to modify code block in readonly mode', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'const a = 10;');
|
||||
await assertRichTexts(page, ['const a = 10;']);
|
||||
|
||||
await switchReadonly(page);
|
||||
await pressBackspace(page, 3);
|
||||
await pressTab(page, 3);
|
||||
await pressEnter(page, 2);
|
||||
await assertRichTexts(page, ['const a = 10;']);
|
||||
});
|
||||
195
blocksuite/tests-legacy/code/selections.spec.ts
Normal file
195
blocksuite/tests-legacy/code/selections.spec.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { dragBetweenCoords } from '../utils/actions/drag.js';
|
||||
import {
|
||||
pressArrowLeft,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEnterWithShortkey,
|
||||
redoByKeyboard,
|
||||
type,
|
||||
undoByKeyboard,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getInlineSelectionIndex,
|
||||
getInlineSelectionText,
|
||||
initEmptyCodeBlockState,
|
||||
} from '../utils/actions/misc.js';
|
||||
import {
|
||||
assertBlockCount,
|
||||
assertBlockSelections,
|
||||
assertRichTextInlineRange,
|
||||
assertRichTexts,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import { getCodeBlock } from './utils.js';
|
||||
|
||||
test('click outside should close language list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const codeBlock = getCodeBlock(page);
|
||||
await codeBlock.clickLanguageButton();
|
||||
const locator = codeBlock.langList;
|
||||
await expect(locator).toBeVisible();
|
||||
|
||||
const rect = await page.locator('affine-filterable-list').boundingBox();
|
||||
if (!rect) throw new Error('Failed to get bounding box of code block.');
|
||||
await page.mouse.click(rect.x - 10, rect.y - 10);
|
||||
|
||||
await expect(locator).toBeHidden();
|
||||
});
|
||||
|
||||
test('split code by enter', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
|
||||
// he|llo
|
||||
await pressArrowLeft(page, 3);
|
||||
|
||||
await pressEnter(page);
|
||||
await assertRichTexts(page, ['he\nllo']);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await assertRichTexts(page, ['he\nllo']);
|
||||
});
|
||||
|
||||
test('split code with selection by enter', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
|
||||
// select 'll'
|
||||
await pressArrowLeft(page, 1);
|
||||
await page.keyboard.down('Shift');
|
||||
await pressArrowLeft(page, 2);
|
||||
await page.keyboard.up('Shift');
|
||||
|
||||
await pressEnter(page);
|
||||
await assertRichTexts(page, ['he\no']);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await assertRichTexts(page, ['he\no']);
|
||||
});
|
||||
|
||||
test('drag select code block can delete it', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
const codeBlock = page.locator('affine-code');
|
||||
const bbox = await codeBlock.boundingBox();
|
||||
if (!bbox) {
|
||||
throw new Error("Failed to get code block's bounding box");
|
||||
}
|
||||
const position = {
|
||||
startX: bbox.x - 10,
|
||||
startY: bbox.y - 10,
|
||||
endX: bbox.x + bbox.width,
|
||||
endY: bbox.y + bbox.height / 2,
|
||||
};
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: position.startX, y: position.startY },
|
||||
{ x: position.endX, y: position.endY },
|
||||
{ steps: 20 }
|
||||
);
|
||||
await page.waitForTimeout(10);
|
||||
await page.keyboard.press('Backspace');
|
||||
const locator = page.locator('affine-code');
|
||||
await expect(locator).toBeHidden();
|
||||
});
|
||||
|
||||
test('press short key and enter at end of code block can jump out', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await pressEnterWithShortkey(page);
|
||||
|
||||
const locator = page.locator('affine-paragraph');
|
||||
await expect(locator).toBeVisible();
|
||||
});
|
||||
|
||||
test('press short key and enter at end of code block with content can jump out', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await type(page, 'const a = 10;');
|
||||
await pressEnterWithShortkey(page);
|
||||
|
||||
const locator = page.locator('affine-paragraph');
|
||||
await expect(locator).toBeVisible();
|
||||
});
|
||||
|
||||
test('press backspace inside should select code block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
const codeBlock = page.locator('affine-code');
|
||||
const selectedRects = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await page.keyboard.press('Backspace');
|
||||
await expect(selectedRects).toHaveCount(1);
|
||||
await expect(codeBlock).toBeVisible();
|
||||
await page.keyboard.press('Backspace');
|
||||
await expect(selectedRects).toHaveCount(0);
|
||||
await expect(codeBlock).toBeHidden();
|
||||
});
|
||||
|
||||
test('press backspace after code block can select code block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
const code = 'const a = 1;';
|
||||
await type(page, code);
|
||||
|
||||
await assertRichTextInlineRange(page, 0, 12);
|
||||
await pressEnterWithShortkey(page);
|
||||
await assertRichTextInlineRange(page, 1, 0);
|
||||
await assertBlockCount(page, 'paragraph', 1);
|
||||
await pressBackspace(page);
|
||||
await assertBlockSelections(page, ['2']);
|
||||
await assertBlockCount(page, 'paragraph', 0);
|
||||
});
|
||||
|
||||
test('press ArrowUp after code block can enter code block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
const code = 'const a = 1;';
|
||||
await type(page, code);
|
||||
|
||||
await pressEnterWithShortkey(page);
|
||||
await page.keyboard.press('ArrowUp');
|
||||
|
||||
const index = await getInlineSelectionIndex(page);
|
||||
expect(index).toBe(0);
|
||||
|
||||
const text = await getInlineSelectionText(page);
|
||||
expect(text).toBe(code);
|
||||
});
|
||||
61
blocksuite/tests-legacy/code/utils.ts
Normal file
61
blocksuite/tests-legacy/code/utils.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const codeBlockController = getCodeBlock(page);
|
||||
* const codeBlock = codeBlockController.codeBlock;
|
||||
* ```
|
||||
*/
|
||||
export function getCodeBlock(page: Page) {
|
||||
const codeBlock = page.locator('affine-code');
|
||||
const languageButton = page.getByTestId('lang-button');
|
||||
|
||||
const clickLanguageButton = async () => {
|
||||
await codeBlock.hover();
|
||||
await languageButton.click({ delay: 50 });
|
||||
};
|
||||
|
||||
const langList = page.locator('affine-filterable-list');
|
||||
const langFilterInput = langList.locator('#filter-input');
|
||||
|
||||
const codeToolbar = page.locator('affine-code-toolbar');
|
||||
|
||||
const copyButton = codeToolbar.getByRole('button', { name: 'Copy code' });
|
||||
const captionButton = codeToolbar.getByRole('button', { name: 'Caption' });
|
||||
const moreButton = codeToolbar.getByRole('button', { name: 'More' });
|
||||
|
||||
const menu = page.locator('.more-popup-menu');
|
||||
|
||||
const openMore = async () => {
|
||||
await moreButton.click();
|
||||
|
||||
const wrapButton = menu.getByRole('button', { name: 'Wrap' });
|
||||
const cancelWrapButton = menu.getByRole('button', { name: 'Cancel wrap' });
|
||||
const duplicateButton = menu.getByRole('button', { name: 'Duplicate' });
|
||||
const deleteButton = menu.getByRole('button', { name: 'Delete' });
|
||||
|
||||
return {
|
||||
menu,
|
||||
wrapButton,
|
||||
cancelWrapButton,
|
||||
duplicateButton,
|
||||
deleteButton,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
codeBlock,
|
||||
codeToolbar,
|
||||
captionButton,
|
||||
languageButton,
|
||||
langList,
|
||||
copyButton,
|
||||
moreButton,
|
||||
langFilterInput,
|
||||
moreMenu: menu,
|
||||
|
||||
openMore,
|
||||
clickLanguageButton,
|
||||
};
|
||||
}
|
||||
588
blocksuite/tests-legacy/database/actions.ts
Normal file
588
blocksuite/tests-legacy/database/actions.ts
Normal file
@@ -0,0 +1,588 @@
|
||||
import type {
|
||||
RichTextCell,
|
||||
RichTextCellEditing,
|
||||
} from '@blocks/database-block/properties/rich-text/cell-renderer.js';
|
||||
import { press } from '@inline/__tests__/utils.js';
|
||||
import { ZERO_WIDTH_SPACE } from '@inline/consts.js';
|
||||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
pressEnter,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import {
|
||||
getBoundingBox,
|
||||
getBoundingClientRect,
|
||||
getEditorLocator,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/misc.js';
|
||||
|
||||
export async function initDatabaseColumn(page: Page, title = '') {
|
||||
const editor = getEditorLocator(page);
|
||||
await editor.locator('affine-data-view-table-group').first().hover();
|
||||
const columnAddBtn = editor.locator('.header-add-column-button');
|
||||
await columnAddBtn.click();
|
||||
await waitNextFrame(page, 200);
|
||||
|
||||
if (title) {
|
||||
await selectAllByKeyboard(page);
|
||||
await type(page, title);
|
||||
await waitNextFrame(page);
|
||||
await pressEnter(page);
|
||||
} else {
|
||||
await pressEnter(page);
|
||||
}
|
||||
}
|
||||
|
||||
export const renameColumn = async (page: Page, name: string) => {
|
||||
const column = page.locator('affine-database-header-column', {
|
||||
hasText: name,
|
||||
});
|
||||
await column.click();
|
||||
};
|
||||
|
||||
export async function performColumnAction(
|
||||
page: Page,
|
||||
name: string,
|
||||
action: string
|
||||
) {
|
||||
await renameColumn(page, name);
|
||||
|
||||
const actionMenu = page.locator(`.affine-menu-button`, { hasText: action });
|
||||
await actionMenu.click();
|
||||
}
|
||||
|
||||
export async function switchColumnType(
|
||||
page: Page,
|
||||
columnType: string,
|
||||
columnIndex = 1
|
||||
) {
|
||||
const { typeIcon } = await getDatabaseHeaderColumn(page, columnIndex);
|
||||
await typeIcon.click();
|
||||
|
||||
await clickColumnType(page, columnType);
|
||||
}
|
||||
|
||||
export function clickColumnType(page: Page, columnType: string) {
|
||||
const typeMenu = page.locator(`.affine-menu-button`, {
|
||||
hasText: new RegExp(`${columnType}`),
|
||||
});
|
||||
return typeMenu.click();
|
||||
}
|
||||
|
||||
export function getDatabaseBodyRows(page: Page) {
|
||||
const rowContainer = page.locator('.affine-database-block-rows');
|
||||
return rowContainer.locator('.database-row');
|
||||
}
|
||||
|
||||
export function getDatabaseBodyRow(page: Page, rowIndex = 0) {
|
||||
const rows = getDatabaseBodyRows(page);
|
||||
return rows.nth(rowIndex);
|
||||
}
|
||||
|
||||
export function getDatabaseTableContainer(page: Page) {
|
||||
const container = page.locator('.affine-database-table-container');
|
||||
return container;
|
||||
}
|
||||
|
||||
export async function assertDatabaseTitleColumnText(
|
||||
page: Page,
|
||||
title: string,
|
||||
index = 0
|
||||
) {
|
||||
const text = await page.evaluate(index => {
|
||||
const rowContainer = document.querySelector('.affine-database-block-rows');
|
||||
const row = rowContainer?.querySelector(
|
||||
`.database-row:nth-child(${index + 1})`
|
||||
);
|
||||
const titleColumnCell = row?.querySelector('.database-cell:nth-child(1)');
|
||||
const titleSpan = titleColumnCell?.querySelector(
|
||||
'.data-view-header-area-rich-text'
|
||||
) as HTMLElement;
|
||||
if (!titleSpan) throw new Error('Cannot find database title column editor');
|
||||
return titleSpan.innerText;
|
||||
}, index);
|
||||
|
||||
if (title === '') {
|
||||
expect(text).toMatch(new RegExp(`^(|[${ZERO_WIDTH_SPACE}])$`));
|
||||
} else {
|
||||
expect(text).toBe(title);
|
||||
}
|
||||
}
|
||||
|
||||
export function getDatabaseBodyCell(
|
||||
page: Page,
|
||||
{
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
}: {
|
||||
rowIndex: number;
|
||||
columnIndex: number;
|
||||
}
|
||||
) {
|
||||
const row = getDatabaseBodyRow(page, rowIndex);
|
||||
const cell = row.locator('.database-cell').nth(columnIndex);
|
||||
return cell;
|
||||
}
|
||||
|
||||
export function getDatabaseBodyCellContent(
|
||||
page: Page,
|
||||
{
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
cellClass,
|
||||
}: {
|
||||
rowIndex: number;
|
||||
columnIndex: number;
|
||||
cellClass: string;
|
||||
}
|
||||
) {
|
||||
const cell = getDatabaseBodyCell(page, { rowIndex, columnIndex });
|
||||
const cellContent = cell.locator(`.${cellClass}`);
|
||||
return cellContent;
|
||||
}
|
||||
|
||||
export function getFirstColumnCell(page: Page, cellClass: string) {
|
||||
const cellContent = getDatabaseBodyCellContent(page, {
|
||||
rowIndex: 0,
|
||||
columnIndex: 1,
|
||||
cellClass,
|
||||
});
|
||||
return cellContent;
|
||||
}
|
||||
|
||||
export async function clickSelectOption(page: Page, index = 0) {
|
||||
await page.locator('.select-option-icon').nth(index).click();
|
||||
}
|
||||
|
||||
export async function performSelectColumnTagAction(
|
||||
page: Page,
|
||||
name: string,
|
||||
index = 0
|
||||
) {
|
||||
await clickSelectOption(page, index);
|
||||
await page
|
||||
.locator('.affine-menu-button', { hasText: new RegExp(name) })
|
||||
.click();
|
||||
}
|
||||
|
||||
export async function assertSelectedStyle(
|
||||
page: Page,
|
||||
key: keyof CSSStyleDeclaration,
|
||||
value: string
|
||||
) {
|
||||
const style = await getElementStyle(page, '.select-selected', key);
|
||||
expect(style).toBe(value);
|
||||
}
|
||||
|
||||
export async function clickDatabaseOutside(page: Page) {
|
||||
const docTitle = page.locator('.doc-title-container');
|
||||
await docTitle.click();
|
||||
}
|
||||
|
||||
export async function assertColumnWidth(locator: Locator, width: number) {
|
||||
const box = await getBoundingBox(locator);
|
||||
expect(box.width).toBe(width + 1);
|
||||
return box;
|
||||
}
|
||||
|
||||
export async function assertDatabaseCellRichTexts(
|
||||
page: Page,
|
||||
{
|
||||
rowIndex = 0,
|
||||
columnIndex = 1,
|
||||
text,
|
||||
}: {
|
||||
rowIndex?: number;
|
||||
columnIndex?: number;
|
||||
text: string;
|
||||
}
|
||||
) {
|
||||
const cellContainer = page.locator(
|
||||
`affine-database-cell-container[data-row-index='${rowIndex}'][data-column-index='${columnIndex}']`
|
||||
);
|
||||
|
||||
const cellEditing = cellContainer.locator(
|
||||
'affine-database-rich-text-cell-editing'
|
||||
);
|
||||
const cell = cellContainer.locator('affine-database-rich-text-cell');
|
||||
|
||||
const richText = (await cellEditing.count()) === 0 ? cell : cellEditing;
|
||||
const actualTexts = await richText.evaluate(ele => {
|
||||
return (ele as RichTextCellEditing).inlineEditor?.yTextString;
|
||||
});
|
||||
expect(actualTexts).toEqual(text);
|
||||
}
|
||||
|
||||
export async function assertDatabaseCellNumber(
|
||||
page: Page,
|
||||
{
|
||||
rowIndex = 0,
|
||||
columnIndex = 1,
|
||||
text,
|
||||
}: {
|
||||
rowIndex?: number;
|
||||
columnIndex?: number;
|
||||
text: string;
|
||||
}
|
||||
) {
|
||||
const actualText = await page
|
||||
.locator('.affine-database-block-rows')
|
||||
.locator('.database-row')
|
||||
.nth(rowIndex)
|
||||
.locator('.database-cell')
|
||||
.nth(columnIndex)
|
||||
.locator('.number')
|
||||
.textContent();
|
||||
expect(actualText?.trim()).toEqual(text);
|
||||
}
|
||||
|
||||
export async function assertDatabaseCellLink(
|
||||
page: Page,
|
||||
{
|
||||
rowIndex = 0,
|
||||
columnIndex = 1,
|
||||
text,
|
||||
}: {
|
||||
rowIndex?: number;
|
||||
columnIndex?: number;
|
||||
text: string;
|
||||
}
|
||||
) {
|
||||
const actualTexts = await page.evaluate(
|
||||
({ rowIndex, columnIndex }) => {
|
||||
const rows = document.querySelector('.affine-database-block-rows');
|
||||
const row = rows?.querySelector(
|
||||
`.database-row:nth-child(${rowIndex + 1})`
|
||||
);
|
||||
const cell = row?.querySelector(
|
||||
`.database-cell:nth-child(${columnIndex + 1})`
|
||||
);
|
||||
const richText =
|
||||
cell?.querySelector<RichTextCell>('affine-database-link-cell') ??
|
||||
cell?.querySelector<RichTextCellEditing>(
|
||||
'affine-database-link-cell-editing'
|
||||
);
|
||||
if (!richText) throw new Error('Missing database rich text cell');
|
||||
return richText.inlineEditor.yText.toString();
|
||||
},
|
||||
{ rowIndex, columnIndex }
|
||||
);
|
||||
expect(actualTexts).toEqual(text);
|
||||
}
|
||||
|
||||
export async function assertDatabaseTitleText(page: Page, text: string) {
|
||||
const dbTitle = page.locator('[data-block-is-database-title="true"]');
|
||||
expect(await dbTitle.inputValue()).toEqual(text);
|
||||
}
|
||||
|
||||
export async function waitSearchTransitionEnd(page: Page) {
|
||||
await waitNextFrame(page, 400);
|
||||
}
|
||||
|
||||
export async function assertDatabaseSearching(
|
||||
page: Page,
|
||||
isSearching: boolean
|
||||
) {
|
||||
const searchExpand = page.locator('.search-container-expand');
|
||||
const count = await searchExpand.count();
|
||||
expect(count).toBe(isSearching ? 1 : 0);
|
||||
}
|
||||
|
||||
export async function focusDatabaseSearch(page: Page) {
|
||||
await (await getDatabaseMouse(page)).mouseOver();
|
||||
|
||||
const searchExpand = page.locator('.search-container-expand');
|
||||
const count = await searchExpand.count();
|
||||
if (count === 1) {
|
||||
const input = page.locator('.affine-database-search-input');
|
||||
await input.click();
|
||||
} else {
|
||||
const searchIcon = page.locator('.affine-database-search-input-icon');
|
||||
await searchIcon.click();
|
||||
await waitSearchTransitionEnd(page);
|
||||
}
|
||||
}
|
||||
|
||||
export async function blurDatabaseSearch(page: Page) {
|
||||
const dbTitle = page.locator('[data-block-is-database-title="true"]');
|
||||
await dbTitle.click();
|
||||
}
|
||||
|
||||
export async function focusDatabaseHeader(page: Page, columnIndex = 0) {
|
||||
const column = page.locator('.affine-database-column').nth(columnIndex);
|
||||
const box = await getBoundingBox(column);
|
||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||
await waitNextFrame(page);
|
||||
return column;
|
||||
}
|
||||
|
||||
export async function getDatabaseMouse(page: Page) {
|
||||
const databaseRect = await getBoundingClientRect(
|
||||
page,
|
||||
'.affine-database-table'
|
||||
);
|
||||
return {
|
||||
mouseOver: async () => {
|
||||
await page.mouse.move(databaseRect.x, databaseRect.y);
|
||||
},
|
||||
mouseLeave: async () => {
|
||||
await page.mouse.move(databaseRect.x - 1, databaseRect.y - 1);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function getDatabaseHeaderColumn(page: Page, index = 0) {
|
||||
const column = page.locator('.affine-database-column').nth(index);
|
||||
const box = await getBoundingBox(column);
|
||||
const textElement = column.locator('.affine-database-column-text-input');
|
||||
const text = await textElement.innerText();
|
||||
const typeIcon = column.locator('.affine-database-column-type-icon');
|
||||
|
||||
return {
|
||||
column,
|
||||
box,
|
||||
text,
|
||||
textElement,
|
||||
typeIcon,
|
||||
};
|
||||
}
|
||||
|
||||
export async function assertRowsSelection(
|
||||
page: Page,
|
||||
rowIndexes: [start: number, end: number]
|
||||
) {
|
||||
const rows = page.locator('data-view-table-row');
|
||||
const startIndex = rowIndexes[0];
|
||||
const endIndex = rowIndexes[1];
|
||||
for (let i = startIndex; i <= endIndex; i++) {
|
||||
const row = rows.nth(i);
|
||||
await row.locator('.row-select-checkbox .selected').isVisible();
|
||||
}
|
||||
}
|
||||
|
||||
export async function assertCellsSelection(
|
||||
page: Page,
|
||||
cellIndexes: {
|
||||
start: [rowIndex: number, columnIndex: number];
|
||||
end?: [rowIndex: number, columnIndex: number];
|
||||
}
|
||||
) {
|
||||
const { start, end } = cellIndexes;
|
||||
|
||||
if (!end) {
|
||||
// single cell
|
||||
const focus = page.locator('.database-focus');
|
||||
const focusBox = await getBoundingBox(focus);
|
||||
|
||||
const [rowIndex, columnIndex] = start;
|
||||
const cell = getDatabaseBodyCell(page, { rowIndex, columnIndex });
|
||||
const cellBox = await getBoundingBox(cell);
|
||||
expect(focusBox).toEqual({
|
||||
x: cellBox.x,
|
||||
y: cellBox.y - 1,
|
||||
height: cellBox.height + 2,
|
||||
width: cellBox.width + 1,
|
||||
});
|
||||
} else {
|
||||
// multi cells
|
||||
const selection = page.locator('.database-selection');
|
||||
const selectionBox = await getBoundingBox(selection);
|
||||
|
||||
const [startRowIndex, startColumnIndex] = start;
|
||||
const [endRowIndex, endColumnIndex] = end;
|
||||
|
||||
const rowIndexStart = Math.min(startRowIndex, endRowIndex);
|
||||
const rowIndexEnd = Math.max(startRowIndex, endRowIndex);
|
||||
const columnIndexStart = Math.min(startColumnIndex, endColumnIndex);
|
||||
const columnIndexEnd = Math.max(startColumnIndex, endColumnIndex);
|
||||
|
||||
let height = 0;
|
||||
let width = 0;
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
for (let i = rowIndexStart; i <= rowIndexEnd; i++) {
|
||||
const cell = getDatabaseBodyCell(page, {
|
||||
rowIndex: i,
|
||||
columnIndex: columnIndexStart,
|
||||
});
|
||||
const box = await getBoundingBox(cell);
|
||||
height += box.height + 1;
|
||||
if (i === rowIndexStart) {
|
||||
y = box.y;
|
||||
}
|
||||
}
|
||||
|
||||
for (let j = columnIndexStart; j <= columnIndexEnd; j++) {
|
||||
const cell = getDatabaseBodyCell(page, {
|
||||
rowIndex: rowIndexStart,
|
||||
columnIndex: j,
|
||||
});
|
||||
const box = await getBoundingBox(cell);
|
||||
width += box.width;
|
||||
if (j === columnIndexStart) {
|
||||
x = box.x;
|
||||
}
|
||||
}
|
||||
|
||||
expect(selectionBox).toEqual({
|
||||
x,
|
||||
y,
|
||||
height,
|
||||
width: width + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function getElementStyle(
|
||||
page: Page,
|
||||
selector: string,
|
||||
key: keyof CSSStyleDeclaration
|
||||
) {
|
||||
const style = await page.evaluate(
|
||||
({ key, selector }) => {
|
||||
const el = document.querySelector<HTMLElement>(selector);
|
||||
if (!el) throw new Error(`Missing ${selector} tag`);
|
||||
// @ts-ignore
|
||||
return el.style[key];
|
||||
},
|
||||
{
|
||||
key,
|
||||
selector,
|
||||
}
|
||||
);
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
export async function focusKanbanCardHeader(page: Page, index = 0) {
|
||||
const cardHeader = page.locator('data-view-header-area-text').nth(index);
|
||||
await cardHeader.click();
|
||||
}
|
||||
|
||||
export async function clickKanbanCardHeader(page: Page, index = 0) {
|
||||
const cardHeader = page.locator('data-view-header-area-text').nth(index);
|
||||
await cardHeader.click();
|
||||
await cardHeader.click();
|
||||
}
|
||||
|
||||
export async function assertKanbanCardHeaderText(
|
||||
page: Page,
|
||||
text: string,
|
||||
index = 0
|
||||
) {
|
||||
const cardHeader = page.locator('data-view-header-area-text').nth(index);
|
||||
|
||||
await expect(cardHeader).toHaveText(text);
|
||||
}
|
||||
|
||||
export async function assertKanbanCellSelected(
|
||||
page: Page,
|
||||
{
|
||||
groupIndex,
|
||||
cardIndex,
|
||||
cellIndex,
|
||||
}: {
|
||||
groupIndex: number;
|
||||
cardIndex: number;
|
||||
cellIndex: number;
|
||||
}
|
||||
) {
|
||||
const border = await page.evaluate(
|
||||
({ groupIndex, cardIndex, cellIndex }) => {
|
||||
const group = document.querySelector(
|
||||
`affine-data-view-kanban-group:nth-child(${groupIndex + 1})`
|
||||
);
|
||||
const card = group?.querySelector(
|
||||
`affine-data-view-kanban-card:nth-child(${cardIndex + 1})`
|
||||
);
|
||||
const cells = Array.from(
|
||||
card?.querySelectorAll<HTMLElement>(`affine-data-view-kanban-cell`) ??
|
||||
[]
|
||||
);
|
||||
const cell = cells[cellIndex];
|
||||
if (!cell) throw new Error(`Missing cell tag`);
|
||||
return cell.style.border;
|
||||
},
|
||||
{
|
||||
groupIndex,
|
||||
cardIndex,
|
||||
cellIndex,
|
||||
}
|
||||
);
|
||||
|
||||
expect(border).toEqual('1px solid var(--affine-primary-color)');
|
||||
}
|
||||
|
||||
export async function assertKanbanCardSelected(
|
||||
page: Page,
|
||||
{
|
||||
groupIndex,
|
||||
cardIndex,
|
||||
}: {
|
||||
groupIndex: number;
|
||||
cardIndex: number;
|
||||
}
|
||||
) {
|
||||
const border = await page.evaluate(
|
||||
({ groupIndex, cardIndex }) => {
|
||||
const group = document.querySelector(
|
||||
`affine-data-view-kanban-group:nth-child(${groupIndex + 1})`
|
||||
);
|
||||
const card = group?.querySelector<HTMLElement>(
|
||||
`affine-data-view-kanban-card:nth-child(${cardIndex + 1})`
|
||||
);
|
||||
if (!card) throw new Error(`Missing card tag`);
|
||||
return card.style.border;
|
||||
},
|
||||
{
|
||||
groupIndex,
|
||||
cardIndex,
|
||||
}
|
||||
);
|
||||
|
||||
expect(border).toEqual('1px solid var(--affine-primary-color)');
|
||||
}
|
||||
|
||||
export function getKanbanCard(
|
||||
page: Page,
|
||||
{
|
||||
groupIndex,
|
||||
cardIndex,
|
||||
}: {
|
||||
groupIndex: number;
|
||||
cardIndex: number;
|
||||
}
|
||||
) {
|
||||
const group = page.locator('affine-data-view-kanban-group').nth(groupIndex);
|
||||
const card = group.locator('affine-data-view-kanban-card').nth(cardIndex);
|
||||
return card;
|
||||
}
|
||||
export const moveToCenterOf = async (page: Page, locator: Locator) => {
|
||||
const box = (await locator.boundingBox())!;
|
||||
expect(box).toBeDefined();
|
||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||
};
|
||||
export const changeColumnType = async (
|
||||
page: Page,
|
||||
column: number,
|
||||
name: string
|
||||
) => {
|
||||
await waitNextFrame(page);
|
||||
await page.locator('affine-database-header-column').nth(column).click();
|
||||
await waitNextFrame(page, 200);
|
||||
await pressKey(page, 'Escape');
|
||||
await pressKey(page, 'ArrowDown');
|
||||
await pressKey(page, 'Enter');
|
||||
await type(page, name);
|
||||
await pressKey(page, 'ArrowDown');
|
||||
await pressKey(page, 'Enter');
|
||||
};
|
||||
export const pressKey = async (page: Page, key: string, count: number = 1) => {
|
||||
for (let i = 0; i < count; i++) {
|
||||
await waitNextFrame(page);
|
||||
await press(page, key);
|
||||
}
|
||||
await waitNextFrame(page);
|
||||
};
|
||||
176
blocksuite/tests-legacy/database/clipboard.spec.ts
Normal file
176
blocksuite/tests-legacy/database/clipboard.spec.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { dragBetweenCoords } from '../utils/actions/drag.js';
|
||||
import {
|
||||
copyByKeyboard,
|
||||
pasteByKeyboard,
|
||||
pressArrowDown,
|
||||
pressArrowUp,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
type,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getBoundingBox,
|
||||
initDatabaseDynamicRowWithData,
|
||||
initDatabaseRowWithData,
|
||||
initEmptyDatabaseState,
|
||||
initEmptyDatabaseWithParagraphState,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { assertRichTexts } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import {
|
||||
assertDatabaseTitleColumnText,
|
||||
getDatabaseBodyCell,
|
||||
getElementStyle,
|
||||
initDatabaseColumn,
|
||||
switchColumnType,
|
||||
} from './actions.js';
|
||||
|
||||
test.describe('copy&paste when editing', () => {
|
||||
test.skip('should support copy&paste of the title column', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseWithParagraphState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseRowWithData(page, 'abc123');
|
||||
await pressEscape(page);
|
||||
|
||||
await pressEnter(page);
|
||||
await page.keyboard.down('Shift');
|
||||
for (let i = 0; i < 4; i++) {
|
||||
await page.keyboard.press('ArrowLeft');
|
||||
}
|
||||
await page.keyboard.up('Shift');
|
||||
await copyByKeyboard(page);
|
||||
|
||||
const bgValue = await getElementStyle(page, '.database-focus', 'boxShadow');
|
||||
expect(bgValue).not.toBe('unset');
|
||||
|
||||
await focusRichText(page, 1);
|
||||
await pasteByKeyboard(page);
|
||||
await assertRichTexts(page, ['Database 1', 'c123']);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('copy&paste when selecting', () => {
|
||||
test.skip('should support copy&paste of a single cell', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseRowWithData(page, 'abc123');
|
||||
await initDatabaseRowWithData(page, '');
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await pressArrowUp(page);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
await pressArrowDown(page);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page);
|
||||
await assertDatabaseTitleColumnText(page, 'abc123', 1);
|
||||
});
|
||||
|
||||
test.skip('should support copy&paste of multi cells', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseRowWithData(page, 'text1');
|
||||
await initDatabaseDynamicRowWithData(page, '123', false);
|
||||
await pressEscape(page);
|
||||
await initDatabaseRowWithData(page, 'text2');
|
||||
await initDatabaseDynamicRowWithData(page, 'a', false);
|
||||
await pressEscape(page);
|
||||
|
||||
await initDatabaseRowWithData(page, '');
|
||||
await initDatabaseRowWithData(page, '');
|
||||
|
||||
const startCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 0,
|
||||
columnIndex: 0,
|
||||
});
|
||||
const startCellBox = await getBoundingBox(startCell);
|
||||
const endCell = getDatabaseBodyCell(page, { rowIndex: 1, columnIndex: 1 });
|
||||
const endCellBox = await getBoundingBox(endCell);
|
||||
const startX = startCellBox.x + startCellBox.width / 2;
|
||||
const startY = startCellBox.y + startCellBox.height / 2;
|
||||
const endX = endCellBox.x + endCellBox.width / 2;
|
||||
const endY = endCellBox.y + endCellBox.height / 2;
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: startX, y: startY },
|
||||
{ x: endX, y: endY },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
await copyByKeyboard(page);
|
||||
|
||||
await pressArrowDown(page);
|
||||
await pressArrowDown(page);
|
||||
await waitNextFrame(page);
|
||||
await pasteByKeyboard(page, false);
|
||||
|
||||
await assertDatabaseTitleColumnText(page, 'text1', 2);
|
||||
await assertDatabaseTitleColumnText(page, 'text2', 3);
|
||||
const selectCell21 = getDatabaseBodyCell(page, {
|
||||
rowIndex: 2,
|
||||
columnIndex: 1,
|
||||
});
|
||||
const selectCell31 = getDatabaseBodyCell(page, {
|
||||
rowIndex: 3,
|
||||
columnIndex: 1,
|
||||
});
|
||||
expect(await selectCell21.innerText()).toBe('123');
|
||||
expect(await selectCell31.innerText()).toBe('a');
|
||||
});
|
||||
|
||||
test.skip('should support copy&paste of a single row', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseRowWithData(page, 'text1');
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await initDatabaseDynamicRowWithData(page, 'abc', false);
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await initDatabaseColumn(page);
|
||||
await switchColumnType(page, 'Number', 2);
|
||||
const numberCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 0,
|
||||
columnIndex: 2,
|
||||
});
|
||||
await numberCell.click();
|
||||
await waitNextFrame(page);
|
||||
await type(page, '123');
|
||||
await pressEscape(page);
|
||||
await pressEscape(page);
|
||||
await copyByKeyboard(page);
|
||||
|
||||
await initDatabaseRowWithData(page, '');
|
||||
await pressEscape(page);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertDatabaseTitleColumnText(page, 'text1', 1);
|
||||
const selectCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 1,
|
||||
columnIndex: 1,
|
||||
});
|
||||
expect(await selectCell.innerText()).toBe('abc');
|
||||
const selectNumberCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 1,
|
||||
columnIndex: 2,
|
||||
});
|
||||
expect(await selectNumberCell.innerText()).toBe('123');
|
||||
});
|
||||
});
|
||||
599
blocksuite/tests-legacy/database/column.spec.ts
Normal file
599
blocksuite/tests-legacy/database/column.spec.ts
Normal file
@@ -0,0 +1,599 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
assertDatabaseColumnOrder,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
getBoundingBox,
|
||||
initDatabaseDynamicRowWithData,
|
||||
initEmptyDatabaseState,
|
||||
pressArrowRight,
|
||||
pressArrowUp,
|
||||
pressArrowUpWithShiftKey,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
redoByClick,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
undoByClick,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import {
|
||||
assertDatabaseCellNumber,
|
||||
assertDatabaseCellRichTexts,
|
||||
assertSelectedStyle,
|
||||
changeColumnType,
|
||||
clickDatabaseOutside,
|
||||
clickSelectOption,
|
||||
getDatabaseHeaderColumn,
|
||||
getFirstColumnCell,
|
||||
initDatabaseColumn,
|
||||
performColumnAction,
|
||||
performSelectColumnTagAction,
|
||||
switchColumnType,
|
||||
} from './actions.js';
|
||||
|
||||
test.describe('column operations', () => {
|
||||
test('should support rename column', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, 'abc');
|
||||
|
||||
const { textElement } = await getDatabaseHeaderColumn(page, 1);
|
||||
expect(await textElement.innerText()).toBe('abc');
|
||||
await textElement.click();
|
||||
await waitNextFrame(page, 200);
|
||||
await pressArrowRight(page);
|
||||
await type(page, '123');
|
||||
await pressEnter(page);
|
||||
expect(await textElement.innerText()).toBe('abc123');
|
||||
|
||||
await undoByClick(page);
|
||||
expect(await textElement.innerText()).toBe('abc');
|
||||
await redoByClick(page);
|
||||
expect(await textElement.innerText()).toBe('abc123');
|
||||
});
|
||||
|
||||
test('should support add new column', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await pressEscape(page);
|
||||
const { text: title1 } = await getDatabaseHeaderColumn(page, 1);
|
||||
expect(title1).toBe('Column 1');
|
||||
|
||||
const selected = getFirstColumnCell(page, 'select-selected');
|
||||
expect(await selected.innerText()).toBe('123');
|
||||
|
||||
await initDatabaseColumn(page, 'abc');
|
||||
const { text: title2 } = await getDatabaseHeaderColumn(page, 2);
|
||||
expect(title2).toBe('abc');
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
const { text: title3 } = await getDatabaseHeaderColumn(page, 3);
|
||||
expect(title3).toBe('Column 2');
|
||||
});
|
||||
|
||||
test('should support right insert column', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, '1');
|
||||
|
||||
await performColumnAction(page, '1', 'Insert right');
|
||||
await selectAllByKeyboard(page);
|
||||
await type(page, '2');
|
||||
await pressEnter(page);
|
||||
const columns = page.locator('.affine-database-column');
|
||||
expect(await columns.count()).toBe(3);
|
||||
|
||||
await assertDatabaseColumnOrder(page, ['1', '2']);
|
||||
});
|
||||
|
||||
test('should support left insert column', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, '1');
|
||||
|
||||
await performColumnAction(page, '1', 'Insert left');
|
||||
await selectAllByKeyboard(page);
|
||||
await type(page, '2');
|
||||
await pressEnter(page);
|
||||
const columns = page.locator('.affine-database-column');
|
||||
expect(await columns.count()).toBe(3);
|
||||
|
||||
await assertDatabaseColumnOrder(page, ['2', '1']);
|
||||
});
|
||||
|
||||
test('should support delete column', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, '1');
|
||||
|
||||
const columns = page.locator('.affine-database-column');
|
||||
expect(await columns.count()).toBe(2);
|
||||
|
||||
await performColumnAction(page, '1', 'Delete');
|
||||
expect(await columns.count()).toBe(1);
|
||||
});
|
||||
|
||||
test('should support duplicate column', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, '1');
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await pressEscape(page);
|
||||
await performColumnAction(page, '1', 'duplicate');
|
||||
await pressEscape(page);
|
||||
const cells = page.locator('affine-database-multi-select-cell');
|
||||
expect(await cells.count()).toBe(2);
|
||||
|
||||
const secondCell = cells.nth(1);
|
||||
const selected = secondCell.locator('.select-selected');
|
||||
expect(await selected.innerText()).toBe('123');
|
||||
});
|
||||
|
||||
test('should support move column right', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, '1');
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await pressEscape(page);
|
||||
await initDatabaseColumn(page, '2');
|
||||
await initDatabaseDynamicRowWithData(page, 'abc', false, 1);
|
||||
await pressEscape(page);
|
||||
await assertDatabaseColumnOrder(page, ['1', '2']);
|
||||
await waitNextFrame(page, 350);
|
||||
await performColumnAction(page, '1', 'Move right');
|
||||
await assertDatabaseColumnOrder(page, ['2', '1']);
|
||||
|
||||
await undoByClick(page);
|
||||
const { column } = await getDatabaseHeaderColumn(page, 2);
|
||||
await column.click();
|
||||
const moveLeft = page.locator('.action', { hasText: 'Move right' });
|
||||
expect(await moveLeft.count()).toBe(0);
|
||||
});
|
||||
|
||||
test('should support move column left', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, '1');
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await pressEscape(page);
|
||||
await initDatabaseColumn(page, '2');
|
||||
await initDatabaseDynamicRowWithData(page, 'abc', false, 1);
|
||||
await pressEscape(page);
|
||||
await assertDatabaseColumnOrder(page, ['1', '2']);
|
||||
|
||||
const { column } = await getDatabaseHeaderColumn(page, 0);
|
||||
await column.click();
|
||||
const moveLeft = page.locator('.action', { hasText: 'Move left' });
|
||||
expect(await moveLeft.count()).toBe(0);
|
||||
await waitNextFrame(page, 200);
|
||||
await pressEscape(page);
|
||||
await pressEscape(page);
|
||||
|
||||
await performColumnAction(page, '2', 'Move left');
|
||||
await assertDatabaseColumnOrder(page, ['2', '1']);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('switch column type', () => {
|
||||
test('switch to number', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123abc', true);
|
||||
await pressEscape(page);
|
||||
await changeColumnType(page, 1, 'Number');
|
||||
|
||||
const cell = getFirstColumnCell(page, 'number');
|
||||
await assertDatabaseCellNumber(page, {
|
||||
text: '',
|
||||
});
|
||||
await pressEnter(page);
|
||||
await type(page, '123abc');
|
||||
await pressEscape(page);
|
||||
expect((await cell.textContent())?.trim()).toBe('123');
|
||||
});
|
||||
|
||||
test('switch to rich-text', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123abc', true);
|
||||
await pressEscape(page);
|
||||
await switchColumnType(page, 'Text');
|
||||
|
||||
// For now, rich-text will only be initialized on click
|
||||
// Therefore, for the time being, here is to detect whether there is '.affine-database-rich-text'
|
||||
const cell = getFirstColumnCell(page, 'affine-database-rich-text');
|
||||
expect(await cell.count()).toBe(1);
|
||||
await pressEnter(page);
|
||||
await type(page, '123');
|
||||
await pressEscape(page);
|
||||
await pressEnter(page);
|
||||
await type(page, 'abc');
|
||||
await pressEscape(page);
|
||||
await assertDatabaseCellRichTexts(page, { text: '123abc123abc' });
|
||||
});
|
||||
|
||||
test('switch between multi-select and select', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await type(page, 'abc');
|
||||
await pressEnter(page);
|
||||
await pressEscape(page);
|
||||
const cell = getFirstColumnCell(page, 'select-selected');
|
||||
expect(await cell.count()).toBe(2);
|
||||
|
||||
await switchColumnType(page, 'Select', 1);
|
||||
expect(await cell.count()).toBe(1);
|
||||
expect(await cell.innerText()).toBe('123');
|
||||
|
||||
await pressEnter(page);
|
||||
await type(page, 'def');
|
||||
await pressEnter(page);
|
||||
expect(await cell.innerText()).toBe('def');
|
||||
|
||||
await switchColumnType(page, 'Multi-select');
|
||||
await pressEnter(page);
|
||||
await type(page, '666');
|
||||
await pressEnter(page);
|
||||
await pressEscape(page);
|
||||
expect(await cell.count()).toBe(2);
|
||||
expect(await cell.nth(0).innerText()).toBe('def');
|
||||
expect(await cell.nth(1).innerText()).toBe('666');
|
||||
|
||||
await switchColumnType(page, 'Select');
|
||||
expect(await cell.count()).toBe(1);
|
||||
expect(await cell.innerText()).toBe('def');
|
||||
|
||||
await pressEnter(page);
|
||||
await type(page, '888');
|
||||
await pressEnter(page);
|
||||
expect(await cell.innerText()).toBe('888');
|
||||
});
|
||||
|
||||
test('switch between number and rich-text', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await switchColumnType(page, 'Number');
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, '123abc', true);
|
||||
await assertDatabaseCellNumber(page, {
|
||||
text: '123',
|
||||
});
|
||||
|
||||
await switchColumnType(page, 'Text');
|
||||
await pressEnter(page);
|
||||
await type(page, 'abc');
|
||||
await pressEscape(page);
|
||||
await assertDatabaseCellRichTexts(page, { text: '123abc' });
|
||||
|
||||
await switchColumnType(page, 'Number');
|
||||
await assertDatabaseCellNumber(page, {
|
||||
text: '123',
|
||||
});
|
||||
});
|
||||
|
||||
test('switch number to select', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await switchColumnType(page, 'Number');
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
const cell = getFirstColumnCell(page, 'number');
|
||||
expect((await cell.textContent())?.trim()).toBe('123');
|
||||
|
||||
await switchColumnType(page, 'Select');
|
||||
await initDatabaseDynamicRowWithData(page, 'abc');
|
||||
const selectCell = getFirstColumnCell(page, 'select-selected');
|
||||
expect(await selectCell.innerText()).toBe('abc');
|
||||
|
||||
await switchColumnType(page, 'Number');
|
||||
await assertDatabaseCellNumber(page, {
|
||||
text: '',
|
||||
});
|
||||
});
|
||||
|
||||
test('switch to checkbox', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
await changeColumnType(page, 1, 'Checkbox');
|
||||
|
||||
const checkbox = getFirstColumnCell(page, 'checkbox');
|
||||
await expect(checkbox).not.toHaveClass('checked');
|
||||
|
||||
await waitNextFrame(page, 500);
|
||||
await checkbox.click();
|
||||
await expect(checkbox).toHaveClass(/checked/);
|
||||
|
||||
await undoByClick(page);
|
||||
await expect(checkbox).not.toHaveClass('checked');
|
||||
});
|
||||
|
||||
test('checkbox to text', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
await changeColumnType(page, 1, 'Checkbox');
|
||||
|
||||
let checkbox = getFirstColumnCell(page, 'checkbox');
|
||||
await expect(checkbox).not.toHaveClass('checked');
|
||||
|
||||
// checked
|
||||
await checkbox.click();
|
||||
await changeColumnType(page, 1, 'Text');
|
||||
await clickDatabaseOutside(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await assertDatabaseCellRichTexts(page, { text: 'Yes' });
|
||||
await clickDatabaseOutside(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await changeColumnType(page, 1, 'Checkbox');
|
||||
checkbox = getFirstColumnCell(page, 'checkbox');
|
||||
await expect(checkbox).toHaveClass(/checked/);
|
||||
|
||||
// not checked
|
||||
await checkbox.click();
|
||||
await changeColumnType(page, 1, 'Text');
|
||||
await clickDatabaseOutside(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await assertDatabaseCellRichTexts(page, { text: 'No' });
|
||||
await clickDatabaseOutside(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await changeColumnType(page, 1, 'Checkbox');
|
||||
checkbox = getFirstColumnCell(page, 'checkbox');
|
||||
await expect(checkbox).not.toHaveClass('checked');
|
||||
});
|
||||
|
||||
test('switch to progress', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
await switchColumnType(page, 'Progress');
|
||||
|
||||
const progress = getFirstColumnCell(page, 'progress');
|
||||
expect(await progress.textContent()).toBe('0');
|
||||
await waitNextFrame(page, 500);
|
||||
const progressBg = page.locator('.affine-database-progress-bg');
|
||||
const {
|
||||
x: progressBgX,
|
||||
y: progressBgY,
|
||||
width: progressBgWidth,
|
||||
} = await getBoundingBox(progressBg);
|
||||
await page.mouse.move(progressBgX, progressBgY);
|
||||
await page.mouse.click(progressBgX, progressBgY);
|
||||
const dragHandle = page.locator('.affine-database-progress-drag-handle');
|
||||
const {
|
||||
x: dragX,
|
||||
y: dragY,
|
||||
width,
|
||||
height,
|
||||
} = await getBoundingBox(dragHandle);
|
||||
const dragCenterX = dragX + width / 2;
|
||||
const dragCenterY = dragY + height / 2;
|
||||
await page.mouse.move(dragCenterX, dragCenterY);
|
||||
|
||||
const endX = dragCenterX + progressBgWidth;
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: dragCenterX, y: dragCenterY },
|
||||
{ x: endX, y: dragCenterY }
|
||||
);
|
||||
expect(await progress.textContent()).toBe('100');
|
||||
await pressEscape(page);
|
||||
await undoByClick(page);
|
||||
expect(await progress.textContent()).toBe('0');
|
||||
});
|
||||
|
||||
test('switch to link', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
|
||||
await switchColumnType(page, 'Link');
|
||||
|
||||
const linkText = 'http://example.com';
|
||||
const cell = getFirstColumnCell(page, 'affine-database-link');
|
||||
await pressEnter(page);
|
||||
await type(page, linkText);
|
||||
await pressEscape(page);
|
||||
const link = cell.locator('affine-database-link-node > a');
|
||||
const linkContent = link.locator('.link-node-text');
|
||||
await expect(link).toHaveAttribute('href', linkText);
|
||||
expect(await linkContent.textContent()).toBe(linkText);
|
||||
|
||||
// not link text
|
||||
await cell.hover();
|
||||
const linkEdit = getFirstColumnCell(page, 'affine-database-link-icon');
|
||||
await linkEdit.click();
|
||||
await selectAllByKeyboard(page);
|
||||
await type(page, 'abc');
|
||||
await pressEnter(page);
|
||||
await expect(link).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('select column tag action', () => {
|
||||
test('should support select tag renaming', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await type(page, 'abc');
|
||||
await pressEnter(page);
|
||||
await clickSelectOption(page);
|
||||
await waitNextFrame(page);
|
||||
await pressArrowRight(page);
|
||||
await type(page, '4567abc00');
|
||||
await pressEnter(page);
|
||||
const options = page.locator('.select-options-container .tag-text');
|
||||
expect(await options.nth(0).innerText()).toBe('abc4567abc00');
|
||||
expect(await options.nth(1).innerText()).toBe('123');
|
||||
});
|
||||
|
||||
test('should select tag renaming support shortcut key', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await clickSelectOption(page);
|
||||
await waitNextFrame(page);
|
||||
await pressArrowRight(page);
|
||||
await type(page, '456');
|
||||
// esc
|
||||
await pressEscape(page);
|
||||
await pressEscape(page);
|
||||
const options = page.locator('.select-options-container .tag-text');
|
||||
const option1 = options.nth(0);
|
||||
expect(await option1.innerText()).toBe('123456');
|
||||
});
|
||||
|
||||
test('should support select tag deletion', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await performSelectColumnTagAction(page, 'Delete');
|
||||
const options = page.locator('.select-option-name');
|
||||
expect(await options.count()).toBe(0);
|
||||
});
|
||||
|
||||
test('should support modifying select tag color', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await performSelectColumnTagAction(page, 'Red');
|
||||
await pressEscape(page);
|
||||
await assertSelectedStyle(
|
||||
page,
|
||||
'backgroundColor',
|
||||
'var(--affine-v2-chip-label-red)'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('drag-to-fill', () => {
|
||||
test('should show when cell in focus and hide on blur', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
|
||||
await pressEscape(page);
|
||||
|
||||
const dragToFillHandle = page.locator('.drag-to-fill');
|
||||
|
||||
await expect(dragToFillHandle).toBeVisible();
|
||||
|
||||
await pressEscape(page);
|
||||
|
||||
await expect(dragToFillHandle).toBeHidden();
|
||||
});
|
||||
|
||||
test('should not show in multi (row or column) selection', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
|
||||
const dragToFillHandle = page.locator('.drag-to-fill');
|
||||
|
||||
await expect(dragToFillHandle).toBeVisible();
|
||||
|
||||
await pressArrowUpWithShiftKey(page);
|
||||
|
||||
await expect(dragToFillHandle).toBeHidden();
|
||||
await pressArrowUp(page);
|
||||
|
||||
await expect(dragToFillHandle).toBeVisible();
|
||||
});
|
||||
|
||||
test('should fill columns with data', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, 'thing', true);
|
||||
await pressEscape(page);
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressBackspace(page);
|
||||
await type(page, 'aaa');
|
||||
await pressEnter(page);
|
||||
await pressEnter(page);
|
||||
|
||||
await pressEscape(page);
|
||||
await pressArrowUp(page);
|
||||
|
||||
const cells = page.locator('affine-database-multi-select-cell');
|
||||
|
||||
expect(await cells.nth(0).innerText()).toBe('thing');
|
||||
expect(await cells.nth(1).innerText()).toBe('aaa');
|
||||
|
||||
const dragToFillHandle = page.locator('.drag-to-fill');
|
||||
|
||||
await expect(dragToFillHandle).toBeVisible();
|
||||
|
||||
const bbox = await getBoundingBox(dragToFillHandle);
|
||||
|
||||
if (!bbox) throw new Error('Expected a bounding box');
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: bbox.x + bbox.width / 2, y: bbox.y + bbox.height / 2 },
|
||||
{ x: bbox.x, y: bbox.y + 200 }
|
||||
);
|
||||
|
||||
expect(await cells.nth(0).innerText()).toBe('thing');
|
||||
expect(await cells.nth(1).innerText()).toBe('thing');
|
||||
});
|
||||
});
|
||||
670
blocksuite/tests-legacy/database/database.spec.ts
Normal file
670
blocksuite/tests-legacy/database/database.spec.ts
Normal file
@@ -0,0 +1,670 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
focusDatabaseTitle,
|
||||
getBoundingBox,
|
||||
initDatabaseDynamicRowWithData,
|
||||
initDatabaseRowWithData,
|
||||
initEmptyDatabaseState,
|
||||
pressArrowLeft,
|
||||
pressArrowRight,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
pressShiftEnter,
|
||||
redoByKeyboard,
|
||||
selectAllByKeyboard,
|
||||
setInlineRangeInInlineEditor,
|
||||
switchReadonly,
|
||||
type,
|
||||
undoByClick,
|
||||
undoByKeyboard,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockProps,
|
||||
assertInlineEditorDeltas,
|
||||
assertRowCount,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import { getFormatBar } from '../utils/query.js';
|
||||
import {
|
||||
assertColumnWidth,
|
||||
assertDatabaseCellRichTexts,
|
||||
assertDatabaseSearching,
|
||||
assertDatabaseTitleText,
|
||||
blurDatabaseSearch,
|
||||
clickColumnType,
|
||||
clickDatabaseOutside,
|
||||
focusDatabaseHeader,
|
||||
focusDatabaseSearch,
|
||||
getDatabaseBodyCell,
|
||||
getDatabaseHeaderColumn,
|
||||
getFirstColumnCell,
|
||||
initDatabaseColumn,
|
||||
switchColumnType,
|
||||
} from './actions.js';
|
||||
|
||||
test('edit database block title and create new rows', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
const locator = page.locator('affine-database');
|
||||
await expect(locator).toBeVisible();
|
||||
const dbTitle = 'Database 1';
|
||||
await assertBlockProps(page, '2', {
|
||||
title: dbTitle,
|
||||
});
|
||||
await focusDatabaseTitle(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await pressBackspace(page);
|
||||
|
||||
const expected = 'hello';
|
||||
await type(page, expected);
|
||||
await assertBlockProps(page, '2', {
|
||||
title: 'hello',
|
||||
});
|
||||
await undoByClick(page);
|
||||
await assertBlockProps(page, '2', {
|
||||
title: 'Database 1',
|
||||
});
|
||||
await initDatabaseRowWithData(page, '');
|
||||
await initDatabaseRowWithData(page, '');
|
||||
await assertRowCount(page, 2);
|
||||
await waitNextFrame(page, 100);
|
||||
await pressEscape(page);
|
||||
await undoByClick(page);
|
||||
await undoByClick(page);
|
||||
await assertRowCount(page, 0);
|
||||
});
|
||||
|
||||
test('edit column title', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, '1');
|
||||
|
||||
// first added column
|
||||
const { column } = await getDatabaseHeaderColumn(page, 1);
|
||||
expect(await column.innerText()).toBe('1');
|
||||
|
||||
await undoByClick(page);
|
||||
expect(await column.innerText()).toBe('Column 1');
|
||||
});
|
||||
|
||||
test('should modify the value when the input loses focus', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await switchColumnType(page, 'Number');
|
||||
await initDatabaseDynamicRowWithData(page, '1', true);
|
||||
|
||||
await clickDatabaseOutside(page);
|
||||
const cell = getFirstColumnCell(page, 'number');
|
||||
const text = await cell.textContent();
|
||||
expect(text?.trim()).toBe('1');
|
||||
});
|
||||
|
||||
test('should rich-text column support soft enter', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await switchColumnType(page, 'Text');
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
|
||||
const cell = getFirstColumnCell(page, 'affine-database-rich-text');
|
||||
await cell.click();
|
||||
await pressArrowLeft(page);
|
||||
await pressEnter(page);
|
||||
await assertDatabaseCellRichTexts(page, { text: '123' });
|
||||
|
||||
await cell.click();
|
||||
await pressArrowRight(page);
|
||||
await pressArrowLeft(page);
|
||||
await pressShiftEnter(page);
|
||||
await pressEnter(page);
|
||||
await assertDatabaseCellRichTexts(page, { text: '12\n3' });
|
||||
});
|
||||
|
||||
test('should the multi-select mode work correctly', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '1', true);
|
||||
await pressEscape(page);
|
||||
await initDatabaseDynamicRowWithData(page, '2');
|
||||
await pressEscape(page);
|
||||
const cell = getFirstColumnCell(page, 'select-selected');
|
||||
expect(await cell.count()).toBe(2);
|
||||
expect(await cell.nth(0).innerText()).toBe('1');
|
||||
expect(await cell.nth(1).innerText()).toBe('2');
|
||||
});
|
||||
|
||||
test('should database search work', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseRowWithData(page, 'text1');
|
||||
await initDatabaseDynamicRowWithData(page, '123', false);
|
||||
await pressEscape(page);
|
||||
await initDatabaseRowWithData(page, 'text2');
|
||||
await initDatabaseDynamicRowWithData(page, 'a', false);
|
||||
await pressEscape(page);
|
||||
await initDatabaseRowWithData(page, 'text3');
|
||||
await initDatabaseDynamicRowWithData(page, '26', false);
|
||||
await pressEscape(page);
|
||||
// search for '2'
|
||||
await focusDatabaseSearch(page);
|
||||
await type(page, '2');
|
||||
const rows = page.locator('.affine-database-block-row');
|
||||
expect(await rows.count()).toBe(3);
|
||||
|
||||
// search for '23'
|
||||
await type(page, '3');
|
||||
expect(await rows.count()).toBe(1);
|
||||
|
||||
const cell = page.locator('.select-selected');
|
||||
expect(await cell.innerText()).toBe('123');
|
||||
|
||||
// clear search input
|
||||
const closeIcon = page.locator('.close-icon');
|
||||
await closeIcon.click();
|
||||
expect(await rows.count()).toBe(3);
|
||||
});
|
||||
|
||||
test('should database search input displayed correctly', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await focusDatabaseSearch(page);
|
||||
await blurDatabaseSearch(page);
|
||||
await assertDatabaseSearching(page, false);
|
||||
|
||||
await focusDatabaseSearch(page);
|
||||
await type(page, '2');
|
||||
await blurDatabaseSearch(page);
|
||||
await assertDatabaseSearching(page, true);
|
||||
|
||||
await focusDatabaseSearch(page);
|
||||
await pressBackspace(page);
|
||||
await blurDatabaseSearch(page);
|
||||
await assertDatabaseSearching(page, false);
|
||||
|
||||
await focusDatabaseSearch(page);
|
||||
await type(page, '2');
|
||||
const closeIcon = page.locator('.close-icon');
|
||||
await closeIcon.click();
|
||||
await blurDatabaseSearch(page);
|
||||
await assertDatabaseSearching(page, false);
|
||||
|
||||
await focusDatabaseSearch(page);
|
||||
await type(page, '2');
|
||||
await pressEscape(page);
|
||||
await blurDatabaseSearch(page);
|
||||
await assertDatabaseSearching(page, false);
|
||||
});
|
||||
|
||||
test('should database title and rich-text support undo/redo', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await switchColumnType(page, 'Text');
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await undoByKeyboard(page);
|
||||
await assertDatabaseCellRichTexts(page, { text: '' });
|
||||
await pressEscape(page);
|
||||
await redoByKeyboard(page);
|
||||
await assertDatabaseCellRichTexts(page, { text: '123' });
|
||||
|
||||
await focusDatabaseTitle(page);
|
||||
await type(page, 'abc');
|
||||
await assertDatabaseTitleText(page, 'Database 1abc');
|
||||
await undoByKeyboard(page);
|
||||
await assertDatabaseTitleText(page, 'Database 1');
|
||||
await redoByKeyboard(page);
|
||||
await assertDatabaseTitleText(page, 'Database 1abc');
|
||||
});
|
||||
|
||||
test('should support drag to change column width', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
const headerColumns = page.locator('.affine-database-column');
|
||||
const titleColumn = headerColumns.nth(0);
|
||||
const normalColumn = headerColumns.nth(1);
|
||||
|
||||
const dragDistance = 100;
|
||||
const titleColumnWidth = 260;
|
||||
const normalColumnWidth = 180;
|
||||
|
||||
await assertColumnWidth(titleColumn, titleColumnWidth - 1);
|
||||
const box = await assertColumnWidth(normalColumn, normalColumnWidth - 1);
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: box.x, y: box.y },
|
||||
{ x: box.x + dragDistance, y: box.y },
|
||||
{
|
||||
steps: 50,
|
||||
beforeMouseUp: async () => {
|
||||
await waitNextFrame(page);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await assertColumnWidth(titleColumn, titleColumnWidth + dragDistance);
|
||||
await assertColumnWidth(normalColumn, normalColumnWidth - 1);
|
||||
|
||||
await undoByClick(page);
|
||||
await assertColumnWidth(titleColumn, titleColumnWidth - 1);
|
||||
await assertColumnWidth(normalColumn, normalColumnWidth - 1);
|
||||
});
|
||||
|
||||
test('should display the add column button on the right side of database correctly', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
const normalColumn = page.locator('.affine-database-column').nth(1);
|
||||
|
||||
const addColumnBtn = page.locator('.header-add-column-button');
|
||||
|
||||
const box = await getBoundingBox(normalColumn);
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: box.x, y: box.y },
|
||||
{ x: box.x + 400, y: box.y },
|
||||
{
|
||||
steps: 50,
|
||||
beforeMouseUp: async () => {
|
||||
await waitNextFrame(page);
|
||||
},
|
||||
}
|
||||
);
|
||||
await focusDatabaseHeader(page);
|
||||
await expect(addColumnBtn).toBeVisible();
|
||||
});
|
||||
|
||||
test('should support drag and drop to move columns', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page, 'column1');
|
||||
await initDatabaseColumn(page, 'column2');
|
||||
await initDatabaseColumn(page, 'column3');
|
||||
|
||||
const column1 = await focusDatabaseHeader(page, 1);
|
||||
const moveIcon = column1.locator('.affine-database-column-move');
|
||||
const moveIconBox = await getBoundingBox(moveIcon);
|
||||
const x = moveIconBox.x + moveIconBox.width / 2;
|
||||
const y = moveIconBox.y + moveIconBox.height / 2;
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x, y },
|
||||
{ x: x + 100, y },
|
||||
{
|
||||
steps: 50,
|
||||
beforeMouseUp: async () => {
|
||||
await waitNextFrame(page);
|
||||
const indicator = page.locator('.vertical-indicator').first();
|
||||
await expect(indicator).toBeVisible();
|
||||
|
||||
const { box } = await getDatabaseHeaderColumn(page, 2);
|
||||
const indicatorBox = await getBoundingBox(indicator);
|
||||
expect(box.x + box.width - indicatorBox.x < 10).toBe(true);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const { text } = await getDatabaseHeaderColumn(page, 2);
|
||||
expect(text).toBe('column1');
|
||||
});
|
||||
|
||||
test('should title column support quick renaming', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, 'a', true);
|
||||
await pressEscape(page);
|
||||
await focusDatabaseHeader(page, 1);
|
||||
const { textElement } = await getDatabaseHeaderColumn(page, 1);
|
||||
await textElement.click();
|
||||
await waitNextFrame(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await type(page, '123');
|
||||
await pressEnter(page);
|
||||
expect(await textElement.innerText()).toBe('123');
|
||||
|
||||
await undoByClick(page);
|
||||
expect(await textElement.innerText()).toBe('Column 1');
|
||||
await textElement.click();
|
||||
await waitNextFrame(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await type(page, '123');
|
||||
await pressEnter(page);
|
||||
expect(await textElement.innerText()).toBe('123');
|
||||
});
|
||||
|
||||
test('should title column support quick changing of column type', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, 'a', true);
|
||||
await pressEscape(page);
|
||||
await initDatabaseDynamicRowWithData(page, 'b');
|
||||
await pressEscape(page);
|
||||
await focusDatabaseHeader(page, 1);
|
||||
const { typeIcon } = await getDatabaseHeaderColumn(page, 1);
|
||||
await typeIcon.click();
|
||||
await waitNextFrame(page);
|
||||
await clickColumnType(page, 'Select');
|
||||
const cell = getFirstColumnCell(page, 'select-selected');
|
||||
expect(await cell.count()).toBe(1);
|
||||
});
|
||||
|
||||
test('database format-bar in header and text column', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await switchColumnType(page, 'Text');
|
||||
await initDatabaseDynamicRowWithData(page, 'column', true);
|
||||
await pressArrowLeft(page);
|
||||
await pressEnter(page);
|
||||
await type(page, 'header');
|
||||
// Title | Column1
|
||||
// ----------------
|
||||
// header | column
|
||||
|
||||
const formatBar = getFormatBar(page);
|
||||
await setInlineRangeInInlineEditor(page, { index: 1, length: 4 }, 1);
|
||||
expect(await formatBar.formatBar.isVisible()).toBe(true);
|
||||
// Title | Column1
|
||||
// ----------------
|
||||
// h|eade|r | column
|
||||
|
||||
await assertInlineEditorDeltas(
|
||||
page,
|
||||
[
|
||||
{
|
||||
insert: 'header',
|
||||
},
|
||||
],
|
||||
1
|
||||
);
|
||||
await formatBar.boldBtn.click();
|
||||
await assertInlineEditorDeltas(
|
||||
page,
|
||||
[
|
||||
{
|
||||
insert: 'h',
|
||||
},
|
||||
{
|
||||
insert: 'eade',
|
||||
attributes: {
|
||||
bold: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'r',
|
||||
},
|
||||
],
|
||||
1
|
||||
);
|
||||
|
||||
await pressEscape(page);
|
||||
await pressArrowRight(page);
|
||||
await pressEnter(page);
|
||||
await setInlineRangeInInlineEditor(page, { index: 2, length: 2 }, 2);
|
||||
expect(await formatBar.formatBar.isVisible()).toBe(true);
|
||||
// Title | Column1
|
||||
// ----------------
|
||||
// header | co|lu|mn
|
||||
|
||||
await assertInlineEditorDeltas(
|
||||
page,
|
||||
[
|
||||
{
|
||||
insert: 'column',
|
||||
},
|
||||
],
|
||||
2
|
||||
);
|
||||
await formatBar.boldBtn.click();
|
||||
await assertInlineEditorDeltas(
|
||||
page,
|
||||
[
|
||||
{
|
||||
insert: 'co',
|
||||
},
|
||||
{
|
||||
insert: 'lu',
|
||||
attributes: {
|
||||
bold: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
insert: 'mn',
|
||||
},
|
||||
],
|
||||
2
|
||||
);
|
||||
});
|
||||
|
||||
test.describe('readonly mode', () => {
|
||||
test('database title should not be edited in readonly mode', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
const locator = page.locator('affine-database');
|
||||
await expect(locator).toBeVisible();
|
||||
|
||||
const dbTitle = 'Database 1';
|
||||
await assertBlockProps(page, '2', {
|
||||
title: dbTitle,
|
||||
});
|
||||
|
||||
await focusDatabaseTitle(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await pressBackspace(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
await assertBlockProps(page, '2', {
|
||||
title: 'hello',
|
||||
});
|
||||
|
||||
await switchReadonly(page);
|
||||
|
||||
await type(page, ' world');
|
||||
await assertBlockProps(page, '2', {
|
||||
title: 'hello',
|
||||
});
|
||||
|
||||
await pressBackspace(page, 'hello world'.length);
|
||||
await assertBlockProps(page, '2', {
|
||||
title: 'hello',
|
||||
});
|
||||
});
|
||||
|
||||
test('should rich-text not be edited in readonly mode', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await switchColumnType(page, 'Text');
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
|
||||
const cell = getFirstColumnCell(page, 'affine-database-rich-text');
|
||||
await cell.click();
|
||||
await type(page, '123');
|
||||
await assertDatabaseCellRichTexts(page, { text: '123' });
|
||||
|
||||
await switchReadonly(page);
|
||||
await pressBackspace(page);
|
||||
await type(page, '789');
|
||||
await assertDatabaseCellRichTexts(page, { text: '123' });
|
||||
});
|
||||
|
||||
test('should hide edit widget after switch to readonly mode', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await switchColumnType(page, 'Text');
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
|
||||
const database = page.locator('affine-database');
|
||||
await expect(database).toBeVisible();
|
||||
|
||||
const databaseMenu = database.locator('.database-ops');
|
||||
await expect(databaseMenu).toBeVisible();
|
||||
|
||||
const addViewButton = database.getByTestId('database-add-view-button');
|
||||
await expect(addViewButton).toBeVisible();
|
||||
|
||||
const titleHeader = page.locator('affine-database-header-column').filter({
|
||||
hasText: 'Title',
|
||||
});
|
||||
await titleHeader.hover();
|
||||
const columnDragBar = titleHeader.locator('.control-r');
|
||||
await expect(columnDragBar).toBeVisible();
|
||||
|
||||
const filter = database.locator('data-view-header-tools-filter');
|
||||
const search = database.locator('data-view-header-tools-search');
|
||||
const options = database.locator('data-view-header-tools-view-options');
|
||||
const headerAddRow = database.locator('data-view-header-tools-add-row');
|
||||
|
||||
await database.hover();
|
||||
await expect(filter).toBeVisible();
|
||||
await expect(search).toBeVisible();
|
||||
await expect(options).toBeVisible();
|
||||
await expect(headerAddRow).toBeVisible();
|
||||
|
||||
const row = database.locator('data-view-table-row');
|
||||
const rowOptions = row.locator('.row-op');
|
||||
const rowDragBar = row.locator('.data-view-table-view-drag-handler>div');
|
||||
await row.hover();
|
||||
await expect(rowOptions).toHaveCount(2);
|
||||
await expect(rowOptions.nth(0)).toBeVisible();
|
||||
await expect(rowOptions.nth(1)).toBeVisible();
|
||||
await expect(rowDragBar).toBeVisible();
|
||||
|
||||
const addRow = database.locator('.data-view-table-group-add-row');
|
||||
await expect(addRow).toBeVisible();
|
||||
|
||||
// Readonly Mode
|
||||
{
|
||||
await switchReadonly(page);
|
||||
await expect(databaseMenu).toBeHidden();
|
||||
await expect(addViewButton).toBeHidden();
|
||||
|
||||
await titleHeader.hover();
|
||||
await expect(columnDragBar).toBeHidden();
|
||||
|
||||
await database.hover();
|
||||
await expect(filter).toBeHidden();
|
||||
await expect(search).toBeVisible(); // Note the search should not be hidden
|
||||
await expect(options).toBeHidden();
|
||||
await expect(headerAddRow).toBeHidden();
|
||||
|
||||
await row.hover();
|
||||
await expect(rowOptions.nth(0)).toBeHidden();
|
||||
await expect(rowOptions.nth(1)).toBeHidden();
|
||||
await expect(rowDragBar).toBeHidden();
|
||||
|
||||
await expect(addRow).toBeHidden();
|
||||
}
|
||||
});
|
||||
|
||||
test('should hide focus border after switch to readonly mode', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await switchColumnType(page, 'Text');
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
|
||||
const database = page.locator('affine-database');
|
||||
await expect(database).toBeVisible();
|
||||
|
||||
const cell = getFirstColumnCell(page, 'affine-database-rich-text');
|
||||
await cell.click();
|
||||
|
||||
const focusBorder = database.locator(
|
||||
'data-view-table-selection .database-focus'
|
||||
);
|
||||
await expect(focusBorder).toBeVisible();
|
||||
|
||||
await switchReadonly(page);
|
||||
await expect(focusBorder).toBeHidden();
|
||||
});
|
||||
|
||||
test('should hide selection after switch to readonly mode', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await switchColumnType(page, 'Text');
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
|
||||
const database = page.locator('affine-database');
|
||||
await expect(database).toBeVisible();
|
||||
|
||||
const startCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 0,
|
||||
columnIndex: 0,
|
||||
});
|
||||
const endCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 0,
|
||||
columnIndex: 1,
|
||||
});
|
||||
|
||||
const startBox = await getBoundingBox(startCell);
|
||||
const endBox = await getBoundingBox(endCell);
|
||||
|
||||
const startX = startBox.x + startBox.width / 2;
|
||||
const startY = startBox.y + startBox.height / 2;
|
||||
const endX = endBox.x + endBox.width / 2;
|
||||
const endY = endBox.y + endBox.height / 2;
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: startX, y: startY },
|
||||
{ x: endX, y: endY }
|
||||
);
|
||||
|
||||
const selection = database.locator(
|
||||
'data-view-table-selection .database-selection'
|
||||
);
|
||||
|
||||
await expect(selection).toBeVisible();
|
||||
|
||||
await switchReadonly(page);
|
||||
await expect(selection).toBeHidden();
|
||||
});
|
||||
});
|
||||
567
blocksuite/tests-legacy/database/selection.spec.ts
Normal file
567
blocksuite/tests-legacy/database/selection.spec.ts
Normal file
@@ -0,0 +1,567 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { dragBetweenCoords } from '../utils/actions/drag.js';
|
||||
import { shiftClick } from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
pressArrowDown,
|
||||
pressArrowDownWithShiftKey,
|
||||
pressArrowLeft,
|
||||
pressArrowRight,
|
||||
pressArrowUp,
|
||||
pressArrowUpWithShiftKey,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
type,
|
||||
} from '../utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
getBoundingBox,
|
||||
initDatabaseDynamicRowWithData,
|
||||
initDatabaseRowWithData,
|
||||
initEmptyDatabaseState,
|
||||
initKanbanViewState,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import {
|
||||
assertCellsSelection,
|
||||
assertDatabaseTitleColumnText,
|
||||
assertKanbanCardHeaderText,
|
||||
assertKanbanCardSelected,
|
||||
assertKanbanCellSelected,
|
||||
assertRowsSelection,
|
||||
clickKanbanCardHeader,
|
||||
focusKanbanCardHeader,
|
||||
getDatabaseBodyCell,
|
||||
getKanbanCard,
|
||||
initDatabaseColumn,
|
||||
switchColumnType,
|
||||
} from './actions.js';
|
||||
|
||||
test.describe('focus', () => {
|
||||
test('should support move focus by arrow key', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await pressEscape(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
});
|
||||
|
||||
test('should support multi row selection', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
await switchColumnType(page, 'Number');
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
|
||||
const selectColumn = getDatabaseBodyCell(page, {
|
||||
rowIndex: 1,
|
||||
columnIndex: 1,
|
||||
});
|
||||
|
||||
const endBox = await getBoundingBox(selectColumn);
|
||||
const endX = endBox.x + endBox.width / 2;
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: endX, y: endBox.y },
|
||||
{ x: endX, y: endBox.y + endBox.height }
|
||||
);
|
||||
await pressEscape(page);
|
||||
await assertRowsSelection(page, [0, 1]);
|
||||
});
|
||||
|
||||
test('should support row selection with dynamic height', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123123', true);
|
||||
await type(page, '456456');
|
||||
await pressEnter(page);
|
||||
await type(page, 'abcabc');
|
||||
await pressEnter(page);
|
||||
await type(page, 'defdef');
|
||||
await pressEnter(page);
|
||||
await pressEscape(page);
|
||||
|
||||
await pressEscape(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('row-level selection', () => {
|
||||
test('should support title selection', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseRowWithData(page, 'title');
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await assertCellsSelection(page, {
|
||||
start: [0, 0],
|
||||
});
|
||||
|
||||
await pressEscape(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
});
|
||||
|
||||
test('should support pressing esc to trigger row selection', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await pressEscape(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
});
|
||||
|
||||
test('should support multi row selection', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
await switchColumnType(page, 'Number');
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
|
||||
const selectColumn = getDatabaseBodyCell(page, {
|
||||
rowIndex: 1,
|
||||
columnIndex: 1,
|
||||
});
|
||||
|
||||
const endBox = await getBoundingBox(selectColumn);
|
||||
const endX = endBox.x + endBox.width / 2;
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: endX, y: endBox.y },
|
||||
{ x: endX, y: endBox.y + endBox.height }
|
||||
);
|
||||
await pressEscape(page);
|
||||
await assertRowsSelection(page, [0, 1]);
|
||||
});
|
||||
|
||||
test('should support row selection with dynamic height', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123123', true);
|
||||
await type(page, '456456');
|
||||
await pressEnter(page);
|
||||
await type(page, 'abcabc');
|
||||
await pressEnter(page);
|
||||
await type(page, 'defdef');
|
||||
await pressEnter(page);
|
||||
await pressEscape(page);
|
||||
|
||||
await pressEscape(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
});
|
||||
|
||||
test('move row selection with (up | down)', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
|
||||
// add two rows
|
||||
await initDatabaseDynamicRowWithData(page, '123123', true);
|
||||
await pressEscape(page);
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, '123123', true);
|
||||
await pressEscape(page);
|
||||
|
||||
await pressEscape(page); // switch to row selection
|
||||
|
||||
await assertRowsSelection(page, [1, 1]);
|
||||
|
||||
await pressArrowUp(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
|
||||
// should not allow under selection
|
||||
await pressArrowUp(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
|
||||
await pressArrowDown(page);
|
||||
await assertRowsSelection(page, [1, 1]);
|
||||
|
||||
// should not allow over selection
|
||||
await pressArrowDown(page);
|
||||
await assertRowsSelection(page, [1, 1]);
|
||||
});
|
||||
|
||||
test('increment decrement row selection with shift+(up | down)', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
|
||||
// add two rows
|
||||
await initDatabaseDynamicRowWithData(page, '123123', true);
|
||||
await pressEscape(page);
|
||||
|
||||
await initDatabaseDynamicRowWithData(page, '123123', true);
|
||||
await pressEscape(page);
|
||||
|
||||
await pressEscape(page); // switch to row selection
|
||||
|
||||
await pressArrowUpWithShiftKey(page);
|
||||
await assertRowsSelection(page, [0, 1]);
|
||||
|
||||
await pressArrowDownWithShiftKey(page);
|
||||
await assertRowsSelection(page, [1, 1]); // should decrement back
|
||||
|
||||
await pressArrowUp(page); // go to first row
|
||||
|
||||
await pressArrowDownWithShiftKey(page);
|
||||
await assertRowsSelection(page, [0, 1]);
|
||||
|
||||
await pressArrowUpWithShiftKey(page);
|
||||
await assertRowsSelection(page, [0, 0]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('cell-level selection', () => {
|
||||
test('should support multi cell selection', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseDynamicRowWithData(page, '', true);
|
||||
await pressEscape(page);
|
||||
await switchColumnType(page, 'Number');
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
|
||||
const startCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 0,
|
||||
columnIndex: 0,
|
||||
});
|
||||
const endCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 1,
|
||||
columnIndex: 1,
|
||||
});
|
||||
|
||||
const startBox = await getBoundingBox(startCell);
|
||||
const endBox = await getBoundingBox(endCell);
|
||||
|
||||
const startX = startBox.x + startBox.width / 2;
|
||||
const startY = startBox.y + startBox.height / 2;
|
||||
const endX = endBox.x + endBox.width / 2;
|
||||
const endY = endBox.y + endBox.height / 2;
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: startX, y: startY },
|
||||
{ x: endX, y: endY }
|
||||
);
|
||||
|
||||
await assertCellsSelection(page, {
|
||||
start: [0, 0],
|
||||
end: [1, 1],
|
||||
});
|
||||
});
|
||||
|
||||
test("should support backspace key to delete cell's content", async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
await initDatabaseColumn(page);
|
||||
await initDatabaseRowWithData(page, 'row1');
|
||||
await initDatabaseDynamicRowWithData(page, 'abc', false);
|
||||
await pressEscape(page);
|
||||
await initDatabaseRowWithData(page, 'row2');
|
||||
await initDatabaseDynamicRowWithData(page, '123', false);
|
||||
await pressEscape(page);
|
||||
|
||||
const startCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 0,
|
||||
columnIndex: 0,
|
||||
});
|
||||
const endCell = getDatabaseBodyCell(page, {
|
||||
rowIndex: 1,
|
||||
columnIndex: 1,
|
||||
});
|
||||
|
||||
const startBox = await getBoundingBox(startCell);
|
||||
const endBox = await getBoundingBox(endCell);
|
||||
|
||||
const startX = startBox.x + startBox.width / 2;
|
||||
const startY = startBox.y + startBox.height / 2;
|
||||
const endX = endBox.x + endBox.width / 2;
|
||||
const endY = endBox.y + endBox.height / 2;
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: startX, y: startY },
|
||||
{ x: endX, y: endY }
|
||||
);
|
||||
|
||||
await pressBackspace(page);
|
||||
await assertDatabaseTitleColumnText(page, '', 0);
|
||||
await assertDatabaseTitleColumnText(page, '', 1);
|
||||
const selectCell1 = getDatabaseBodyCell(page, {
|
||||
rowIndex: 0,
|
||||
columnIndex: 1,
|
||||
});
|
||||
expect(await selectCell1.innerText()).toBe('');
|
||||
const selectCell2 = getDatabaseBodyCell(page, {
|
||||
rowIndex: 1,
|
||||
columnIndex: 1,
|
||||
});
|
||||
expect(await selectCell2.innerText()).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('kanban view selection', () => {
|
||||
test("should support move cell's focus by arrow key(up&down) within a card", async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initKanbanViewState(page, {
|
||||
rows: ['row1'],
|
||||
columns: [
|
||||
{
|
||||
type: 'number',
|
||||
value: [1],
|
||||
},
|
||||
{
|
||||
type: 'rich-text',
|
||||
value: ['text'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await focusKanbanCardHeader(page);
|
||||
await assertKanbanCellSelected(page, {
|
||||
// group by `number` column, the first(groupIndex: 0) group is `Ungroups`
|
||||
groupIndex: 1,
|
||||
cardIndex: 0,
|
||||
cellIndex: 0,
|
||||
});
|
||||
|
||||
await pressArrowDown(page, 3);
|
||||
await assertKanbanCellSelected(page, {
|
||||
groupIndex: 1,
|
||||
cardIndex: 0,
|
||||
cellIndex: 0,
|
||||
});
|
||||
|
||||
await pressArrowUp(page);
|
||||
await assertKanbanCellSelected(page, {
|
||||
groupIndex: 1,
|
||||
cardIndex: 0,
|
||||
cellIndex: 2,
|
||||
});
|
||||
});
|
||||
|
||||
test("should support move cell's focus by arrow key(up&down) within multi cards", async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initKanbanViewState(page, {
|
||||
rows: ['row1', 'row2'],
|
||||
columns: [
|
||||
{
|
||||
type: 'number',
|
||||
value: [1, 2],
|
||||
},
|
||||
{
|
||||
type: 'rich-text',
|
||||
value: ['text'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await focusKanbanCardHeader(page);
|
||||
await pressArrowUp(page);
|
||||
await assertKanbanCellSelected(page, {
|
||||
groupIndex: 1,
|
||||
cardIndex: 1,
|
||||
cellIndex: 2,
|
||||
});
|
||||
|
||||
await pressArrowDown(page);
|
||||
await assertKanbanCellSelected(page, {
|
||||
groupIndex: 1,
|
||||
cardIndex: 0,
|
||||
cellIndex: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test("should support move cell's focus by arrow key(left&right)", async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initKanbanViewState(page, {
|
||||
rows: ['row1', 'row2', 'row3'],
|
||||
columns: [
|
||||
{
|
||||
type: 'number',
|
||||
value: [undefined, 1, 10],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await focusKanbanCardHeader(page);
|
||||
|
||||
await pressArrowRight(page, 3);
|
||||
await assertKanbanCellSelected(page, {
|
||||
groupIndex: 0,
|
||||
cardIndex: 0,
|
||||
cellIndex: 0,
|
||||
});
|
||||
|
||||
await pressArrowLeft(page);
|
||||
await assertKanbanCellSelected(page, {
|
||||
groupIndex: 2,
|
||||
cardIndex: 0,
|
||||
cellIndex: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test("should support move card's focus by arrow key(up&down)", async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initKanbanViewState(page, {
|
||||
rows: ['row1', 'row2', 'row3'],
|
||||
columns: [
|
||||
{
|
||||
type: 'number',
|
||||
value: [undefined, undefined, undefined],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await focusKanbanCardHeader(page);
|
||||
await pressEscape(page);
|
||||
await pressEscape(page);
|
||||
await assertKanbanCardSelected(page, {
|
||||
groupIndex: 0,
|
||||
cardIndex: 0,
|
||||
});
|
||||
|
||||
await pressArrowDown(page, 3);
|
||||
await assertKanbanCardSelected(page, {
|
||||
groupIndex: 0,
|
||||
cardIndex: 0,
|
||||
});
|
||||
|
||||
await pressArrowUp(page);
|
||||
await assertKanbanCardSelected(page, {
|
||||
groupIndex: 0,
|
||||
cardIndex: 2,
|
||||
});
|
||||
});
|
||||
|
||||
test("should support move card's focus by arrow key(left&right)", async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initKanbanViewState(page, {
|
||||
rows: ['row1', 'row2', 'row3'],
|
||||
columns: [
|
||||
{
|
||||
type: 'number',
|
||||
value: [undefined, 1, 10],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await focusKanbanCardHeader(page);
|
||||
await pressEscape(page);
|
||||
await pressEscape(page);
|
||||
|
||||
await pressArrowRight(page, 3);
|
||||
await assertKanbanCardSelected(page, {
|
||||
groupIndex: 0,
|
||||
cardIndex: 0,
|
||||
});
|
||||
|
||||
await pressArrowLeft(page);
|
||||
await assertKanbanCardSelected(page, {
|
||||
groupIndex: 2,
|
||||
cardIndex: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test('should support multi card selection', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initKanbanViewState(page, {
|
||||
rows: ['row1', 'row2'],
|
||||
columns: [
|
||||
{
|
||||
type: 'number',
|
||||
value: [undefined, 1],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await focusKanbanCardHeader(page);
|
||||
await pressEscape(page);
|
||||
await pressEscape(page);
|
||||
|
||||
const card = getKanbanCard(page, {
|
||||
groupIndex: 1,
|
||||
cardIndex: 0,
|
||||
});
|
||||
const box = await getBoundingBox(card);
|
||||
await shiftClick(page, {
|
||||
x: box.x + box.width / 2,
|
||||
y: box.y + box.height / 2,
|
||||
});
|
||||
|
||||
await assertKanbanCardSelected(page, {
|
||||
groupIndex: 0,
|
||||
cardIndex: 0,
|
||||
});
|
||||
await assertKanbanCardSelected(page, {
|
||||
groupIndex: 1,
|
||||
cardIndex: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test("should support move cursor in card's title by arrow key(left&right)", async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initKanbanViewState(page, {
|
||||
rows: ['row1'],
|
||||
columns: [
|
||||
{
|
||||
type: 'rich-text',
|
||||
value: ['text'],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await clickKanbanCardHeader(page);
|
||||
await type(page, 'abc');
|
||||
await pressArrowLeft(page, 2);
|
||||
await pressArrowRight(page);
|
||||
await pressBackspace(page);
|
||||
await pressEscape(page);
|
||||
|
||||
await assertKanbanCardHeaderText(page, 'row1ac');
|
||||
});
|
||||
});
|
||||
112
blocksuite/tests-legacy/database/sort.spec.ts
Normal file
112
blocksuite/tests-legacy/database/sort.spec.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { expect, type Locator } from '@playwright/test';
|
||||
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
initDatabaseDynamicRowWithData,
|
||||
initEmptyDatabaseState,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import { initDatabaseColumn, switchColumnType } from './actions.js';
|
||||
|
||||
test('database sort with multiple rules', async ({ page }) => {
|
||||
// Initialize database
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
|
||||
// Add test columns: Name (text) and Age (number)
|
||||
await initDatabaseColumn(page, 'Name');
|
||||
await switchColumnType(page, 'Text', 1);
|
||||
await initDatabaseColumn(page, 'Age');
|
||||
await switchColumnType(page, 'Number', 2);
|
||||
|
||||
// Add test data
|
||||
const testData = [
|
||||
{ name: 'Alice', age: '25' },
|
||||
{ name: 'Bob', age: '30' },
|
||||
{ name: 'Alice', age: '20' },
|
||||
{ name: 'Charlie', age: '25' },
|
||||
];
|
||||
|
||||
for (const data of testData) {
|
||||
await initDatabaseDynamicRowWithData(page, data.name, true, 0);
|
||||
await initDatabaseDynamicRowWithData(page, data.age, false, 1);
|
||||
}
|
||||
|
||||
// Open sort menu
|
||||
const sortButton = page.locator('data-view-header-tools-sort');
|
||||
await sortButton.click();
|
||||
|
||||
// Add first sort rule: Name ascending
|
||||
await page.locator('affine-menu').getByText('Name').click();
|
||||
await waitNextFrame(page);
|
||||
|
||||
// Add second sort rule: Age ascending
|
||||
await page.getByText('Add sort').click();
|
||||
await page.locator('affine-menu').getByText('Age').click();
|
||||
await waitNextFrame(page);
|
||||
|
||||
// Get all rows after sorting
|
||||
const rows = await page.locator('affine-database-row').all();
|
||||
const getCellText = async (row: Locator, index: number) => {
|
||||
const cell = row.locator('.cell').nth(index);
|
||||
return cell.innerText();
|
||||
};
|
||||
|
||||
// Verify sorting results
|
||||
// Should be sorted by Name first, then by Age
|
||||
const expectedOrder = [
|
||||
{ name: 'Alice', age: '20' },
|
||||
{ name: 'Alice', age: '25' },
|
||||
{ name: 'Bob', age: '30' },
|
||||
{ name: 'Charlie', age: '25' },
|
||||
];
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const name = await getCellText(rows[i], 1);
|
||||
const age = await getCellText(rows[i], 2);
|
||||
expect(name).toBe(expectedOrder[i].name);
|
||||
expect(age).toBe(expectedOrder[i].age);
|
||||
}
|
||||
|
||||
// Change sort order of Name to descending
|
||||
await page.locator('.sort-item').first().getByText('Ascending').click();
|
||||
await page.getByText('Descending').click();
|
||||
await waitNextFrame(page);
|
||||
|
||||
// Verify new sorting results
|
||||
const expectedOrderDesc = [
|
||||
{ name: 'Charlie', age: '25' },
|
||||
{ name: 'Bob', age: '30' },
|
||||
{ name: 'Alice', age: '20' },
|
||||
{ name: 'Alice', age: '25' },
|
||||
];
|
||||
|
||||
const rowsAfterDesc = await page.locator('affine-database-row').all();
|
||||
for (let i = 0; i < rowsAfterDesc.length; i++) {
|
||||
const name = await getCellText(rowsAfterDesc[i], 1);
|
||||
const age = await getCellText(rowsAfterDesc[i], 2);
|
||||
expect(name).toBe(expectedOrderDesc[i].name);
|
||||
expect(age).toBe(expectedOrderDesc[i].age);
|
||||
}
|
||||
|
||||
// Remove first sort rule
|
||||
await page.locator('.sort-item').first().getByRole('img').last().click();
|
||||
await waitNextFrame(page);
|
||||
|
||||
// Verify sorting now only by Age
|
||||
const expectedOrderAgeOnly = [
|
||||
{ name: 'Alice', age: '20' },
|
||||
{ name: 'Alice', age: '25' },
|
||||
{ name: 'Charlie', age: '25' },
|
||||
{ name: 'Bob', age: '30' },
|
||||
];
|
||||
|
||||
const rowsAfterRemove = await page.locator('affine-database-row').all();
|
||||
for (let i = 0; i < rowsAfterRemove.length; i++) {
|
||||
const name = await getCellText(rowsAfterRemove[i], 1);
|
||||
const age = await getCellText(rowsAfterRemove[i], 2);
|
||||
expect(name).toBe(expectedOrderAgeOnly[i].name);
|
||||
expect(age).toBe(expectedOrderAgeOnly[i].age);
|
||||
}
|
||||
});
|
||||
103
blocksuite/tests-legacy/database/statistics.spec.ts
Normal file
103
blocksuite/tests-legacy/database/statistics.spec.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { press } from '@inline/__tests__/utils.js';
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import { type } from '../utils/actions/index.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
getAddRow,
|
||||
initEmptyDatabaseState,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
import { changeColumnType, moveToCenterOf, pressKey } from './actions.js';
|
||||
|
||||
const addRow = async (page: Page, count: number = 1) => {
|
||||
await waitNextFrame(page);
|
||||
const addRow = getAddRow(page);
|
||||
for (let i = 0; i < count; i++) {
|
||||
await addRow.click();
|
||||
}
|
||||
await press(page, 'Escape');
|
||||
await waitNextFrame(page);
|
||||
};
|
||||
const insertRightColumn = async (page: Page, index = 0) => {
|
||||
await waitNextFrame(page);
|
||||
await page.locator('affine-database-header-column').nth(index).click();
|
||||
await waitNextFrame(page, 200);
|
||||
await pressKey(page, 'Escape');
|
||||
const menu = page.locator('.affine-menu-button', {
|
||||
hasText: new RegExp('Insert Right'),
|
||||
});
|
||||
await menu.click();
|
||||
await waitNextFrame(page, 200);
|
||||
await pressKey(page, 'Enter');
|
||||
};
|
||||
const menuSelect = async (page: Page, selectors: string[]) => {
|
||||
await waitNextFrame(page);
|
||||
for (const name of selectors) {
|
||||
const menu = page.locator('.affine-menu-button', {
|
||||
hasText: new RegExp(name),
|
||||
});
|
||||
await menu.click();
|
||||
}
|
||||
};
|
||||
test.describe('title', () => {
|
||||
test('empty count', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
await addRow(page, 3);
|
||||
const statCell = page.locator('affine-database-column-stats-cell').nth(0);
|
||||
await moveToCenterOf(page, statCell);
|
||||
await statCell.click();
|
||||
await menuSelect(page, ['Count', 'Count Empty']);
|
||||
const value = statCell.locator('.value');
|
||||
expect((await value.textContent())?.trim()).toBe('3');
|
||||
await page.locator('affine-database-cell-container').nth(0).click();
|
||||
await pressKey(page, 'Enter');
|
||||
await type(page, 'asd');
|
||||
await pressKey(page, 'Escape');
|
||||
expect((await value.textContent())?.trim()).toBe('2');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('rich-text', () => {
|
||||
test('empty count', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
await addRow(page, 3);
|
||||
await insertRightColumn(page);
|
||||
await changeColumnType(page, 1, 'text');
|
||||
const statCell = page.locator('affine-database-column-stats-cell').nth(1);
|
||||
await moveToCenterOf(page, statCell);
|
||||
await statCell.click();
|
||||
await menuSelect(page, ['Count', 'Count Empty']);
|
||||
const value = statCell.locator('.value');
|
||||
expect((await value.textContent())?.trim()).toBe('3');
|
||||
await page.locator('affine-database-cell-container').nth(1).click();
|
||||
await pressKey(page, 'Enter');
|
||||
await type(page, 'asd');
|
||||
await pressKey(page, 'Escape');
|
||||
expect((await value.textContent())?.trim()).toBe('2');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('select', () => {
|
||||
test('empty count', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
await addRow(page, 3);
|
||||
await insertRightColumn(page);
|
||||
await changeColumnType(page, 1, 'select');
|
||||
const statCell = page.locator('affine-database-column-stats-cell').nth(1);
|
||||
await moveToCenterOf(page, statCell);
|
||||
await statCell.click();
|
||||
await menuSelect(page, ['Count', 'Count Empty']);
|
||||
const value = statCell.locator('.value');
|
||||
expect((await value.textContent())?.trim()).toBe('3');
|
||||
await page.locator('affine-database-cell-container').nth(1).click();
|
||||
await pressKey(page, 'Enter');
|
||||
await type(page, 'select');
|
||||
await pressKey(page, 'Enter');
|
||||
expect((await value.textContent())?.trim()).toBe('2');
|
||||
});
|
||||
});
|
||||
19
blocksuite/tests-legacy/database/title.spec.ts
Normal file
19
blocksuite/tests-legacy/database/title.spec.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { press } from '@inline/__tests__/utils.js';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
initDatabaseDynamicRowWithData,
|
||||
initEmptyDatabaseState,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('title', () => {
|
||||
test('should able to link doc by press @', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyDatabaseState(page);
|
||||
await initDatabaseDynamicRowWithData(page, '123', true);
|
||||
await press(page, '@');
|
||||
await expect(page.locator('.linked-doc-popover')).toBeVisible();
|
||||
});
|
||||
});
|
||||
768
blocksuite/tests-legacy/drag.spec.ts
Normal file
768
blocksuite/tests-legacy/drag.spec.ts
Normal file
@@ -0,0 +1,768 @@
|
||||
import { BLOCK_CHILDREN_CONTAINER_PADDING_LEFT } from '@blocks/_common/consts.js';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
dragBetweenCoords,
|
||||
dragBetweenIndices,
|
||||
dragHandleFromBlockToBlockBottomById,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyParagraphState,
|
||||
initThreeLists,
|
||||
initThreeParagraphs,
|
||||
pressEnter,
|
||||
pressShiftTab,
|
||||
pressTab,
|
||||
type,
|
||||
} from './utils/actions/index.js';
|
||||
import {
|
||||
getBoundingClientRect,
|
||||
getEditorHostLocator,
|
||||
getPageSnapshot,
|
||||
initParagraphsByCount,
|
||||
} from './utils/actions/misc.js';
|
||||
import { assertRichTexts } from './utils/asserts.js';
|
||||
import { test } from './utils/playwright.js';
|
||||
|
||||
test('only have one drag handle in screen', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
const topLeft = await page.evaluate(() => {
|
||||
const paragraph = document.querySelector('[data-block-id="2"]');
|
||||
const box = paragraph?.getBoundingClientRect();
|
||||
if (!box) {
|
||||
throw new Error();
|
||||
}
|
||||
return { x: box.left, y: box.top + 2 };
|
||||
}, []);
|
||||
|
||||
const bottomRight = await page.evaluate(() => {
|
||||
const paragraph = document.querySelector('[data-block-id="4"]');
|
||||
const box = paragraph?.getBoundingClientRect();
|
||||
if (!box) {
|
||||
throw new Error();
|
||||
}
|
||||
return { x: box.right, y: box.bottom - 2 };
|
||||
}, []);
|
||||
|
||||
await page.mouse.move(topLeft.x, topLeft.y);
|
||||
const length1 = await page.evaluate(() => {
|
||||
const handles = document.querySelectorAll('affine-drag-handle-widget');
|
||||
return handles.length;
|
||||
}, []);
|
||||
expect(length1).toBe(1);
|
||||
await page.mouse.move(bottomRight.x, bottomRight.y);
|
||||
const length2 = await page.evaluate(() => {
|
||||
const handles = document.querySelectorAll('affine-drag-handle-widget');
|
||||
return handles.length;
|
||||
}, []);
|
||||
expect(length2).toBe(1);
|
||||
});
|
||||
|
||||
test('move drag handle in paragraphs', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
await dragHandleFromBlockToBlockBottomById(page, '2', '4');
|
||||
await expect(page.locator('.affine-drag-indicator')).toBeHidden();
|
||||
await assertRichTexts(page, ['456', '789', '123']);
|
||||
});
|
||||
|
||||
test('move drag handle in list', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeLists(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
await dragHandleFromBlockToBlockBottomById(page, '5', '3', false);
|
||||
await expect(page.locator('.affine-drag-indicator')).toBeHidden();
|
||||
await assertRichTexts(page, ['789', '123', '456']);
|
||||
});
|
||||
|
||||
test('move drag handle in nested block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '-');
|
||||
await page.keyboard.press('Space', { delay: 50 });
|
||||
await type(page, '1');
|
||||
await pressEnter(page);
|
||||
await type(page, '2');
|
||||
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, '21');
|
||||
await pressEnter(page);
|
||||
await type(page, '22');
|
||||
await pressEnter(page);
|
||||
await type(page, '23');
|
||||
await pressEnter(page);
|
||||
await pressShiftTab(page);
|
||||
|
||||
await type(page, '3');
|
||||
|
||||
await assertRichTexts(page, ['1', '2', '21', '22', '23', '3']);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(page, '5', '7');
|
||||
await expect(page.locator('.affine-drag-indicator')).toBeHidden();
|
||||
await assertRichTexts(page, ['1', '2', '22', '23', '21', '3']);
|
||||
|
||||
// FIXME(DND)
|
||||
// await dragHandleFromBlockToBlockBottomById(page, '3', '8');
|
||||
// await expect(page.locator('.affine-drag-indicator')).toBeHidden();
|
||||
// await assertRichTexts(page, ['2', '22', '23', '21', '3', '1']);
|
||||
});
|
||||
|
||||
test('move drag handle into another block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '-');
|
||||
await page.keyboard.press('Space', { delay: 50 });
|
||||
await type(page, '1');
|
||||
await pressEnter(page);
|
||||
await type(page, '2');
|
||||
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, '21');
|
||||
await pressEnter(page);
|
||||
await type(page, '22');
|
||||
await pressEnter(page);
|
||||
await type(page, '23');
|
||||
await pressEnter(page);
|
||||
await pressShiftTab(page);
|
||||
|
||||
await type(page, '3');
|
||||
|
||||
await assertRichTexts(page, ['1', '2', '21', '22', '23', '3']);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(
|
||||
page,
|
||||
'5',
|
||||
'7',
|
||||
true,
|
||||
2 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT
|
||||
);
|
||||
await expect(page.locator('.affine-drag-indicator')).toBeHidden();
|
||||
await assertRichTexts(page, ['1', '2', '22', '23', '21', '3']);
|
||||
// FIXME(DND)
|
||||
// await assertBlockChildrenIds(page, '7', ['5']);
|
||||
|
||||
// await dragHandleFromBlockToBlockBottomById(
|
||||
// page,
|
||||
// '3',
|
||||
// '8',
|
||||
// true,
|
||||
// 2 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT
|
||||
// );
|
||||
// await expect(page.locator('.affine-drag-indicator')).toBeHidden();
|
||||
// await assertRichTexts(page, ['2', '22', '23', '21', '3', '1']);
|
||||
// await assertBlockChildrenIds(page, '8', ['3']);
|
||||
});
|
||||
|
||||
test('move to the last block of each level in multi-level nesting', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '-');
|
||||
await page.keyboard.press('Space', { delay: 50 });
|
||||
await type(page, 'A');
|
||||
await pressEnter(page);
|
||||
await type(page, 'B');
|
||||
await pressEnter(page);
|
||||
await type(page, 'C');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'D');
|
||||
await pressEnter(page);
|
||||
await type(page, 'E');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'F');
|
||||
await pressEnter(page);
|
||||
await type(page, 'G');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(page, '3', '9');
|
||||
await expect(page.locator('.affine-drag-indicator')).toBeHidden();
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_drag_3_9.json`
|
||||
);
|
||||
|
||||
// FIXME(DND)
|
||||
// await dragHandleFromBlockToBlockBottomById(
|
||||
// page,
|
||||
// '4',
|
||||
// '3',
|
||||
// true,
|
||||
// -(1 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT)
|
||||
// );
|
||||
// await expect(page.locator('.affine-drag-indicator')).toBeHidden();
|
||||
//
|
||||
// expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
// `${testInfo.title}_drag_4_3.json`
|
||||
// );
|
||||
//
|
||||
// await assertRichTexts(page, ['C', 'D', 'E', 'F', 'G', 'A', 'B']);
|
||||
// await dragHandleFromBlockToBlockBottomById(
|
||||
// page,
|
||||
// '3',
|
||||
// '4',
|
||||
// true,
|
||||
// -(2 * BLOCK_CHILDREN_CONTAINER_PADDING_LEFT)
|
||||
// );
|
||||
// await expect(page.locator('.affine-drag-indicator')).toBeHidden();
|
||||
//
|
||||
// expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
// `${testInfo.title}_drag_3_4.json`
|
||||
// );
|
||||
//
|
||||
// await assertRichTexts(page, ['C', 'D', 'E', 'F', 'G', 'B', 'A']);
|
||||
});
|
||||
|
||||
test('should sync selected-blocks to session-manager when clicking drag handle', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await focusRichText(page, 1);
|
||||
const rect = await getBoundingClientRect(page, '[data-block-id="1"]');
|
||||
if (!rect) {
|
||||
throw new Error();
|
||||
}
|
||||
await page.mouse.move(rect.x + 10, rect.y + 10, { steps: 2 });
|
||||
|
||||
const handle = page.locator('.affine-drag-handle-container');
|
||||
await handle.click();
|
||||
|
||||
await page.keyboard.press('Backspace');
|
||||
await assertRichTexts(page, ['', '456', '789']);
|
||||
});
|
||||
|
||||
test.fixme(
|
||||
'should be able to drag & drop multiple blocks',
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[0, 0],
|
||||
[1, 3],
|
||||
{ x: -60, y: 0 },
|
||||
{ x: 80, y: 0 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(page, '2', '4', true);
|
||||
await expect(page.locator('.affine-drag-indicator')).toBeHidden();
|
||||
|
||||
await assertRichTexts(page, ['789', '123', '456']);
|
||||
|
||||
// Selection is still 2 after drop
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
}
|
||||
);
|
||||
|
||||
test.fixme(
|
||||
'should be able to drag & drop multiple blocks to nested block',
|
||||
async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '-');
|
||||
await page.keyboard.press('Space', { delay: 50 });
|
||||
await type(page, 'A');
|
||||
await pressEnter(page);
|
||||
await type(page, 'B');
|
||||
await pressEnter(page);
|
||||
await type(page, 'C');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'D');
|
||||
await pressEnter(page);
|
||||
await type(page, 'E');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await type(page, 'F');
|
||||
await pressEnter(page);
|
||||
await type(page, 'G');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[0, 0],
|
||||
[1, 1],
|
||||
{ x: -80, y: 0 },
|
||||
{ x: 80, y: 0 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(page, '3', '8');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_finial.json`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test('should blur rich-text first on starting block selection', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await expect(page.locator('*:focus')).toHaveCount(1);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(page, '2', '4');
|
||||
await expect(page.locator('.affine-drag-indicator')).toBeHidden();
|
||||
await assertRichTexts(page, ['456', '789', '123']);
|
||||
|
||||
await expect(page.locator('*:focus')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('hide drag handle when mouse is hovering over the title', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
|
||||
const rect = await getBoundingClientRect(
|
||||
page,
|
||||
'.affine-note-block-container'
|
||||
);
|
||||
const dragHandle = page.locator('.affine-drag-handle-container');
|
||||
// When there is a gap between paragraph blocks, it is the correct behavior for the drag handle to appear
|
||||
// when the mouse is over the gap. Therefore, we use rect.y - 20 to make the Y offset greater than the gap between the
|
||||
// paragraph blocks.
|
||||
await page.mouse.move(rect.x, rect.y - 20, { steps: 2 });
|
||||
await expect(dragHandle).toBeHidden();
|
||||
|
||||
await page.mouse.move(rect.x, rect.y, { steps: 2 });
|
||||
expect(await dragHandle.isVisible()).toBe(true);
|
||||
await expect(dragHandle).toBeVisible();
|
||||
});
|
||||
|
||||
test.fixme('should create preview when dragging', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
const dragPreview = page.locator('affine-drag-preview');
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[0, 0],
|
||||
[1, 3],
|
||||
{ x: -60, y: 0 },
|
||||
{ x: 80, y: 0 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
|
||||
await dragHandleFromBlockToBlockBottomById(
|
||||
page,
|
||||
'2',
|
||||
'4',
|
||||
true,
|
||||
undefined,
|
||||
async () => {
|
||||
await expect(dragPreview).toBeVisible();
|
||||
await expect(dragPreview.locator('[data-block-id]')).toHaveCount(4);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test.fixme(
|
||||
'should drag and drop blocks under block-level selection',
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[0, 0],
|
||||
[1, 3],
|
||||
{ x: -60, y: 0 },
|
||||
{ x: 80, y: 0 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
|
||||
const editorHost = getEditorHostLocator(page);
|
||||
const editors = editorHost.locator('rich-text');
|
||||
const editorRect0 = await editors.nth(0).boundingBox();
|
||||
const editorRect2 = await editors.nth(2).boundingBox();
|
||||
if (!editorRect0 || !editorRect2) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: editorRect0.x - 10,
|
||||
y: editorRect0.y + editorRect0.height / 2,
|
||||
},
|
||||
{
|
||||
x: editorRect2.x + 10,
|
||||
y: editorRect2.y + editorRect2.height / 2 + 10,
|
||||
},
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
await assertRichTexts(page, ['789', '123', '456']);
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
}
|
||||
);
|
||||
|
||||
test('should trigger click event on editor container when clicking on blocks under block-level selection', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[0, 0],
|
||||
[1, 3],
|
||||
{ x: -60, y: 0 },
|
||||
{ x: 80, y: 0 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
await expect(page.locator('*:focus')).toHaveCount(0);
|
||||
|
||||
const editorHost = getEditorHostLocator(page);
|
||||
const editors = editorHost.locator('rich-text');
|
||||
const editorRect0 = await editors.nth(0).boundingBox();
|
||||
if (!editorRect0) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
await page.mouse.move(
|
||||
editorRect0.x + 10,
|
||||
editorRect0.y + editorRect0.height / 2
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
await expect(blockSelections).toHaveCount(0);
|
||||
await expect(page.locator('*:focus')).toHaveCount(1);
|
||||
});
|
||||
|
||||
test('should get to selected block when dragging unselected block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '123');
|
||||
await pressEnter(page);
|
||||
await type(page, '456');
|
||||
await assertRichTexts(page, ['123', '456']);
|
||||
|
||||
const editorHost = getEditorHostLocator(page);
|
||||
const editors = editorHost.locator('rich-text');
|
||||
const editorRect0 = await editors.nth(0).boundingBox();
|
||||
const editorRect1 = await editors.nth(1).boundingBox();
|
||||
|
||||
if (!editorRect0 || !editorRect1) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
await page.mouse.move(editorRect1.x - 5, editorRect0.y);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(1);
|
||||
|
||||
await page.mouse.move(editorRect1.x - 5, editorRect0.y);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(
|
||||
editorRect1.x - 5,
|
||||
editorRect1.y + editorRect1.height / 2 + 1,
|
||||
{
|
||||
steps: 10,
|
||||
}
|
||||
);
|
||||
await page.mouse.up();
|
||||
|
||||
await expect(blockSelections).toHaveCount(1);
|
||||
|
||||
// FIXME(DND)
|
||||
// await assertRichTexts(page, ['456', '123']);
|
||||
});
|
||||
|
||||
test.fixme(
|
||||
'should clear the currently selected block when clicked again',
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '123');
|
||||
await pressEnter(page);
|
||||
await type(page, '456');
|
||||
await assertRichTexts(page, ['123', '456']);
|
||||
|
||||
const editorHost = getEditorHostLocator(page);
|
||||
const editors = editorHost.locator('rich-text');
|
||||
const editorRect0 = await editors.nth(0).boundingBox();
|
||||
const editorRect1 = await editors.nth(1).boundingBox();
|
||||
|
||||
if (!editorRect0 || !editorRect1) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
await page.mouse.move(
|
||||
editorRect1.x + 5,
|
||||
editorRect1.y + editorRect1.height / 2
|
||||
);
|
||||
|
||||
await page.mouse.move(
|
||||
editorRect1.x - 10,
|
||||
editorRect1.y + editorRect1.height / 2
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(1);
|
||||
|
||||
let selectedBlockRect = await blockSelections.nth(0).boundingBox();
|
||||
|
||||
if (!selectedBlockRect) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
expect(editorRect1).toEqual(selectedBlockRect);
|
||||
|
||||
await page.mouse.move(
|
||||
editorRect0.x - 10,
|
||||
editorRect0.y + editorRect0.height / 2
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
|
||||
await expect(blockSelections).toHaveCount(1);
|
||||
|
||||
selectedBlockRect = await blockSelections.nth(0).boundingBox();
|
||||
|
||||
if (!selectedBlockRect) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
expect(editorRect0).toEqual(selectedBlockRect);
|
||||
}
|
||||
);
|
||||
|
||||
test.fixme(
|
||||
'should support moving blocks from multiple notes',
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await page.evaluate(() => {
|
||||
const { doc } = window;
|
||||
|
||||
const rootId = doc.addBlock('affine:page', {
|
||||
title: new doc.Text(),
|
||||
});
|
||||
doc.addBlock('affine:surface', {}, rootId);
|
||||
|
||||
['123', '456', '789', '987', '654', '321'].forEach(text => {
|
||||
const noteId = doc.addBlock('affine:note', {}, rootId);
|
||||
doc.addBlock(
|
||||
'affine:paragraph',
|
||||
{
|
||||
text: new doc.Text(text),
|
||||
},
|
||||
noteId
|
||||
);
|
||||
});
|
||||
|
||||
doc.resetHistory();
|
||||
});
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[1, 0],
|
||||
[2, 3],
|
||||
{ x: -60, y: 0 },
|
||||
{ x: 80, y: 0 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
|
||||
const editorHost = getEditorHostLocator(page);
|
||||
const editors = editorHost.locator('rich-text');
|
||||
const editorRect1 = await editors.nth(1).boundingBox();
|
||||
const editorRect3 = await editors.nth(3).boundingBox();
|
||||
if (!editorRect1 || !editorRect3) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: editorRect1.x - 10,
|
||||
y: editorRect1.y + editorRect1.height / 2,
|
||||
},
|
||||
{
|
||||
x: editorRect3.x + 10,
|
||||
y: editorRect3.y + editorRect3.height / 2 + 10,
|
||||
},
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
await assertRichTexts(page, ['123', '987', '456', '789', '654', '321']);
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[5, 0],
|
||||
[4, 3],
|
||||
{ x: -60, y: 0 },
|
||||
{ x: 80, y: 0 },
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
const editorRect0 = await editors.nth(0).boundingBox();
|
||||
const editorRect5 = await editors.nth(5).boundingBox();
|
||||
if (!editorRect0 || !editorRect5) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: editorRect5.x - 10,
|
||||
y: editorRect5.y + editorRect5.height / 2,
|
||||
},
|
||||
{
|
||||
x: editorRect0.x + 10,
|
||||
y: editorRect0.y + editorRect0.height / 2 - 5,
|
||||
},
|
||||
{
|
||||
steps: 50,
|
||||
}
|
||||
);
|
||||
|
||||
await assertRichTexts(page, ['654', '321', '123', '987', '456', '789']);
|
||||
await expect(blockSelections).toHaveCount(2);
|
||||
}
|
||||
);
|
||||
|
||||
test('drag handle should show on right block when scroll viewport', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initParagraphsByCount(page, 30);
|
||||
|
||||
await page.mouse.wheel(0, 200);
|
||||
|
||||
const editorHost = getEditorHostLocator(page);
|
||||
const editors = editorHost.locator('rich-text');
|
||||
const blockRect28 = await editors.nth(28).boundingBox();
|
||||
if (!blockRect28) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
await page.mouse.move(blockRect28.x + 10, blockRect28.y + 10);
|
||||
const dragHandle = page.locator('.affine-drag-handle-container');
|
||||
await expect(dragHandle).toBeVisible();
|
||||
|
||||
await page.mouse.move(
|
||||
blockRect28.x - 10,
|
||||
blockRect28.y + blockRect28.height / 2
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
|
||||
const blockSelections = page
|
||||
.locator('affine-block-selection')
|
||||
.locator('visible=true');
|
||||
await expect(blockSelections).toHaveCount(1);
|
||||
|
||||
const selectedBlockRect = await blockSelections.nth(0).boundingBox();
|
||||
|
||||
if (!selectedBlockRect) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
expect(blockRect28).toEqual(selectedBlockRect);
|
||||
});
|
||||
435
blocksuite/tests-legacy/edgeless/align.spec.ts
Normal file
435
blocksuite/tests-legacy/edgeless/align.spec.ts
Normal file
@@ -0,0 +1,435 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addBasicBrushElement,
|
||||
createConnectorElement,
|
||||
createFrameElement,
|
||||
createNote,
|
||||
createShapeElement,
|
||||
setEdgelessTool,
|
||||
Shape,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
clickView,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessSelectedModelRect,
|
||||
getSelectedRect,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('auto arrange align', () => {
|
||||
test('arrange shapes', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond);
|
||||
await createShapeElement(page, [100, -100], [300, 100], Shape.Ellipse);
|
||||
await createShapeElement(page, [200, 300], [300, 400], Shape.Square);
|
||||
await createShapeElement(page, [400, 100], [500, 200], Shape.Triangle);
|
||||
await createShapeElement(
|
||||
page,
|
||||
[0, 200],
|
||||
[100, 300],
|
||||
Shape['Rounded rectangle']
|
||||
);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, -100, 500, 500]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoArrange');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 560, 320]);
|
||||
});
|
||||
|
||||
test('arrange rotated shapes', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Ellipse);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
|
||||
const point = await toViewCoord(page, [100, 100]);
|
||||
await page.mouse.click(point[0] + 50, point[1] + 50);
|
||||
await page.mouse.move(point[0] - 5, point[1] - 5);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(point[0] - 5, point[1] + 45);
|
||||
await page.mouse.up();
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 220, 220]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoArrange');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 261, 141]);
|
||||
});
|
||||
|
||||
test('arrange connected shapes', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Ellipse);
|
||||
await createConnectorElement(page, [50, 100], [150, 100]);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 200, 200]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoArrange');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, -21, 220, 141.4]);
|
||||
});
|
||||
|
||||
test('arrange connector', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [200, 200], [300, 200]);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 300, 200]);
|
||||
|
||||
// arrange
|
||||
await triggerComponentToolbarAction(page, 'autoArrange');
|
||||
await waitNextFrame(page, 200);
|
||||
await assertEdgelessSelectedModelRect(page, [0, 0, 220, 100]);
|
||||
});
|
||||
|
||||
test('arrange edgeless text', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
|
||||
const point = await toViewCoord(page, [200, -100]);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.dblclick(point[0], point[1], {
|
||||
delay: 100,
|
||||
});
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'a');
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await assertEdgelessSelectedModelRect(page, [0, -125, 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]);
|
||||
});
|
||||
});
|
||||
244
blocksuite/tests-legacy/edgeless/auto-complete.spec.ts
Normal file
244
blocksuite/tests-legacy/edgeless/auto-complete.spec.ts
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
180
blocksuite/tests-legacy/edgeless/auto-connect.spec.ts
Normal file
180
blocksuite/tests-legacy/edgeless/auto-connect.spec.ts
Normal file
@@ -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
|
||||
);
|
||||
});
|
||||
});
|
||||
358
blocksuite/tests-legacy/edgeless/basic.spec.ts
Normal file
358
blocksuite/tests-legacy/edgeless/basic.spec.ts
Normal file
@@ -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();
|
||||
});
|
||||
193
blocksuite/tests-legacy/edgeless/brush.spec.ts
Normal file
193
blocksuite/tests-legacy/edgeless/brush.spec.ts
Normal file
@@ -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]);
|
||||
});
|
||||
235
blocksuite/tests-legacy/edgeless/clipboard.spec.ts
Normal file
235
blocksuite/tests-legacy/edgeless/clipboard.spec.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
createNote,
|
||||
createShapeElement,
|
||||
decreaseZoomLevel,
|
||||
deleteAll,
|
||||
getAllSortedIds,
|
||||
Shape,
|
||||
switchEditorMode,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
copyByKeyboard,
|
||||
cutByKeyboard,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
enterPlaygroundRoom,
|
||||
expectConsoleMessage,
|
||||
focusTitle,
|
||||
getCurrentEditorDocId,
|
||||
initEmptyEdgelessState,
|
||||
mockParseDocUrlService,
|
||||
pasteByKeyboard,
|
||||
pasteContent,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import { assertRichImage } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('mime', () => {
|
||||
test('should paste svg in text/plain mime', async ({ page }) => {
|
||||
expectConsoleMessage(page, 'Error: Image sourceId is missing!', 'warning');
|
||||
await commonSetup(page);
|
||||
const content = {
|
||||
'text/plain': `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
|
||||
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
|
||||
<script>alert("Malicious script executed!");</script>
|
||||
</svg>
|
||||
`,
|
||||
};
|
||||
|
||||
await pasteContent(page, content);
|
||||
|
||||
// wait for paste
|
||||
await page.waitForTimeout(200);
|
||||
await assertRichImage(page, 1);
|
||||
});
|
||||
|
||||
test('should not paste bad svg', async ({ page }) => {
|
||||
expectConsoleMessage(page, 'BlockSuiteError: val does not exist', 'error');
|
||||
expectConsoleMessage(page, 'Error: Image sourceId is missing!', 'warning');
|
||||
|
||||
await commonSetup(page);
|
||||
const contents = [
|
||||
{
|
||||
'text/plain': `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
|
||||
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
|
||||
<script>alert("Malicious script executed!");</script>
|
||||
`,
|
||||
},
|
||||
|
||||
{
|
||||
'text/plain': `<svg width="100" height="100">
|
||||
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
|
||||
<script>alert("Malicious script executed!");</script>
|
||||
</svg>
|
||||
`,
|
||||
},
|
||||
];
|
||||
for (const content of contents) {
|
||||
await pasteContent(page, content);
|
||||
}
|
||||
|
||||
await assertRichImage(page, 0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('frame clipboard', () => {
|
||||
test('copy and paste frame with shape elements inside', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [100, -100]);
|
||||
await page.mouse.click(10, 50);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addFrame');
|
||||
const originIds = await getAllSortedIds(page);
|
||||
expect(originIds.length).toBe(3);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(6);
|
||||
});
|
||||
|
||||
test('copy and paste frame with group elements inside', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [100, -100]);
|
||||
await page.mouse.click(10, 50);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await triggerComponentToolbarAction(page, 'createFrameOnMoreOption');
|
||||
const originIds = await getAllSortedIds(page);
|
||||
expect(originIds.length).toBe(5);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(10);
|
||||
});
|
||||
|
||||
test('copy and paste frame with frame inside', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [100, -100]);
|
||||
await page.mouse.click(10, 50);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addFrame');
|
||||
|
||||
await decreaseZoomLevel(page);
|
||||
await createShapeElement(page, [700, 0], [800, 100], Shape.Square);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addFrame');
|
||||
|
||||
const originIds = await getAllSortedIds(page);
|
||||
expect(originIds.length).toBe(5);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(10);
|
||||
});
|
||||
|
||||
test('cut frame with shape elements inside', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [100, -100]);
|
||||
await page.mouse.click(10, 50);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addFrame');
|
||||
const originIds = await getAllSortedIds(page);
|
||||
expect(originIds.length).toBe(3);
|
||||
|
||||
await cutByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('pasting URLs', () => {
|
||||
test('pasting github pr url', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await waitNextFrame(page);
|
||||
await pasteContent(page, {
|
||||
'text/plain': 'https://github.com/toeverything/blocksuite/pull/7217',
|
||||
});
|
||||
|
||||
await expect(
|
||||
page.locator('affine-embed-edgeless-github-block')
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('pasting internal link', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await waitNextFrame(page);
|
||||
await focusTitle(page);
|
||||
const docId = await getCurrentEditorDocId(page);
|
||||
|
||||
await type(page, 'doc title');
|
||||
|
||||
await switchEditorMode(page);
|
||||
await deleteAll(page);
|
||||
|
||||
await mockParseDocUrlService(page, {
|
||||
'http://workspace/doc-id': docId,
|
||||
});
|
||||
|
||||
await pasteContent(page, {
|
||||
'text/plain': 'http://workspace/doc-id',
|
||||
});
|
||||
|
||||
await expect(
|
||||
page.locator('affine-embed-edgeless-linked-doc-block')
|
||||
).toBeVisible();
|
||||
|
||||
await expect(
|
||||
page.locator('.affine-embed-linked-doc-content-title')
|
||||
).toHaveText('doc title');
|
||||
});
|
||||
|
||||
test('pasting external link', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await waitNextFrame(page);
|
||||
await focusTitle(page);
|
||||
|
||||
await type(page, 'doc title');
|
||||
|
||||
await switchEditorMode(page);
|
||||
await deleteAll(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await pasteContent(page, {
|
||||
'text/plain': 'https://affine.pro',
|
||||
});
|
||||
|
||||
await expect(page.locator('bookmark-card')).toBeVisible();
|
||||
});
|
||||
});
|
||||
368
blocksuite/tests-legacy/edgeless/color-picker.spec.ts
Normal file
368
blocksuite/tests-legacy/edgeless/color-picker.spec.ts
Normal file
@@ -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 });
|
||||
});
|
||||
});
|
||||
142
blocksuite/tests-legacy/edgeless/connector/clipboard.spec.ts
Normal file
142
blocksuite/tests-legacy/edgeless/connector/clipboard.spec.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
copyByKeyboard,
|
||||
createConnectorElement,
|
||||
createNote,
|
||||
createShapeElement,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
getAllSortedIds,
|
||||
getTypeById,
|
||||
pasteByKeyboard,
|
||||
selectAllByKeyboard,
|
||||
Shape,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertConnectorPath } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test.describe('connector clipboard', () => {
|
||||
test('copy and paste connector whose both sides connect nothing', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createConnectorElement(page, [0, 0], [200, 100]);
|
||||
await waitNextFrame(page);
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [100, -50]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page);
|
||||
await assertConnectorPath(
|
||||
page,
|
||||
[
|
||||
[0, -100],
|
||||
[100, -100],
|
||||
[100, 0],
|
||||
[200, 0],
|
||||
],
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
test('copy and paste connector whose both sides connect elements', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [60, 50], [240, 50]);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [150, -50]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page);
|
||||
await assertConnectorPath(
|
||||
page,
|
||||
[
|
||||
[100, -50],
|
||||
[200, -50],
|
||||
],
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
test('copy and paste connector whose both sides connect elements, but only paste connector', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [70, 50], [230, 50]);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [150, -50]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page);
|
||||
await assertConnectorPath(
|
||||
page,
|
||||
[
|
||||
[100, -50],
|
||||
[200, -50],
|
||||
],
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
test('copy and paste connector whose one side connects elements', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [55, 50], [200, 50]);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [100, -50]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, false);
|
||||
await assertConnectorPath(
|
||||
page,
|
||||
[
|
||||
[100, -50],
|
||||
[200, -50],
|
||||
],
|
||||
1
|
||||
);
|
||||
});
|
||||
|
||||
test('original relative index should keep same when copy and paste group with note and shape', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [100, 50]);
|
||||
await page.mouse.click(10, 50);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(6);
|
||||
expect(await getTypeById(page, sortedIds[0])).toBe(
|
||||
await getTypeById(page, sortedIds[3])
|
||||
);
|
||||
expect(await getTypeById(page, sortedIds[1])).toBe(
|
||||
await getTypeById(page, sortedIds[4])
|
||||
);
|
||||
expect(await getTypeById(page, sortedIds[2])).toBe(
|
||||
await getTypeById(page, sortedIds[5])
|
||||
);
|
||||
});
|
||||
});
|
||||
320
blocksuite/tests-legacy/edgeless/connector/connector.spec.ts
Normal file
320
blocksuite/tests-legacy/edgeless/connector/connector.spec.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addBasicConnectorElement,
|
||||
changeConnectorStrokeColor,
|
||||
changeConnectorStrokeStyle,
|
||||
changeConnectorStrokeWidth,
|
||||
createConnectorElement,
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
pickColorAtPoints,
|
||||
rotateElementByHandle,
|
||||
Shape,
|
||||
toModelCoord,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import { pressBackspace, waitNextFrame } from '../../utils/actions/index.js';
|
||||
import {
|
||||
assertConnectorPath,
|
||||
assertEdgelessNonSelectedRect,
|
||||
assertEdgelessSelectedRect,
|
||||
assertExists,
|
||||
} from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test('path #1, the upper line is parallel with the lower line of antoher, and anchor from top to bottom of another', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, -100], [300, 0], Shape.Square);
|
||||
await createConnectorElement(page, [50, 0], [250, 0]);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -20],
|
||||
[150, -20],
|
||||
[150, 20],
|
||||
[250, 20],
|
||||
[250, 0],
|
||||
]);
|
||||
});
|
||||
|
||||
test('path #2, the top-right point is overlapped with the bottom-left point of another, and anchor from top to bottom of another', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, -100], [200, 0], Shape.Square);
|
||||
await createConnectorElement(page, [50, 0], [150, 0]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -120],
|
||||
[220, -120],
|
||||
[220, 20],
|
||||
[150, 20],
|
||||
[150, 0],
|
||||
]);
|
||||
});
|
||||
|
||||
test('path #3, the two shape are parallel in x axis, the anchor from the right to right', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 50], [300, 50]);
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[150, 50],
|
||||
[150, 120],
|
||||
[320, 120],
|
||||
[320, 50],
|
||||
[300, 50],
|
||||
]);
|
||||
});
|
||||
|
||||
test('when element is removed, connector should be deleted too', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 50], [200, 0]);
|
||||
|
||||
//select
|
||||
await dragBetweenViewCoords(page, [10, -10], [20, 20]);
|
||||
await pressBackspace(page);
|
||||
await dragBetweenViewCoords(page, [100, 50], [0, 50]);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test('connector connects triangle shape', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Triangle);
|
||||
await createConnectorElement(page, [75, 50], [100, 50]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[75, 50],
|
||||
[100, 50],
|
||||
]);
|
||||
});
|
||||
|
||||
test('connector connects diamond shape', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Diamond);
|
||||
await createConnectorElement(page, [100, 50], [200, 50]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[200, 50],
|
||||
]);
|
||||
});
|
||||
|
||||
test('connector connects rotated Square shape', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [50, 0], [50, -100]);
|
||||
await dragBetweenViewCoords(page, [-10, 50], [60, 60]);
|
||||
await rotateElementByHandle(page, 30, 'top-left');
|
||||
await assertConnectorPath(page, [
|
||||
[75, 6.7],
|
||||
[75, -46.65],
|
||||
[50, -46.65],
|
||||
[50, -100],
|
||||
]);
|
||||
await rotateElementByHandle(page, 30, 'top-left');
|
||||
await assertConnectorPath(page, [
|
||||
[93.3, 25],
|
||||
[138.3, 25],
|
||||
[138.3, -38.3],
|
||||
[50, -38.3],
|
||||
[50, -100],
|
||||
]);
|
||||
});
|
||||
|
||||
test('change connector line width', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
const start = { x: 100, y: 200 };
|
||||
const end = { x: 300, y: 300 };
|
||||
await addBasicConnectorElement(page, start, end);
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y);
|
||||
await triggerComponentToolbarAction(page, 'changeConnectorStrokeColor');
|
||||
await changeConnectorStrokeColor(page, '--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]);
|
||||
});
|
||||
});
|
||||
206
blocksuite/tests-legacy/edgeless/connector/elbow.spec.ts
Normal file
206
blocksuite/tests-legacy/edgeless/connector/elbow.spec.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import {
|
||||
assertEdgelessConnectorToolMode,
|
||||
ConnectorMode,
|
||||
createConnectorElement,
|
||||
createShapeElement,
|
||||
deleteAllConnectors,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
redoByClick,
|
||||
setEdgelessTool,
|
||||
Shape,
|
||||
undoByClick,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertConnectorPath } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test('elbow connector without node and width greater than height', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await setEdgelessTool(page, 'connector');
|
||||
await assertEdgelessConnectorToolMode(page, ConnectorMode.Curve);
|
||||
await dragBetweenViewCoords(page, [0, 0], [200, 100]);
|
||||
await assertConnectorPath(page, [
|
||||
[0, 0],
|
||||
[100, 0],
|
||||
[100, 100],
|
||||
[200, 100],
|
||||
]);
|
||||
});
|
||||
|
||||
test('elbow connector without node and width less than height', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createConnectorElement(page, [0, 0], [100, 200]);
|
||||
await assertConnectorPath(page, [
|
||||
[0, 0],
|
||||
[0, 100],
|
||||
[100, 100],
|
||||
[100, 200],
|
||||
]);
|
||||
});
|
||||
|
||||
test('elbow connector one side attached element another side free', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [51, 50], [200, 0]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[150, 50],
|
||||
[150, 0],
|
||||
[200, 0],
|
||||
]);
|
||||
|
||||
await deleteAllConnectors(page);
|
||||
await createConnectorElement(page, [50, 50], [125, 0]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[125, 50],
|
||||
[125, 0],
|
||||
]);
|
||||
});
|
||||
|
||||
test('elbow connector both side attatched element', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [50, 50], [249, 50]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[200, 50],
|
||||
]);
|
||||
|
||||
// Could drag directly
|
||||
// because the default shape type change to general style with filled color
|
||||
await dragBetweenViewCoords(page, [250, 50], [250, 0]);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[150, 50],
|
||||
[150, 0],
|
||||
[200, 0],
|
||||
]);
|
||||
|
||||
await dragBetweenViewCoords(page, [250, 0], [150, -50]);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -50],
|
||||
[100, -50],
|
||||
]);
|
||||
|
||||
await dragBetweenViewCoords(page, [150, -50], [150, -150]);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -50],
|
||||
[150, -50],
|
||||
[150, -100],
|
||||
]);
|
||||
|
||||
await undoByClick(page);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -50],
|
||||
[100, -50],
|
||||
]);
|
||||
await undoByClick(page);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[150, 50],
|
||||
[150, 0],
|
||||
[200, 0],
|
||||
]);
|
||||
await undoByClick(page);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[200, 50],
|
||||
]);
|
||||
await redoByClick(page);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[150, 50],
|
||||
[150, 0],
|
||||
[200, 0],
|
||||
]);
|
||||
await redoByClick(page);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -50],
|
||||
[100, -50],
|
||||
]);
|
||||
await redoByClick(page);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -50],
|
||||
[150, -50],
|
||||
[150, -100],
|
||||
]);
|
||||
});
|
||||
|
||||
test('elbow connector both side attached element with one attach element and other is fixed', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [50, 0], [250, 50]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -20],
|
||||
[150, -20],
|
||||
[150, 50],
|
||||
[200, 50],
|
||||
]);
|
||||
|
||||
// select
|
||||
await dragBetweenViewCoords(page, [255, -10], [255, 55]);
|
||||
await dragBetweenViewCoords(page, [250, 50], [250, 0]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -20],
|
||||
[150, -20],
|
||||
[150, 0],
|
||||
[200, 0],
|
||||
]);
|
||||
|
||||
await dragBetweenViewCoords(page, [250, 0], [250, -20]);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -20],
|
||||
[200, -20],
|
||||
]);
|
||||
|
||||
await dragBetweenViewCoords(page, [250, -20], [150, -150]);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -50],
|
||||
[150, -50],
|
||||
[150, -100],
|
||||
]);
|
||||
});
|
||||
|
||||
test('elbow connector both side attached element with all fixed', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [50, 0], [300, 50]);
|
||||
await assertConnectorPath(page, [
|
||||
[50, 0],
|
||||
[50, -20],
|
||||
[320, -20],
|
||||
[320, 50],
|
||||
[300, 50],
|
||||
]);
|
||||
});
|
||||
107
blocksuite/tests-legacy/edgeless/connector/group.spec.ts
Normal file
107
blocksuite/tests-legacy/edgeless/connector/group.spec.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
clickView,
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
moveView,
|
||||
selectAllByKeyboard,
|
||||
Shape,
|
||||
triggerComponentToolbarAction,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertConnectorPath } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test.describe('groups connections', () => {
|
||||
async function groupsSetup(page: Page) {
|
||||
await commonSetup(page);
|
||||
|
||||
// group 1
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
// group 2
|
||||
await createShapeElement(page, [500, 0], [600, 100], Shape.Square);
|
||||
await createShapeElement(page, [600, 100], [700, 200], Shape.Square);
|
||||
await dragBetweenViewCoords(page, [550, -50], [650, 250]);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
await waitNextFrame(page);
|
||||
}
|
||||
|
||||
test('should connect to other groups', async ({ page }) => {
|
||||
await groupsSetup(page);
|
||||
|
||||
// click button
|
||||
await triggerComponentToolbarAction(page, 'quickConnect');
|
||||
|
||||
// move to group 1
|
||||
await moveView(page, [200, 50]);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[500, 100],
|
||||
[200, 50],
|
||||
]);
|
||||
});
|
||||
|
||||
test('should connect to elements within other groups', async ({ page }) => {
|
||||
await groupsSetup(page);
|
||||
|
||||
// click button
|
||||
await triggerComponentToolbarAction(page, 'quickConnect');
|
||||
|
||||
// move to group 1
|
||||
await moveView(page, [200, 100]);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[500, 100],
|
||||
[200, 100],
|
||||
]);
|
||||
|
||||
// move to elements within group 1
|
||||
await moveView(page, [190, 150]);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[500, 100],
|
||||
[200, 150],
|
||||
]);
|
||||
});
|
||||
|
||||
test('elements within groups should connect to other groups', async ({
|
||||
page,
|
||||
}) => {
|
||||
await groupsSetup(page);
|
||||
|
||||
// click elements within group 1
|
||||
await clickView(page, [40, 40]);
|
||||
await clickView(page, [60, 60]);
|
||||
|
||||
// click button
|
||||
await triggerComponentToolbarAction(page, 'quickConnect');
|
||||
|
||||
// move to elements within group 2
|
||||
await moveView(page, [610, 50]);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[600, 50],
|
||||
]);
|
||||
|
||||
// move to group 2
|
||||
await moveView(page, [600, 100]);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[600, 100],
|
||||
]);
|
||||
});
|
||||
});
|
||||
334
blocksuite/tests-legacy/edgeless/connector/label.spec.ts
Normal file
334
blocksuite/tests-legacy/edgeless/connector/label.spec.ts
Normal file
@@ -0,0 +1,334 @@
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addBasicConnectorElement,
|
||||
createConnectorElement,
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
locatorComponentToolbar,
|
||||
setEdgelessTool,
|
||||
Shape,
|
||||
SHORT_KEY,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
type,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/index.js';
|
||||
import {
|
||||
assertConnectorPath,
|
||||
assertEdgelessCanvasText,
|
||||
assertPointAlmostEqual,
|
||||
} from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test.describe('connector label with straight shape', () => {
|
||||
async function getEditorCenter(page: Page) {
|
||||
const bounds = await page
|
||||
.locator('edgeless-connector-label-editor rich-text')
|
||||
.boundingBox();
|
||||
assertExists(bounds);
|
||||
const cx = bounds.x + bounds.width / 2;
|
||||
const cy = bounds.y + bounds.height / 2;
|
||||
return [cx, cy];
|
||||
}
|
||||
|
||||
function calcOffsetDistance(s: number[], e: number[], p: number[]) {
|
||||
const p1 = Math.hypot(s[1] - p[1], s[0] - p[0]);
|
||||
const f1 = Math.hypot(s[1] - e[1], s[0] - e[0]);
|
||||
return p1 / f1;
|
||||
}
|
||||
|
||||
test('should insert in the middle of the path when clicking on the button', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
const start = { x: 100, y: 200 };
|
||||
const end = { x: 300, y: 300 };
|
||||
await addBasicConnectorElement(page, start, end);
|
||||
await page.mouse.click(105, 200);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'addText');
|
||||
await type(page, ' a ');
|
||||
await assertEdgelessCanvasText(page, ' a ');
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.click(105, 200);
|
||||
|
||||
const addTextBtn = locatorComponentToolbar(page).getByRole('button', {
|
||||
name: 'Add text',
|
||||
});
|
||||
await expect(addTextBtn).toBeHidden();
|
||||
|
||||
await page.mouse.dblclick(200, 250);
|
||||
await assertEdgelessCanvasText(page, 'a');
|
||||
|
||||
await page.keyboard.press('Backspace');
|
||||
await assertEdgelessCanvasText(page, '');
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.click(200, 250);
|
||||
|
||||
await expect(addTextBtn).toBeVisible();
|
||||
});
|
||||
|
||||
test('should insert at the place when double clicking on the path', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
await setEdgelessTool(page, 'connector');
|
||||
|
||||
await page.mouse.move(0, 0);
|
||||
|
||||
const menu = page.locator('edgeless-connector-menu');
|
||||
await expect(menu).toBeVisible();
|
||||
|
||||
const straightBtn = menu.locator('edgeless-tool-icon-button', {
|
||||
hasText: 'Straight',
|
||||
});
|
||||
await expect(straightBtn).toBeVisible();
|
||||
await straightBtn.click();
|
||||
|
||||
const start = { x: 250, y: 250 };
|
||||
const end = { x: 500, y: 250 };
|
||||
await addBasicConnectorElement(page, start, end);
|
||||
|
||||
await page.mouse.dblclick(300, 250);
|
||||
await type(page, 'a');
|
||||
await assertEdgelessCanvasText(page, 'a');
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.dblclick(300, 250);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await type(page, 'b');
|
||||
await assertEdgelessCanvasText(page, 'ab');
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.dblclick(300, 250);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await type(page, 'c');
|
||||
await assertEdgelessCanvasText(page, 'c');
|
||||
await waitNextFrame(page);
|
||||
|
||||
const [cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [300, 250]);
|
||||
expect((cx - 250) / (500 - 250)).toBeCloseTo(50 / 250);
|
||||
});
|
||||
|
||||
test('should move alone the path', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 50], [200, 50]);
|
||||
|
||||
await dragBetweenViewCoords(page, [140, 40], [160, 60]);
|
||||
await triggerComponentToolbarAction(page, 'changeConnectorShape');
|
||||
const straightBtn = locatorComponentToolbar(page).getByRole('button', {
|
||||
name: 'Straight',
|
||||
});
|
||||
await straightBtn.click();
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[200, 50],
|
||||
]);
|
||||
|
||||
const [x, y] = await toViewCoord(page, [150, 50]);
|
||||
await page.mouse.dblclick(x, y);
|
||||
await type(page, 'label');
|
||||
await assertEdgelessCanvasText(page, 'label');
|
||||
await waitNextFrame(page);
|
||||
|
||||
let [cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [x, y]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await dragBetweenViewCoords(page, [150, 50], [130, 30]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.dblclick(x - 20, y);
|
||||
await waitNextFrame(page);
|
||||
|
||||
[cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [x - 20, y]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await dragBetweenViewCoords(page, [130, 50], [170, 70]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.dblclick(x + 20, y);
|
||||
await waitNextFrame(page);
|
||||
|
||||
[cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [x + 20, y]);
|
||||
});
|
||||
|
||||
test('should only move within constraints', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 50], [200, 50]);
|
||||
|
||||
await assertConnectorPath(page, [
|
||||
[100, 50],
|
||||
[200, 50],
|
||||
]);
|
||||
|
||||
const [x, y] = await toViewCoord(page, [150, 50]);
|
||||
await page.mouse.dblclick(x, y);
|
||||
await type(page, 'label');
|
||||
await assertEdgelessCanvasText(page, 'label');
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await dragBetweenViewCoords(page, [150, 50], [300, 110]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.dblclick(x + 55, y);
|
||||
await waitNextFrame(page);
|
||||
|
||||
let [cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [x + 50, y]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await dragBetweenViewCoords(page, [200, 50], [0, 50]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.dblclick(x - 55, y);
|
||||
await waitNextFrame(page);
|
||||
|
||||
[cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [x - 50, y]);
|
||||
});
|
||||
|
||||
test('should automatically adjust position via offset distance', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 50], [200, 50]);
|
||||
|
||||
await dragBetweenViewCoords(page, [140, 40], [160, 60]);
|
||||
await triggerComponentToolbarAction(page, 'changeConnectorShape');
|
||||
const straightBtn = locatorComponentToolbar(page).getByRole('button', {
|
||||
name: 'Straight',
|
||||
});
|
||||
await straightBtn.click();
|
||||
|
||||
const point = [170, 50];
|
||||
const offsetDistance = calcOffsetDistance([100, 50], [200, 50], point);
|
||||
let [x, y] = await toViewCoord(page, point);
|
||||
await page.mouse.dblclick(x, y);
|
||||
await type(page, 'label');
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.dblclick(x, y);
|
||||
await waitNextFrame(page);
|
||||
|
||||
let [cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [x, y]);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.click(50, 50);
|
||||
await waitNextFrame(page);
|
||||
await dragBetweenViewCoords(page, [50, 50], [-50, 50]);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await page.mouse.click(250, 50);
|
||||
await waitNextFrame(page);
|
||||
await dragBetweenViewCoords(page, [250, 50], [350, 50]);
|
||||
await waitNextFrame(page);
|
||||
|
||||
const start = [0, 50];
|
||||
const end = [300, 50];
|
||||
const mx = start[0] + offsetDistance * (end[0] - start[0]);
|
||||
const my = start[1] + offsetDistance * (end[1] - start[1]);
|
||||
[x, y] = await toViewCoord(page, [mx, my]);
|
||||
|
||||
await page.mouse.dblclick(x, y);
|
||||
await waitNextFrame(page);
|
||||
|
||||
[cx, cy] = await getEditorCenter(page);
|
||||
assertPointAlmostEqual([cx, cy], [x, y]);
|
||||
});
|
||||
|
||||
test('should enter the label editing state when pressing `Enter`', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
const start = { x: 100, y: 200 };
|
||||
const end = { x: 300, y: 300 };
|
||||
await addBasicConnectorElement(page, start, end);
|
||||
await page.mouse.click(105, 200);
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
await type(page, ' a ');
|
||||
await assertEdgelessCanvasText(page, ' a ');
|
||||
});
|
||||
|
||||
test('should exit the label editing state when pressing `Mod-Enter` or `Escape`', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
const start = { x: 100, y: 200 };
|
||||
const end = { x: 300, y: 300 };
|
||||
await addBasicConnectorElement(page, start, end);
|
||||
await page.mouse.click(105, 200);
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
await waitNextFrame(page);
|
||||
await type(page, ' a ');
|
||||
await assertEdgelessCanvasText(page, ' a ');
|
||||
|
||||
await page.keyboard.press(`${SHORT_KEY}+Enter`);
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'b');
|
||||
await assertEdgelessCanvasText(page, 'b');
|
||||
|
||||
await page.keyboard.press('Escape');
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'c');
|
||||
await assertEdgelessCanvasText(page, 'c');
|
||||
});
|
||||
});
|
||||
596
blocksuite/tests-legacy/edgeless/edgeless-text.spec.ts
Normal file
596
blocksuite/tests-legacy/edgeless/edgeless-text.spec.ts
Normal file
@@ -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`
|
||||
);
|
||||
});
|
||||
88
blocksuite/tests-legacy/edgeless/element-toolbar.spec.ts
Normal file
88
blocksuite/tests-legacy/edgeless/element-toolbar.spec.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addBasicRectShapeElement,
|
||||
locatorComponentToolbar,
|
||||
resizeElementByHandle,
|
||||
selectNoteInEdgeless,
|
||||
switchEditorMode,
|
||||
zoomResetByKeyboard,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
} from '../utils/actions/misc.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('toolbar should appear when select note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
|
||||
const toolbar = locatorComponentToolbar(page);
|
||||
await expect(toolbar).toBeVisible();
|
||||
});
|
||||
|
||||
test('tooltip should be hidden after clicking on button', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
|
||||
const toolbar = locatorComponentToolbar(page);
|
||||
const modeBtn = toolbar.getByRole('button', { name: 'Mode' });
|
||||
|
||||
await modeBtn.hover();
|
||||
await expect(page.locator('.blocksuite-portal')).toBeVisible();
|
||||
|
||||
await modeBtn.click();
|
||||
await expect(page.locator('.blocksuite-portal')).toBeHidden();
|
||||
await expect(page.locator('note-display-mode-panel')).toBeVisible();
|
||||
|
||||
await modeBtn.click();
|
||||
await expect(page.locator('.blocksuite-portal')).toBeVisible();
|
||||
await expect(page.locator('note-display-mode-panel')).toBeHidden();
|
||||
|
||||
await modeBtn.click();
|
||||
await expect(page.locator('.blocksuite-portal')).toBeHidden();
|
||||
await expect(page.locator('note-display-mode-panel')).toBeVisible();
|
||||
|
||||
const colorBtn = toolbar.getByRole('button', {
|
||||
name: 'Background',
|
||||
});
|
||||
|
||||
await colorBtn.hover();
|
||||
await expect(page.locator('.blocksuite-portal')).toBeVisible();
|
||||
|
||||
await colorBtn.click();
|
||||
await expect(page.locator('.blocksuite-portal')).toBeHidden();
|
||||
await expect(page.locator('note-display-mode-panel')).toBeHidden();
|
||||
await expect(page.locator('edgeless-color-panel')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should be hidden when resizing element', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 210, y: 110 }, { x: 310, y: 210 });
|
||||
await page.mouse.click(220, 120);
|
||||
|
||||
const toolbar = locatorComponentToolbar(page);
|
||||
await expect(toolbar).toBeVisible();
|
||||
|
||||
await resizeElementByHandle(page, { x: 400, y: 300 }, 'top-left', 30);
|
||||
|
||||
await page.mouse.move(450, 300);
|
||||
await expect(toolbar).toBeEmpty();
|
||||
|
||||
await page.mouse.move(320, 220);
|
||||
await expect(toolbar).toBeEmpty();
|
||||
|
||||
await page.mouse.up();
|
||||
await expect(toolbar).toBeVisible();
|
||||
});
|
||||
49
blocksuite/tests-legacy/edgeless/eraser.spec.ts
Normal file
49
blocksuite/tests-legacy/edgeless/eraser.spec.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { click } from '../utils/actions/click.js';
|
||||
import { dragBetweenCoords } from '../utils/actions/drag.js';
|
||||
import {
|
||||
addBasicRectShapeElement,
|
||||
deleteAll,
|
||||
getNoteBoundBoxInEdgeless,
|
||||
setEdgelessTool,
|
||||
switchEditorMode,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
} from '../utils/actions/misc.js';
|
||||
import {
|
||||
assertBlockCount,
|
||||
assertEdgelessNonSelectedRect,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('erase shape', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await deleteAll(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 0, y: 0 }, { x: 100, y: 100 });
|
||||
await setEdgelessTool(page, 'eraser');
|
||||
|
||||
await dragBetweenCoords(page, { x: 50, y: 150 }, { x: 50, y: 50 });
|
||||
await click(page, { x: 50, y: 50 });
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test('erase note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await assertBlockCount(page, 'edgeless-note', 1);
|
||||
|
||||
await setEdgelessTool(page, 'eraser');
|
||||
const box = await getNoteBoundBoxInEdgeless(page, noteId);
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: 0, y: 0 },
|
||||
{ x: box.x + 10, y: box.y + 10 }
|
||||
);
|
||||
await assertBlockCount(page, 'edgeless-note', 0);
|
||||
});
|
||||
157
blocksuite/tests-legacy/edgeless/frame/clipboard.spec.ts
Normal file
157
blocksuite/tests-legacy/edgeless/frame/clipboard.spec.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
import { clickView, moveView } from 'utils/actions/click.js';
|
||||
import {
|
||||
autoFit,
|
||||
createFrame as _createFrame,
|
||||
createShapeElement,
|
||||
deleteAll,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
getAllSortedIds,
|
||||
getFirstContainerId,
|
||||
getIds,
|
||||
Shape,
|
||||
shiftClickView,
|
||||
triggerComponentToolbarAction,
|
||||
zoomResetByKeyboard,
|
||||
} from 'utils/actions/edgeless.js';
|
||||
import {
|
||||
copyByKeyboard,
|
||||
pasteByKeyboard,
|
||||
pressBackspace,
|
||||
pressEscape,
|
||||
} from 'utils/actions/keyboard.js';
|
||||
import { assertContainerOfElements } from 'utils/asserts.js';
|
||||
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
const createFrame = async (
|
||||
page: Page,
|
||||
coord1: [number, number],
|
||||
coord2: [number, number]
|
||||
) => {
|
||||
await _createFrame(page, coord1, coord2);
|
||||
await autoFit(page);
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
});
|
||||
|
||||
test.describe('frame copy and paste', () => {
|
||||
test('copy of frame should keep relationship of child elements', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [450, 450]);
|
||||
await createShapeElement(page, [200, 200], [300, 300], Shape.Square);
|
||||
|
||||
const frameTitle = page.locator('affine-frame-title');
|
||||
|
||||
await pressEscape(page);
|
||||
await frameTitle.click();
|
||||
await copyByKeyboard(page);
|
||||
await deleteAll(page);
|
||||
await moveView(page, [500, 500]); // center copy
|
||||
await pasteByKeyboard(page);
|
||||
|
||||
const frameId = await getFirstContainerId(page);
|
||||
const shapeId = (await getAllSortedIds(page)).filter(id => id !== frameId);
|
||||
await assertContainerOfElements(page, shapeId, frameId);
|
||||
});
|
||||
|
||||
test('copy of frame by alt/option dragging should keep relationship of child elements', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [450, 450]);
|
||||
await createShapeElement(page, [200, 200], [300, 300], Shape.Square);
|
||||
await createShapeElement(page, [250, 250], [350, 350], Shape.Square);
|
||||
await createShapeElement(page, [300, 300], [400, 400], Shape.Square);
|
||||
await pressEscape(page);
|
||||
|
||||
const frameTitles = page.locator('affine-frame-title');
|
||||
|
||||
await shiftClickView(page, [260, 260]);
|
||||
await shiftClickView(page, [310, 310]);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await pressEscape(page);
|
||||
|
||||
await frameTitles.nth(0).click();
|
||||
await page.keyboard.down('Alt');
|
||||
await dragBetweenViewCoords(page, [60, 60], [460, 460]);
|
||||
await page.keyboard.up('Alt');
|
||||
await pressEscape(page);
|
||||
|
||||
await frameTitles.nth(0).click({ modifiers: ['Shift'] });
|
||||
await shiftClickView(page, [250, 250]);
|
||||
await shiftClickView(page, [350, 350]);
|
||||
await pressBackspace(page); // remove original elements
|
||||
|
||||
const frameId = await getFirstContainerId(page);
|
||||
const groupId = await getFirstContainerId(page, [frameId]);
|
||||
const shapeIds = (await getIds(page)).filter(
|
||||
id => ![frameId, groupId].includes(id)
|
||||
);
|
||||
|
||||
await assertContainerOfElements(page, [groupId], frameId);
|
||||
await assertContainerOfElements(page, [shapeIds[0]], frameId);
|
||||
await assertContainerOfElements(page, [shapeIds[1]], groupId);
|
||||
await assertContainerOfElements(page, [shapeIds[2]], groupId);
|
||||
});
|
||||
|
||||
test('duplicate element in frame', async ({ page }) => {
|
||||
await createFrame(page, [50, 50], [450, 450]);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await pressEscape(page);
|
||||
|
||||
const frameTitles = page.locator('affine-frame-title');
|
||||
|
||||
await frameTitles.nth(0).click();
|
||||
await page.locator('edgeless-more-button').click();
|
||||
await page.locator('editor-menu-action', { hasText: 'Duplicate' }).click();
|
||||
await pressEscape(page);
|
||||
|
||||
await frameTitles.nth(0).click();
|
||||
await shiftClickView(page, [150, 150]);
|
||||
await pressBackspace(page); // remove original elements
|
||||
|
||||
const frameId = await getFirstContainerId(page);
|
||||
const shapeIds = (await getIds(page)).filter(id => id !== frameId);
|
||||
await assertContainerOfElements(page, shapeIds, frameId);
|
||||
});
|
||||
|
||||
test('copy of element by alt/option dragging in frame should belong to frame', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [450, 450]);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await pressEscape(page);
|
||||
|
||||
await clickView(page, [150, 150]);
|
||||
await page.keyboard.down('Alt');
|
||||
await dragBetweenViewCoords(page, [150, 150], [250, 250]);
|
||||
await page.keyboard.up('Alt');
|
||||
|
||||
const frameId = await getFirstContainerId(page);
|
||||
const shapeIds = (await getIds(page)).filter(id => id !== frameId);
|
||||
await assertContainerOfElements(page, shapeIds, frameId);
|
||||
});
|
||||
|
||||
test('copy of element by alt/option dragging out of frame should not belong to frame', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [450, 450]);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await pressEscape(page);
|
||||
|
||||
await clickView(page, [150, 150]);
|
||||
await page.keyboard.down('Alt');
|
||||
await dragBetweenViewCoords(page, [150, 150], [550, 550]);
|
||||
await page.keyboard.up('Alt');
|
||||
|
||||
const frameId = await getFirstContainerId(page);
|
||||
const shapeIds = (await getIds(page)).filter(id => id !== frameId);
|
||||
await assertContainerOfElements(page, [shapeIds[0]], frameId);
|
||||
await assertContainerOfElements(page, [shapeIds[1]], null);
|
||||
});
|
||||
});
|
||||
224
blocksuite/tests-legacy/edgeless/frame/frame-mindmap.spec.ts
Normal file
224
blocksuite/tests-legacy/edgeless/frame/frame-mindmap.spec.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
import { clickView } from 'utils/actions/click.js';
|
||||
import {
|
||||
createFrame,
|
||||
dragBetweenViewCoords as _dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
getFirstContainerId,
|
||||
getSelectedBound,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
zoomResetByKeyboard,
|
||||
} from 'utils/actions/edgeless.js';
|
||||
import { pressEscape } from 'utils/actions/keyboard.js';
|
||||
import { waitNextFrame } from 'utils/actions/misc.js';
|
||||
import { assertContainerOfElements } from 'utils/asserts.js';
|
||||
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
const dragBetweenViewCoords = async (
|
||||
page: Page,
|
||||
start: number[],
|
||||
end: number[]
|
||||
) => {
|
||||
// dragging slowly may drop frame if mindmap is existed, so for test we drag quickly
|
||||
await _dragBetweenViewCoords(page, start, end, { steps: 2 });
|
||||
await waitNextFrame(page);
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
});
|
||||
|
||||
test('drag root node of mindmap into frame partially, then drag root node of mindmap out.', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [550, 550]);
|
||||
await pressEscape(page);
|
||||
const frameId = await getFirstContainerId(page);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'addMindmap');
|
||||
const mindmapId = await getFirstContainerId(page, [frameId]);
|
||||
|
||||
// drag in
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
await clickView(page, [
|
||||
mindmapBound[0] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
]);
|
||||
await dragBetweenViewCoords(
|
||||
page,
|
||||
[mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]],
|
||||
[100, 100]
|
||||
);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], frameId);
|
||||
|
||||
// drag out
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
await clickView(page, [
|
||||
mindmapBound[0] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
]);
|
||||
await dragBetweenViewCoords(
|
||||
page,
|
||||
[mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]],
|
||||
[-100, -100]
|
||||
);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], null);
|
||||
});
|
||||
|
||||
test('drag root node of mindmap into frame fully, then drag root node of mindmap out.', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [550, 550]);
|
||||
const frameId = await getFirstContainerId(page);
|
||||
await pressEscape(page);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'addMindmap');
|
||||
const mindmapId = await getFirstContainerId(page, [frameId]);
|
||||
|
||||
// drag in
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
|
||||
await clickView(page, [
|
||||
mindmapBound[0] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
]);
|
||||
await dragBetweenViewCoords(
|
||||
page,
|
||||
[mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]],
|
||||
[100, 200]
|
||||
);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], frameId);
|
||||
|
||||
// drag out
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
await clickView(page, [
|
||||
mindmapBound[0] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
]);
|
||||
await dragBetweenViewCoords(
|
||||
page,
|
||||
[mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]],
|
||||
[-100, -100]
|
||||
);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], null);
|
||||
});
|
||||
|
||||
test('drag whole mindmap into frame, then drag root node of mindmap out.', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [550, 550]);
|
||||
const frameId = await getFirstContainerId(page);
|
||||
await pressEscape(page);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'addMindmap');
|
||||
const mindmapId = await getFirstContainerId(page, [frameId]);
|
||||
|
||||
// drag in
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
const rootNodePos = [
|
||||
mindmapBound[0] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
];
|
||||
await dragBetweenViewCoords(page, rootNodePos, [
|
||||
rootNodePos[0] - 20,
|
||||
rootNodePos[1] + 200,
|
||||
]);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], frameId);
|
||||
|
||||
// drag out
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
const rootNodePos = [
|
||||
mindmapBound[0] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
];
|
||||
|
||||
await dragBetweenViewCoords(page, rootNodePos, [-100, -100]);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], null);
|
||||
});
|
||||
|
||||
test('add mindmap into frame, then drag root node of mindmap out.', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [550, 550]);
|
||||
const frameId = await getFirstContainerId(page);
|
||||
await pressEscape(page);
|
||||
|
||||
const button = page.locator('edgeless-mindmap-tool-button');
|
||||
await button.click();
|
||||
await toViewCoord(page, [100, 200]);
|
||||
await clickView(page, [100, 200]);
|
||||
const mindmapId = await getFirstContainerId(page, [frameId]);
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], frameId);
|
||||
|
||||
// drag out
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
pressEscape(page);
|
||||
await clickView(page, [
|
||||
mindmapBound[0] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
]);
|
||||
await dragBetweenViewCoords(
|
||||
page,
|
||||
[mindmapBound[0] + 10, mindmapBound[1] + 0.5 * mindmapBound[3]],
|
||||
[-20, -20]
|
||||
);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], null);
|
||||
});
|
||||
|
||||
test('add mindmap out of frame and add new node in frame then drag frame', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [500, 50], [1000, 550]);
|
||||
await pressEscape(page);
|
||||
|
||||
const button = page.locator('edgeless-mindmap-tool-button');
|
||||
await button.click();
|
||||
await toViewCoord(page, [20, 200]);
|
||||
await clickView(page, [20, 200]);
|
||||
await waitNextFrame(page, 100);
|
||||
const mindmapId = await getFirstContainerId(page);
|
||||
|
||||
// add new node
|
||||
{
|
||||
const mindmapBound = await getSelectedBound(page);
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 500);
|
||||
await clickView(page, [
|
||||
mindmapBound[2] - 50,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
]);
|
||||
await waitNextFrame(page, 500);
|
||||
await clickView(page, [
|
||||
mindmapBound[2] + 10,
|
||||
mindmapBound[1] + 0.5 * mindmapBound[3],
|
||||
]);
|
||||
await pressEscape(page, 2);
|
||||
}
|
||||
|
||||
await assertContainerOfElements(page, [mindmapId], null);
|
||||
});
|
||||
158
blocksuite/tests-legacy/edgeless/frame/frame-title.spec.ts
Normal file
158
blocksuite/tests-legacy/edgeless/frame/frame-title.spec.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
import {
|
||||
addNote,
|
||||
autoFit,
|
||||
createFrame as _createFrame,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
getFrameTitle,
|
||||
zoomOutByKeyboard,
|
||||
zoomResetByKeyboard,
|
||||
} from 'utils/actions/edgeless.js';
|
||||
import {
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
type,
|
||||
} from 'utils/actions/keyboard.js';
|
||||
import { waitNextFrame } from 'utils/actions/misc.js';
|
||||
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
const createFrame = async (
|
||||
page: Page,
|
||||
coord1: [number, number],
|
||||
coord2: [number, number]
|
||||
) => {
|
||||
const frame = await _createFrame(page, coord1, coord2);
|
||||
await autoFit(page);
|
||||
return frame;
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
});
|
||||
|
||||
const enterFrameTitleEditor = async (page: Page) => {
|
||||
const frameTitle = page.locator('affine-frame-title');
|
||||
await frameTitle.dblclick();
|
||||
|
||||
const frameTitleEditor = page.locator('edgeless-frame-title-editor');
|
||||
await frameTitleEditor.waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
return frameTitleEditor;
|
||||
};
|
||||
|
||||
test.describe('frame title rendering', () => {
|
||||
test('frame title should be displayed', async ({ page }) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
await expect(frameTitle).toBeVisible();
|
||||
await expect(frameTitle).toHaveText('Frame 1');
|
||||
});
|
||||
|
||||
test('frame title should be rendered on the top', async ({ page }) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
await expect(frameTitle).toBeVisible();
|
||||
|
||||
const frameTitleBounding = await frameTitle.boundingBox();
|
||||
expect(frameTitleBounding).not.toBeNull();
|
||||
if (!frameTitleBounding) return;
|
||||
|
||||
const frameTitleCenter = [
|
||||
frameTitleBounding.x + frameTitleBounding.width / 2,
|
||||
frameTitleBounding.y + frameTitleBounding.height / 2,
|
||||
];
|
||||
|
||||
await addNote(page, '', frameTitleCenter[0], frameTitleCenter[1]);
|
||||
await pressEscape(page, 3);
|
||||
await waitNextFrame(page, 500);
|
||||
|
||||
try {
|
||||
// if the frame title is rendered on the top, it should be clickable
|
||||
await frameTitle.click();
|
||||
} catch {
|
||||
expect(true, 'frame title should be rendered on the top').toBeFalsy();
|
||||
}
|
||||
});
|
||||
|
||||
test('should not display frame title component when title is empty', async ({
|
||||
page,
|
||||
}) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
await enterFrameTitleEditor(page);
|
||||
|
||||
await pressBackspace(page);
|
||||
await pressEnter(page);
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
await expect(frameTitle).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('frame title editing', () => {
|
||||
test('edit frame title by db-click title', async ({ page }) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
|
||||
await enterFrameTitleEditor(page);
|
||||
|
||||
await type(page, 'ABC');
|
||||
await pressEnter(page);
|
||||
await expect(frameTitle).toHaveText('ABC');
|
||||
});
|
||||
|
||||
test('frame title can be edited repeatedly', async ({ page }) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
|
||||
await enterFrameTitleEditor(page);
|
||||
await type(page, 'ABC');
|
||||
await pressEnter(page);
|
||||
|
||||
await enterFrameTitleEditor(page);
|
||||
await type(page, 'DEF');
|
||||
await pressEnter(page);
|
||||
await expect(frameTitle).toHaveText('DEF');
|
||||
});
|
||||
|
||||
test('edit frame after zoom', async ({ page }) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
|
||||
await zoomOutByKeyboard(page);
|
||||
await enterFrameTitleEditor(page);
|
||||
await type(page, 'ABC');
|
||||
await pressEnter(page);
|
||||
await expect(frameTitle).toHaveText('ABC');
|
||||
});
|
||||
|
||||
test('edit frame title after drag', async ({ page }) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
await dragBetweenViewCoords(page, [50 + 10, 50 + 10], [50 + 20, 50 + 20]);
|
||||
|
||||
await enterFrameTitleEditor(page);
|
||||
await type(page, 'ABC');
|
||||
await pressEnter(page);
|
||||
await expect(frameTitle).toHaveText('ABC');
|
||||
});
|
||||
|
||||
test('blur unmount frame editor', async ({ page }) => {
|
||||
await createFrame(page, [50, 50], [150, 150]);
|
||||
const frameTitleEditor = await enterFrameTitleEditor(page);
|
||||
await page.mouse.click(10, 10);
|
||||
await expect(frameTitleEditor).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('enter unmount frame editor', async ({ page }) => {
|
||||
await createFrame(page, [50, 50], [150, 150]);
|
||||
const frameTitleEditor = await enterFrameTitleEditor(page);
|
||||
await pressEnter(page);
|
||||
await expect(frameTitleEditor).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
414
blocksuite/tests-legacy/edgeless/frame/frame.spec.ts
Normal file
414
blocksuite/tests-legacy/edgeless/frame/frame.spec.ts
Normal file
@@ -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']);
|
||||
});
|
||||
64
blocksuite/tests-legacy/edgeless/frame/layer.spec.ts
Normal file
64
blocksuite/tests-legacy/edgeless/frame/layer.spec.ts
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
158
blocksuite/tests-legacy/edgeless/frame/selection.spec.ts
Normal file
158
blocksuite/tests-legacy/edgeless/frame/selection.spec.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
import { click, clickView, dblclickView } from 'utils/actions/click.js';
|
||||
import {
|
||||
addNote,
|
||||
autoFit,
|
||||
createFrame as _createFrame,
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
getFrameTitle,
|
||||
getSelectedBoundCount,
|
||||
getSelectedIds,
|
||||
Shape,
|
||||
toViewCoord,
|
||||
zoomResetByKeyboard,
|
||||
} from 'utils/actions/edgeless.js';
|
||||
import {
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
pressEscape,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
} from 'utils/actions/keyboard.js';
|
||||
import { waitNextFrame } from 'utils/actions/misc.js';
|
||||
import {
|
||||
assertEdgelessCanvasText,
|
||||
assertRichTexts,
|
||||
assertSelectedBound,
|
||||
} from 'utils/asserts.js';
|
||||
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
const createFrame = async (
|
||||
page: Page,
|
||||
coord1: [number, number],
|
||||
coord2: [number, number]
|
||||
) => {
|
||||
const frame = await _createFrame(page, coord1, coord2);
|
||||
await autoFit(page);
|
||||
return frame;
|
||||
};
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
});
|
||||
|
||||
test.describe('frame selection', () => {
|
||||
test('frame can not be selected by click blank area of frame if it has title', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [150, 150]);
|
||||
await pressEscape(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
|
||||
await clickView(page, [100, 100]);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
});
|
||||
|
||||
test('frame can selected by click blank area of frame if it has not title', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [150, 150]);
|
||||
await pressEscape(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
|
||||
await page.locator('affine-frame-title').dblclick();
|
||||
await pressBackspace(page);
|
||||
await pressEnter(page);
|
||||
|
||||
await clickView(page, [100, 100]);
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
});
|
||||
|
||||
test('frame can be selected by click frame title', async ({ page }) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
await pressEscape(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
await frameTitle.click();
|
||||
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
await assertSelectedBound(page, [50, 50, 100, 100]);
|
||||
});
|
||||
|
||||
test('frame can be selected by click frame title when a note overlap on it', async ({
|
||||
page,
|
||||
}) => {
|
||||
const frame = await createFrame(page, [50, 50], [150, 150]);
|
||||
await pressEscape(page);
|
||||
|
||||
const frameTitle = getFrameTitle(page, frame);
|
||||
const frameTitleBox = await frameTitle.boundingBox();
|
||||
expect(frameTitleBox).not.toBeNull();
|
||||
if (frameTitleBox === null) return;
|
||||
|
||||
const frameTitleCenter = {
|
||||
x: frameTitleBox.x + frameTitleBox.width / 2,
|
||||
y: frameTitleBox.y + frameTitleBox.height / 2,
|
||||
};
|
||||
|
||||
await addNote(page, '', frameTitleCenter.x - 10, frameTitleCenter.y);
|
||||
await pressEscape(page, 3);
|
||||
await waitNextFrame(page, 500);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
|
||||
await click(page, frameTitleCenter);
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
const selectedIds = await getSelectedIds(page);
|
||||
expect(selectedIds.length).toBe(1);
|
||||
expect(selectedIds[0]).toBe(frame);
|
||||
});
|
||||
|
||||
test('shape inside frame can be selected and edited', async ({ page }) => {
|
||||
await createFrame(page, [50, 50], [150, 150]);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await pressEscape(page);
|
||||
|
||||
await clickView(page, [150, 150]);
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
await assertSelectedBound(page, [100, 100, 100, 100]);
|
||||
|
||||
await dblclickView(page, [150, 150]);
|
||||
await type(page, 'hello');
|
||||
await assertEdgelessCanvasText(page, 'hello');
|
||||
});
|
||||
|
||||
test('dom inside frame can be selected and edited', async ({ page }) => {
|
||||
await createFrame(page, [50, 50], [150, 150]);
|
||||
const noteCoord = await toViewCoord(page, [100, 100]);
|
||||
await addNote(page, '', noteCoord[0], noteCoord[1]);
|
||||
await page.mouse.click(noteCoord[0] - 80, noteCoord[1]);
|
||||
|
||||
await dblclickView(page, [150, 150]);
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['hello']);
|
||||
});
|
||||
|
||||
test('element in frame should not be selected when frame is selected by drag or Cmd/Ctrl + A', async ({
|
||||
page,
|
||||
}) => {
|
||||
await createFrame(page, [50, 50], [200, 200]);
|
||||
await createShapeElement(page, [100, 100], [150, 150], Shape.Square);
|
||||
await pressEscape(page);
|
||||
|
||||
await dragBetweenViewCoords(page, [0, 0], [250, 250]);
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
await assertSelectedBound(page, [50, 50, 150, 150]);
|
||||
|
||||
await pressEscape(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(0);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
expect(await getSelectedBoundCount(page)).toBe(1);
|
||||
await assertSelectedBound(page, [50, 50, 150, 150]);
|
||||
});
|
||||
});
|
||||
152
blocksuite/tests-legacy/edgeless/group/clipboard.spec.ts
Normal file
152
blocksuite/tests-legacy/edgeless/group/clipboard.spec.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
copyByKeyboard,
|
||||
createConnectorElement,
|
||||
createNote,
|
||||
createShapeElement,
|
||||
decreaseZoomLevel,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
edgelessCommonSetup,
|
||||
getAllSortedIds,
|
||||
getFirstContainerId,
|
||||
pasteByKeyboard,
|
||||
selectAllByKeyboard,
|
||||
Shape,
|
||||
toViewCoord,
|
||||
triggerComponentToolbarAction,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/index.js';
|
||||
import {
|
||||
assertContainerChildCount,
|
||||
assertContainerIds,
|
||||
} from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test.describe('clipboard', () => {
|
||||
test('copy and paste group', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 0], [200, 100], Shape.Square);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const originGroupId = await getFirstContainerId(page);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
await waitNextFrame(page, 100);
|
||||
const move = await toViewCoord(page, [100, -50]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await waitNextFrame(page, 1000);
|
||||
await pasteByKeyboard(page, false);
|
||||
const copyedGroupId = await getFirstContainerId(page, [originGroupId]);
|
||||
|
||||
await assertContainerIds(page, {
|
||||
[originGroupId]: 2,
|
||||
[copyedGroupId]: 2,
|
||||
null: 2,
|
||||
});
|
||||
await assertContainerChildCount(page, originGroupId, 2);
|
||||
await assertContainerChildCount(page, copyedGroupId, 2);
|
||||
});
|
||||
|
||||
test('copy and paste group with connector', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 0], [200, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 50], [200, 50]);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const originGroupId = await getFirstContainerId(page);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
await waitNextFrame(page, 100);
|
||||
const move = await toViewCoord(page, [100, -50]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await waitNextFrame(page, 1000);
|
||||
await pasteByKeyboard(page, false);
|
||||
const copyedGroupId = await getFirstContainerId(page, [originGroupId]);
|
||||
|
||||
await assertContainerIds(page, {
|
||||
[originGroupId]: 3,
|
||||
[copyedGroupId]: 3,
|
||||
null: 2,
|
||||
});
|
||||
await assertContainerChildCount(page, originGroupId, 3);
|
||||
await assertContainerChildCount(page, copyedGroupId, 3);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('group clipboard', () => {
|
||||
test('copy and paste group with shape and note inside', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [100, -100]);
|
||||
await page.mouse.click(10, 50);
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
const originIds = await getAllSortedIds(page);
|
||||
expect(originIds.length).toBe(3);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(6);
|
||||
});
|
||||
|
||||
test('copy and paste group with group inside', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
await createNote(page, [100, -100]);
|
||||
await page.mouse.click(10, 50);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'createGroupOnMoreOption');
|
||||
|
||||
const originIds = await getAllSortedIds(page);
|
||||
expect(originIds.length).toBe(5);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(10);
|
||||
});
|
||||
|
||||
test('copy and paste group with frame inside', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createNote(page, [100, -100]);
|
||||
await page.mouse.click(10, 50);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addFrame');
|
||||
|
||||
await decreaseZoomLevel(page);
|
||||
await createShapeElement(page, [700, 0], [800, 100], Shape.Square);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
const originIds = await getAllSortedIds(page);
|
||||
expect(originIds.length).toBe(5);
|
||||
|
||||
await copyByKeyboard(page);
|
||||
const move = await toViewCoord(page, [250, 250]);
|
||||
await page.mouse.move(move[0], move[1]);
|
||||
await page.mouse.click(move[0], move[1]);
|
||||
await pasteByKeyboard(page, true);
|
||||
await waitNextFrame(page, 500);
|
||||
const sortedIds = await getAllSortedIds(page);
|
||||
expect(sortedIds.length).toBe(10);
|
||||
});
|
||||
});
|
||||
123
blocksuite/tests-legacy/edgeless/group/group-and-ungroup.spec.ts
Normal file
123
blocksuite/tests-legacy/edgeless/group/group-and-ungroup.spec.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
captureHistory,
|
||||
clickView,
|
||||
createShapeElement,
|
||||
edgelessCommonSetup,
|
||||
getFirstContainerId,
|
||||
getIds,
|
||||
redoByKeyboard,
|
||||
selectAllByKeyboard,
|
||||
Shape,
|
||||
shiftClickView,
|
||||
toIdCountMap,
|
||||
triggerComponentToolbarAction,
|
||||
undoByKeyboard,
|
||||
} from '../../utils/actions/index.js';
|
||||
import {
|
||||
assertContainerChildCount,
|
||||
assertContainerChildIds,
|
||||
assertContainerIds,
|
||||
assertSelectedBound,
|
||||
} from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
let initShapes: string[] = [];
|
||||
async function init(page: Page) {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 0], [200, 100], Shape.Square);
|
||||
initShapes = await getIds(page);
|
||||
}
|
||||
|
||||
test.describe('group and ungroup in group', () => {
|
||||
let outterGroupId: string;
|
||||
let newAddedShape: string;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
newAddedShape = (await getIds(page)).filter(
|
||||
id => !initShapes.includes(id)
|
||||
)[0];
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
outterGroupId = await getFirstContainerId(page);
|
||||
});
|
||||
|
||||
test('group in group', async ({ page }) => {
|
||||
await clickView(page, [50, 50]);
|
||||
await shiftClickView(page, [150, 50]);
|
||||
await captureHistory(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const groupId = await getFirstContainerId(page, [outterGroupId]);
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
[outterGroupId]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
await assertContainerChildCount(page, outterGroupId, 2);
|
||||
|
||||
// undo the creation
|
||||
await undoByKeyboard(page);
|
||||
await assertContainerIds(page, {
|
||||
[outterGroupId]: 3,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, outterGroupId, 3);
|
||||
|
||||
// redo the creation
|
||||
await redoByKeyboard(page);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
[outterGroupId]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
await assertContainerChildCount(page, outterGroupId, 2);
|
||||
});
|
||||
|
||||
test('ungroup in group', async ({ page }) => {
|
||||
await clickView(page, [50, 50]);
|
||||
await shiftClickView(page, [150, 50]);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await captureHistory(page);
|
||||
const groupId = await getFirstContainerId(page, [outterGroupId]);
|
||||
await triggerComponentToolbarAction(page, 'ungroup');
|
||||
await assertContainerIds(page, { [outterGroupId]: 3, null: 1 });
|
||||
await assertContainerChildIds(
|
||||
page,
|
||||
toIdCountMap(await getIds(page, true)),
|
||||
outterGroupId
|
||||
);
|
||||
|
||||
// undo, group should in group again
|
||||
await undoByKeyboard(page);
|
||||
await assertContainerIds(page, {
|
||||
[outterGroupId]: 2,
|
||||
[groupId]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildIds(page, toIdCountMap(initShapes), groupId);
|
||||
await assertContainerChildIds(
|
||||
page,
|
||||
{
|
||||
[groupId]: 1,
|
||||
[newAddedShape]: 1,
|
||||
},
|
||||
outterGroupId
|
||||
);
|
||||
|
||||
// redo, group should be ungroup again
|
||||
await redoByKeyboard(page);
|
||||
await assertContainerIds(page, { [outterGroupId]: 3, null: 1 });
|
||||
await assertContainerChildIds(
|
||||
page,
|
||||
toIdCountMap(await getIds(page, true)),
|
||||
outterGroupId
|
||||
);
|
||||
});
|
||||
});
|
||||
260
blocksuite/tests-legacy/edgeless/group/group.spec.ts
Normal file
260
blocksuite/tests-legacy/edgeless/group/group.spec.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import { clickView } from '../../utils/actions/click.js';
|
||||
import {
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
getFirstContainerId,
|
||||
Shape,
|
||||
shiftClickView,
|
||||
triggerComponentToolbarAction,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
pressBackspace,
|
||||
redoByKeyboard,
|
||||
selectAllByKeyboard,
|
||||
SHORT_KEY,
|
||||
undoByKeyboard,
|
||||
} from '../../utils/actions/keyboard.js';
|
||||
import { captureHistory } from '../../utils/actions/misc.js';
|
||||
import {
|
||||
assertCanvasElementsCount,
|
||||
assertContainerChildCount,
|
||||
assertContainerIds,
|
||||
assertEdgelessNonSelectedRect,
|
||||
assertSelectedBound,
|
||||
} from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
export const GROUP_ROOT_ID = 'GROUP_ROOT';
|
||||
|
||||
test.describe('group', () => {
|
||||
async function init(page: Page) {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 0], [200, 100], Shape.Square);
|
||||
}
|
||||
|
||||
test.describe('group create', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
});
|
||||
|
||||
test('create group button not show when single select', async ({
|
||||
page,
|
||||
}) => {
|
||||
await clickView(page, [50, 50]);
|
||||
await expect(
|
||||
page.locator('edgeless-element-toolbar-widget')
|
||||
).toBeVisible();
|
||||
await expect(page.locator('edgeless-add-group-button')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('create button show up when multi select', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await expect(page.locator('edgeless-add-group-button')).toBeVisible();
|
||||
});
|
||||
|
||||
test('create group by component toolbar', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
});
|
||||
|
||||
test('create group by shortcut mod + G', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+g`);
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
});
|
||||
|
||||
test('create group and undo, redo', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await captureHistory(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+g`);
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
await undoByKeyboard(page);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
await redoByKeyboard(page);
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('ungroup', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
});
|
||||
|
||||
test('ungroup by component toolbar', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
await triggerComponentToolbarAction(page, 'ungroup');
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test('ungroup by shortcut mod + shift + G', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
await page.keyboard.press(`${SHORT_KEY}+Shift+g`);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test('ungroup and undo, redo', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
await captureHistory(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+Shift+g`);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
await undoByKeyboard(page);
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
await redoByKeyboard(page);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('drag group', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
});
|
||||
|
||||
test('drag group to move', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await dragBetweenViewCoords(page, [100, 50], [110, 50]);
|
||||
await assertSelectedBound(page, [10, 0, 200, 100]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('select', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
});
|
||||
|
||||
test('select group by click', async ({ page }) => {
|
||||
await clickView(page, [300, -100]);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
await clickView(page, [50, 50]);
|
||||
await assertSelectedBound(page, [0, 0, 200, 100]);
|
||||
});
|
||||
|
||||
test('select sub-element by first select group', async ({ page }) => {
|
||||
await clickView(page, [50, 50]);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
});
|
||||
|
||||
test('select element when enter gorup', async ({ page }) => {
|
||||
await clickView(page, [50, 50]);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
await clickView(page, [150, 50]);
|
||||
await assertSelectedBound(page, [100, 0, 100, 100]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('delete', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
});
|
||||
|
||||
test('delete root group', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const groupId = await getFirstContainerId(page);
|
||||
await captureHistory(page);
|
||||
await pressBackspace(page);
|
||||
await assertCanvasElementsCount(page, 0);
|
||||
|
||||
// undo the delete
|
||||
await undoByKeyboard(page);
|
||||
await assertCanvasElementsCount(page, 3);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
|
||||
// redo the delete
|
||||
await redoByKeyboard(page);
|
||||
await assertCanvasElementsCount(page, 0);
|
||||
});
|
||||
|
||||
test('delete sub-element in group', async ({ page }) => {
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const groupId = await getFirstContainerId(page);
|
||||
await captureHistory(page);
|
||||
await clickView(page, [50, 50]);
|
||||
await pressBackspace(page);
|
||||
|
||||
await assertCanvasElementsCount(page, 2);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 1,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 1);
|
||||
|
||||
// undo the delete
|
||||
await undoByKeyboard(page);
|
||||
await assertCanvasElementsCount(page, 3);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
|
||||
// redo the delete
|
||||
await redoByKeyboard(page);
|
||||
await assertCanvasElementsCount(page, 2);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 1,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 1);
|
||||
});
|
||||
|
||||
test('delete group in group', async ({ page }) => {
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const firstGroup = await getFirstContainerId(page);
|
||||
await clickView(page, [50, 50]);
|
||||
await shiftClickView(page, [150, 50]);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
const secondGroup = await getFirstContainerId(page, [firstGroup]);
|
||||
await captureHistory(page);
|
||||
|
||||
// delete group in group
|
||||
await pressBackspace(page);
|
||||
await assertCanvasElementsCount(page, 2);
|
||||
await assertContainerIds(page, {
|
||||
[firstGroup]: 1,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, firstGroup, 1);
|
||||
|
||||
// undo the delete
|
||||
await undoByKeyboard(page);
|
||||
await assertCanvasElementsCount(page, 5);
|
||||
await assertContainerIds(page, {
|
||||
[firstGroup]: 2,
|
||||
[secondGroup]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, firstGroup, 2);
|
||||
await assertContainerChildCount(page, secondGroup, 2);
|
||||
|
||||
// redo the delete
|
||||
await redoByKeyboard(page);
|
||||
await assertCanvasElementsCount(page, 2);
|
||||
await assertContainerIds(page, {
|
||||
[firstGroup]: 1,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, firstGroup, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
116
blocksuite/tests-legacy/edgeless/group/release.spec.ts
Normal file
116
blocksuite/tests-legacy/edgeless/group/release.spec.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
captureHistory,
|
||||
clickView,
|
||||
createShapeElement,
|
||||
edgelessCommonSetup,
|
||||
getFirstContainerId,
|
||||
redoByKeyboard,
|
||||
selectAllByKeyboard,
|
||||
Shape,
|
||||
shiftClickView,
|
||||
triggerComponentToolbarAction,
|
||||
undoByKeyboard,
|
||||
} from '../../utils/actions/index.js';
|
||||
import {
|
||||
assertContainerChildCount,
|
||||
assertContainerIds,
|
||||
assertSelectedBound,
|
||||
} from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
async function init(page: Page) {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 0], [200, 100], Shape.Square);
|
||||
}
|
||||
|
||||
test.describe('release from group', () => {
|
||||
let outterGroupId: string;
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
await createShapeElement(page, [200, 0], [300, 100], Shape.Square);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
|
||||
outterGroupId = await getFirstContainerId(page);
|
||||
});
|
||||
|
||||
test('release element from group', async ({ page }) => {
|
||||
await clickView(page, [50, 50]);
|
||||
await captureHistory(page);
|
||||
await triggerComponentToolbarAction(page, 'releaseFromGroup');
|
||||
await assertContainerIds(page, {
|
||||
[outterGroupId]: 2,
|
||||
null: 2,
|
||||
});
|
||||
await assertContainerChildCount(page, outterGroupId, 2);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
|
||||
// undo the release
|
||||
await undoByKeyboard(page);
|
||||
await assertContainerIds(page, {
|
||||
[outterGroupId]: 3,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, outterGroupId, 3);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
|
||||
// redo the release
|
||||
await redoByKeyboard(page);
|
||||
await assertContainerIds(page, {
|
||||
[outterGroupId]: 2,
|
||||
null: 2,
|
||||
});
|
||||
await assertContainerChildCount(page, outterGroupId, 2);
|
||||
await assertSelectedBound(page, [0, 0, 100, 100]);
|
||||
});
|
||||
|
||||
test('release group from group', async ({ page }) => {
|
||||
await clickView(page, [50, 50]);
|
||||
await shiftClickView(page, [150, 50]);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
await captureHistory(page);
|
||||
const groupId = await getFirstContainerId(page, [outterGroupId]);
|
||||
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
[outterGroupId]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
await assertContainerChildCount(page, outterGroupId, 2);
|
||||
|
||||
// release group from group
|
||||
await triggerComponentToolbarAction(page, 'releaseFromGroup');
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
[outterGroupId]: 1,
|
||||
null: 2,
|
||||
});
|
||||
await assertContainerChildCount(page, outterGroupId, 1);
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
|
||||
// undo the release
|
||||
await undoByKeyboard(page);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
[outterGroupId]: 2,
|
||||
null: 1,
|
||||
});
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
await assertContainerChildCount(page, outterGroupId, 2);
|
||||
|
||||
// redo the release
|
||||
await redoByKeyboard(page);
|
||||
await assertContainerIds(page, {
|
||||
[groupId]: 2,
|
||||
[outterGroupId]: 1,
|
||||
null: 2,
|
||||
});
|
||||
await assertContainerChildCount(page, outterGroupId, 1);
|
||||
await assertContainerChildCount(page, groupId, 2);
|
||||
});
|
||||
});
|
||||
72
blocksuite/tests-legacy/edgeless/group/title.spec.ts
Normal file
72
blocksuite/tests-legacy/edgeless/group/title.spec.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
createShapeElement,
|
||||
dblclickView,
|
||||
edgelessCommonSetup,
|
||||
getSelectedBound,
|
||||
pressEnter,
|
||||
selectAllByKeyboard,
|
||||
Shape,
|
||||
triggerComponentToolbarAction,
|
||||
type,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertEdgelessCanvasText } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
async function init(page: Page) {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [100, 0], [200, 100], Shape.Square);
|
||||
}
|
||||
|
||||
test.describe('group title', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await init(page);
|
||||
await selectAllByKeyboard(page);
|
||||
await triggerComponentToolbarAction(page, 'addGroup');
|
||||
});
|
||||
|
||||
test('edit group title by component toolbar', async ({ page }) => {
|
||||
expect(await page.locator('edgeless-group-title-editor').count()).toBe(0);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'renameGroup');
|
||||
await page.locator('edgeless-group-title-editor').waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
});
|
||||
|
||||
test('edit group title by dbclick', async ({ page }) => {
|
||||
expect(await page.locator('edgeless-group-title-editor').count()).toBe(0);
|
||||
|
||||
const bound = await getSelectedBound(page);
|
||||
await dblclickView(page, [bound[0] + 10, bound[1] - 10]);
|
||||
await page.locator('edgeless-group-title-editor').waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
await type(page, 'ABC');
|
||||
await assertEdgelessCanvasText(page, 'ABC');
|
||||
});
|
||||
|
||||
test('blur unmount group editor', async ({ page }) => {
|
||||
const bound = await getSelectedBound(page);
|
||||
await dblclickView(page, [bound[0] + 10, bound[1] - 10]);
|
||||
|
||||
await page.locator('edgeless-group-title-editor').waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
await page.mouse.click(10, 10);
|
||||
expect(await page.locator('edgeless-group-title-editor').count()).toBe(0);
|
||||
});
|
||||
|
||||
test('enter unmount group editor', async ({ page }) => {
|
||||
const bound = await getSelectedBound(page);
|
||||
await dblclickView(page, [bound[0] + 10, bound[1] - 10]);
|
||||
|
||||
await page.locator('edgeless-group-title-editor').waitFor({
|
||||
state: 'attached',
|
||||
});
|
||||
await pressEnter(page);
|
||||
expect(await page.locator('edgeless-group-title-editor').count()).toBe(0);
|
||||
});
|
||||
});
|
||||
262
blocksuite/tests-legacy/edgeless/lasso.spec.ts
Normal file
262
blocksuite/tests-legacy/edgeless/lasso.spec.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
import { sleep } from '@blocksuite/global/utils';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addBasicRectShapeElement,
|
||||
assertEdgelessTool,
|
||||
edgelessCommonSetup as commonSetup,
|
||||
setEdgelessTool,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
dragBetweenCoords,
|
||||
selectAllByKeyboard,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessNonSelectedRect,
|
||||
assertEdgelessSelectedRect,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.skip('lasso tool should deselect when dragging in an empty area', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await addBasicRectShapeElement(page, start, end);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await assertEdgelessTool(page, 'lasso');
|
||||
|
||||
await dragBetweenCoords(page, { x: 10, y: 10 }, { x: 15, y: 15 });
|
||||
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test.skip('freehand lasso basic test', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 });
|
||||
|
||||
await page.mouse.click(10, 10); // deselect
|
||||
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await assertEdgelessTool(page, 'lasso');
|
||||
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
|
||||
// simulate a basic lasso selection to select both the rects
|
||||
const points: [number, number][] = [
|
||||
[500, 100],
|
||||
[500, 500],
|
||||
[90, 500],
|
||||
];
|
||||
await page.mouse.move(90, 90);
|
||||
await page.mouse.down();
|
||||
for (const point of points) await page.mouse.move(...point);
|
||||
await page.mouse.up();
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 200, 200]);
|
||||
});
|
||||
|
||||
test.skip('freehand lasso add to selection', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 });
|
||||
|
||||
await page.mouse.click(10, 10); // deselect
|
||||
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await assertEdgelessTool(page, 'lasso');
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
|
||||
// some random selection covering the rectangle
|
||||
let points: [number, number][] = [
|
||||
[250, 90],
|
||||
[250, 300],
|
||||
[10, 300],
|
||||
];
|
||||
await page.mouse.move(90, 90);
|
||||
await page.mouse.down();
|
||||
for (const point of points) await page.mouse.move(...point);
|
||||
await page.mouse.up();
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
points = [
|
||||
[400, 250],
|
||||
[400, 450],
|
||||
[250, 450],
|
||||
];
|
||||
|
||||
await page.keyboard.down('Shift'); // addition selection
|
||||
await page.mouse.move(250, 250);
|
||||
await page.mouse.down();
|
||||
for (const point of points) await page.mouse.move(...point);
|
||||
await page.mouse.up();
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 200, 200]);
|
||||
});
|
||||
|
||||
test.skip('freehand lasso subtract from selection', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 });
|
||||
await setEdgelessTool(page, 'default');
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
|
||||
const points: [number, number][] = [
|
||||
[410, 290],
|
||||
[410, 410],
|
||||
[290, 410],
|
||||
];
|
||||
|
||||
await page.keyboard.down('Alt');
|
||||
|
||||
await page.mouse.move(290, 290);
|
||||
await page.mouse.down();
|
||||
for (const point of points) await page.mouse.move(...point);
|
||||
await page.mouse.up();
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]); // only the first rectangle should be selected
|
||||
});
|
||||
|
||||
test.skip('polygonal lasso basic test', async ({ page }) => {
|
||||
await commonSetup(page);
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 });
|
||||
await page.mouse.click(10, 10); // deselect
|
||||
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await setEdgelessTool(page, 'lasso'); // switch to polygonal lasso
|
||||
await sleep(100);
|
||||
|
||||
const points: [number, number][] = [
|
||||
[90, 90],
|
||||
[500, 90],
|
||||
[500, 500],
|
||||
[90, 500],
|
||||
[90, 90],
|
||||
];
|
||||
|
||||
for (const point of points) {
|
||||
await page.mouse.click(...point);
|
||||
}
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 200, 200]);
|
||||
});
|
||||
|
||||
test.skip('polygonal lasso add to selection by holding Shift Key', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 });
|
||||
|
||||
await page.mouse.click(10, 10); // deselect
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await sleep(100);
|
||||
|
||||
let points: [number, number][] = [
|
||||
[90, 90],
|
||||
[150, 90],
|
||||
[150, 150],
|
||||
[90, 150],
|
||||
[90, 90],
|
||||
];
|
||||
|
||||
// select the first rectangle
|
||||
for (const point of points) await page.mouse.click(...point);
|
||||
|
||||
points = [
|
||||
[290, 290],
|
||||
[350, 290],
|
||||
[350, 350],
|
||||
[290, 350],
|
||||
[290, 290],
|
||||
];
|
||||
|
||||
await page.keyboard.down('Shift'); // add to selection
|
||||
// selects the second rectangle
|
||||
for (const point of points) await page.mouse.click(...point);
|
||||
|
||||
// by the end both of the rects should be selected
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 200, 200]);
|
||||
});
|
||||
|
||||
test.skip('polygonal lasso subtract from selection by holding Alt', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await addBasicRectShapeElement(page, { x: 300, y: 300 }, { x: 400, y: 400 });
|
||||
|
||||
await selectAllByKeyboard(page);
|
||||
|
||||
const points: [number, number][] = [
|
||||
[290, 290],
|
||||
[350, 290],
|
||||
[350, 350],
|
||||
[290, 350],
|
||||
[290, 290],
|
||||
];
|
||||
|
||||
// switch to polygonal lasso tool
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await sleep(100);
|
||||
|
||||
await page.keyboard.down('Alt'); // subtract from selection
|
||||
for (const point of points) await page.mouse.click(...point);
|
||||
|
||||
// By the end the second rectangle must be deselected leaving the first rect selection
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
});
|
||||
|
||||
test.skip('polygonal lasso should complete selection when clicking the last point', async ({
|
||||
page,
|
||||
}) => {
|
||||
await commonSetup(page);
|
||||
|
||||
// switch to polygonal lasso
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await setEdgelessTool(page, 'lasso');
|
||||
await sleep(100);
|
||||
|
||||
const lassoPoints: [number, number][] = [
|
||||
[100, 100],
|
||||
[200, 200],
|
||||
[250, 150],
|
||||
[100, 100],
|
||||
];
|
||||
|
||||
for (const point of lassoPoints) await page.mouse.click(...point);
|
||||
|
||||
const isSelecting = await page.evaluate(() => {
|
||||
const edgeless = document.querySelector('affine-edgeless-root');
|
||||
if (!edgeless) throw new Error('Missing edgless root block');
|
||||
|
||||
const curController = edgeless.gfx.tool.currentTool$.peek();
|
||||
if (curController?.toolName !== 'lasso')
|
||||
throw new Error('expected lasso tool controller');
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (curController as any)['_isSelecting'];
|
||||
});
|
||||
|
||||
expect(isSelecting).toBe(false);
|
||||
});
|
||||
354
blocksuite/tests-legacy/edgeless/linked-doc.spec.ts
Normal file
354
blocksuite/tests-legacy/edgeless/linked-doc.spec.ts
Normal file
@@ -0,0 +1,354 @@
|
||||
import { assertNotExists } from '@blocksuite/global/utils';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
activeNoteInEdgeless,
|
||||
createConnectorElement,
|
||||
createNote,
|
||||
createShapeElement,
|
||||
edgelessCommonSetup,
|
||||
getConnectorPath,
|
||||
locatorComponentToolbarMoreButton,
|
||||
selectNoteInEdgeless,
|
||||
Shape,
|
||||
triggerComponentToolbarAction,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
addBasicBrushElement,
|
||||
pressEnter,
|
||||
selectAllByKeyboard,
|
||||
type,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import { assertConnectorPath, assertExists } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('note to linked doc', () => {
|
||||
test('select a note and turn it into a linked doc', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const noteId = await createNote(page, [100, 0], '');
|
||||
await activeNoteInEdgeless(page, noteId);
|
||||
await waitNextFrame(page, 200);
|
||||
await type(page, 'Hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'World');
|
||||
|
||||
await page.mouse.click(10, 50);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'turnIntoLinkedDoc');
|
||||
|
||||
await waitNextFrame(page, 200);
|
||||
const embedSyncedBlock = page.locator('affine-embed-synced-doc-block');
|
||||
assertExists(embedSyncedBlock);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'openLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const noteBlock = page.locator('affine-edgeless-note');
|
||||
assertExists(noteBlock);
|
||||
const noteContent = await noteBlock.innerText();
|
||||
expect(noteContent).toBe('Hello\nWorld');
|
||||
});
|
||||
|
||||
test('turn note into a linked doc, connector keeps', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const noteId = await createNote(page, [100, 0]);
|
||||
await createShapeElement(page, [100, 100], [100, 100], Shape.Square);
|
||||
await createConnectorElement(page, [100, 150], [100, 10]);
|
||||
const connectorPath = await getConnectorPath(page);
|
||||
|
||||
await page.mouse.click(10, 50);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'turnIntoLinkedDoc');
|
||||
|
||||
await waitNextFrame(page, 200);
|
||||
const embedSyncedBlock = page.locator('affine-embed-synced-doc-block');
|
||||
assertExists(embedSyncedBlock);
|
||||
|
||||
await assertConnectorPath(page, [connectorPath[0], connectorPath[1]], 0);
|
||||
});
|
||||
|
||||
// TODO FIX ME
|
||||
test.skip('embed-synced-doc card can not turn into linked doc', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const noteId = await createNote(page, [100, 0]);
|
||||
await activeNoteInEdgeless(page, noteId);
|
||||
await waitNextFrame(page, 200);
|
||||
await type(page, 'Hello World');
|
||||
|
||||
await page.mouse.click(10, 50);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'turnIntoLinkedDoc');
|
||||
|
||||
const moreButton = locatorComponentToolbarMoreButton(page);
|
||||
await moreButton.click();
|
||||
const turnButton = page.locator('.turn-into-linked-doc');
|
||||
assertNotExists(turnButton);
|
||||
});
|
||||
|
||||
// TODO FIX ME
|
||||
test.skip('embed-linked-doc card can not turn into linked doc', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
const noteId = await createNote(page, [100, 0]);
|
||||
await activeNoteInEdgeless(page, noteId);
|
||||
await waitNextFrame(page, 200);
|
||||
await type(page, 'Hello World');
|
||||
|
||||
await page.mouse.click(10, 50);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'turnIntoLinkedDoc');
|
||||
|
||||
await triggerComponentToolbarAction(page, 'toCardView');
|
||||
const moreButton = locatorComponentToolbarMoreButton(page);
|
||||
await moreButton.click();
|
||||
const turnButton = page.locator('.turn-into-linked-doc');
|
||||
assertNotExists(turnButton);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('single edgeless element to linked doc', () => {
|
||||
test('select a shape, turn into a linked doc', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [100, 100], [100, 100], Shape.Square);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'createLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
const linkedSyncedBlock = page.locator('affine-linked-synced-doc-block');
|
||||
assertExists(linkedSyncedBlock);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'openLinkedDoc');
|
||||
await waitNextFrame(page, 200);
|
||||
|
||||
const shapes = await page.evaluate(() => {
|
||||
const container = document.querySelector('affine-edgeless-root');
|
||||
return container!.service
|
||||
.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);
|
||||
});
|
||||
});
|
||||
559
blocksuite/tests-legacy/edgeless/lock.spec.ts
Normal file
559
blocksuite/tests-legacy/edgeless/lock.spec.ts
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
126
blocksuite/tests-legacy/edgeless/mindmap.spec.ts
Normal file
126
blocksuite/tests-legacy/edgeless/mindmap.spec.ts
Normal file
@@ -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);
|
||||
});
|
||||
155
blocksuite/tests-legacy/edgeless/note/drag-handle.spec.ts
Normal file
155
blocksuite/tests-legacy/edgeless/note/drag-handle.spec.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
addNote,
|
||||
dragHandleFromBlockToBlockBottomById,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyEdgelessState,
|
||||
initThreeParagraphs,
|
||||
setEdgelessTool,
|
||||
switchEditorMode,
|
||||
type,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertRectExist, assertRichTexts } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
const CENTER_X = 450;
|
||||
const CENTER_Y = 450;
|
||||
|
||||
test('drag handle should be shown when a note is activated in default mode or hidden in other modes', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
const noteBox = await page.locator('affine-edgeless-note').boundingBox();
|
||||
if (!noteBox) {
|
||||
throw new Error('Missing edgeless affine-note');
|
||||
}
|
||||
|
||||
const [x, y] = [noteBox.x + 26, noteBox.y + noteBox.height / 2];
|
||||
|
||||
await page.mouse.move(x, y);
|
||||
await expect(page.locator('.affine-drag-handle-container')).toBeHidden();
|
||||
await page.mouse.dblclick(x, y);
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.move(x, y);
|
||||
|
||||
await expect(page.locator('.affine-drag-handle-container')).toBeVisible();
|
||||
|
||||
await page.mouse.move(0, 0);
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await page.mouse.move(x, y);
|
||||
await expect(page.locator('.affine-drag-handle-container')).toBeHidden();
|
||||
|
||||
await page.mouse.move(0, 0);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.move(x, y);
|
||||
await expect(page.locator('.affine-drag-handle-container')).toBeVisible();
|
||||
});
|
||||
|
||||
test('drag handle can drag note into another note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
const noteRect = await page
|
||||
.locator(`[data-block-id="${noteId}"]`)
|
||||
.boundingBox();
|
||||
assertRectExist(noteRect);
|
||||
|
||||
const secondNoteId = await addNote(page, 'hello world', 100, 100);
|
||||
await waitNextFrame(page);
|
||||
const secondNoteRect = await page
|
||||
.locator(`[data-block-id="${secondNoteId}"]`)
|
||||
.boundingBox();
|
||||
assertRectExist(secondNoteRect);
|
||||
|
||||
{
|
||||
const [x, y] = [
|
||||
noteRect.x + noteRect.width / 2,
|
||||
noteRect.y + noteRect.height / 2,
|
||||
];
|
||||
await page.mouse.click(noteRect.x, noteRect.y + noteRect.height + 100);
|
||||
await page.mouse.move(x, y);
|
||||
await page.mouse.click(x, y);
|
||||
|
||||
const handlerRect = await page
|
||||
.locator('.affine-drag-handle-container')
|
||||
.boundingBox();
|
||||
assertRectExist(handlerRect);
|
||||
|
||||
await page.mouse.move(
|
||||
handlerRect.x + handlerRect.width / 2,
|
||||
handlerRect.y + handlerRect.height / 2
|
||||
);
|
||||
await page.mouse.down();
|
||||
|
||||
const [targetX, targetY] = [
|
||||
secondNoteRect.x + 10,
|
||||
secondNoteRect.y + secondNoteRect.height / 2,
|
||||
];
|
||||
await page.mouse.move(targetX, targetY);
|
||||
await page.mouse.up();
|
||||
|
||||
await waitNextFrame(page);
|
||||
}
|
||||
});
|
||||
|
||||
test('drag handle should work inside one note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await initThreeParagraphs(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
await page.mouse.dblclick(CENTER_X, CENTER_Y);
|
||||
await dragHandleFromBlockToBlockBottomById(page, '3', '5');
|
||||
await waitNextFrame(page);
|
||||
await expect(page.locator('affine-drag-handle-container')).toBeHidden();
|
||||
await assertRichTexts(page, ['456', '789', '123']);
|
||||
});
|
||||
|
||||
test.fixme(
|
||||
'drag handle should work across multiple notes',
|
||||
async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
await setEdgelessTool(page, 'note');
|
||||
|
||||
await page.mouse.click(200, 200);
|
||||
await focusRichText(page, 3);
|
||||
await waitNextFrame(page);
|
||||
|
||||
// block id 7
|
||||
await type(page, '000');
|
||||
|
||||
await page.mouse.dblclick(CENTER_X, CENTER_Y - 20);
|
||||
await dragHandleFromBlockToBlockBottomById(page, '3', '7');
|
||||
await expect(page.locator('.affine-drag-handle-container')).toBeHidden();
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['456', '789', '000', '123']);
|
||||
|
||||
// await page.mouse.dblclick(305, 305);
|
||||
await dragHandleFromBlockToBlockBottomById(page, '3', '4');
|
||||
await waitNextFrame(page);
|
||||
await expect(page.locator('.affine-drag-handle-container')).toBeHidden();
|
||||
await assertRichTexts(page, ['456', '123', '789', '000']);
|
||||
|
||||
await expect(page.locator('selected > *')).toHaveCount(0);
|
||||
}
|
||||
);
|
||||
80
blocksuite/tests-legacy/edgeless/note/mode.spec.ts
Normal file
80
blocksuite/tests-legacy/edgeless/note/mode.spec.ts
Normal file
@@ -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);
|
||||
});
|
||||
531
blocksuite/tests-legacy/edgeless/note/note.spec.ts
Normal file
531
blocksuite/tests-legacy/edgeless/note/note.spec.ts
Normal file
@@ -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']);
|
||||
});
|
||||
254
blocksuite/tests-legacy/edgeless/note/resize.spec.ts
Normal file
254
blocksuite/tests-legacy/edgeless/note/resize.spec.ts
Normal file
@@ -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,
|
||||
]);
|
||||
});
|
||||
146
blocksuite/tests-legacy/edgeless/note/scale.spec.ts
Normal file
146
blocksuite/tests-legacy/edgeless/note/scale.spec.ts
Normal file
@@ -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');
|
||||
});
|
||||
});
|
||||
156
blocksuite/tests-legacy/edgeless/note/slicer.spec.ts
Normal file
156
blocksuite/tests-legacy/edgeless/note/slicer.spec.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
initSixParagraphs,
|
||||
initThreeParagraphs,
|
||||
selectNoteInEdgeless,
|
||||
switchEditorMode,
|
||||
triggerComponentToolbarAction,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertRectExist, assertRichTexts } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test.describe('note slicer', () => {
|
||||
test('could enable and disenable note slicer', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await initSixParagraphs(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
// note slicer button should not be visible when note slicer setting is disenabled
|
||||
await expect(page.locator('.note-slicer-button')).toBeHidden();
|
||||
await expect(page.locator('.note-slicer-dividing-line')).toHaveCount(0);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting');
|
||||
// note slicer button should be visible when note slicer setting is enabled
|
||||
await expect(page.locator('.note-slicer-button')).toBeVisible();
|
||||
await expect(page.locator('.note-slicer-dividing-line')).toHaveCount(5);
|
||||
});
|
||||
|
||||
test('note slicer will add new note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await initSixParagraphs(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await expect(page.locator('affine-edgeless-note')).toHaveCount(1);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting');
|
||||
await expect(page.locator('.note-slicer-button')).toBeVisible();
|
||||
|
||||
await page.locator('.note-slicer-button').click();
|
||||
|
||||
await expect(page.locator('affine-edgeless-note')).toHaveCount(2);
|
||||
});
|
||||
|
||||
test('note slicer button should appears at right position', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting');
|
||||
|
||||
const blocks = await page
|
||||
.locator(`[data-block-id="${noteId}"] [data-block-id]`)
|
||||
.all();
|
||||
expect(blocks.length).toBe(3);
|
||||
|
||||
const firstBlockRect = await blocks[0].boundingBox();
|
||||
assertRectExist(firstBlockRect);
|
||||
const secondBlockRect = await blocks[1].boundingBox();
|
||||
assertRectExist(secondBlockRect);
|
||||
await page.mouse.move(
|
||||
secondBlockRect.x + 1,
|
||||
secondBlockRect.y + secondBlockRect.height / 2
|
||||
);
|
||||
|
||||
let slicerButtonRect = await page
|
||||
.locator('.note-slicer-button')
|
||||
.boundingBox();
|
||||
assertRectExist(slicerButtonRect);
|
||||
|
||||
let buttonRectMiddle = slicerButtonRect.y + slicerButtonRect.height / 2;
|
||||
|
||||
expect(buttonRectMiddle).toBeGreaterThan(
|
||||
firstBlockRect.y + firstBlockRect.height
|
||||
);
|
||||
expect(buttonRectMiddle).toBeGreaterThan(secondBlockRect.y);
|
||||
|
||||
const thirdBlockRect = await blocks[2].boundingBox();
|
||||
assertRectExist(thirdBlockRect);
|
||||
await page.mouse.move(
|
||||
thirdBlockRect.x + 1,
|
||||
thirdBlockRect.y + thirdBlockRect.height / 2
|
||||
);
|
||||
|
||||
slicerButtonRect = await page.locator('.note-slicer-button').boundingBox();
|
||||
assertRectExist(slicerButtonRect);
|
||||
|
||||
buttonRectMiddle = slicerButtonRect.y + slicerButtonRect.height / 2;
|
||||
expect(buttonRectMiddle).toBeGreaterThan(
|
||||
secondBlockRect.y + secondBlockRect.height
|
||||
);
|
||||
expect(buttonRectMiddle).toBeLessThan(thirdBlockRect.y);
|
||||
});
|
||||
|
||||
test('note slicer button should appears at right position when editor is not located at left top corner', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
|
||||
await page.evaluate(() => {
|
||||
const el = document.createElement('div');
|
||||
const app = document.querySelector('#app') as HTMLElement;
|
||||
|
||||
el.style.height = '100px';
|
||||
el.style.background = 'red';
|
||||
|
||||
app!.style.paddingLeft = '80px';
|
||||
|
||||
document.body.insertBefore(el, app);
|
||||
});
|
||||
|
||||
const blocks = await page
|
||||
.locator(`[data-block-id="${noteId}"] [data-block-id]`)
|
||||
.all();
|
||||
expect(blocks.length).toBe(3);
|
||||
|
||||
const firstBlockRect = await blocks[0].boundingBox();
|
||||
assertRectExist(firstBlockRect);
|
||||
const secondBlockRect = await blocks[1].boundingBox();
|
||||
assertRectExist(secondBlockRect);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting');
|
||||
await page.mouse.move(
|
||||
secondBlockRect.x + 1,
|
||||
secondBlockRect.y + secondBlockRect.height / 2
|
||||
);
|
||||
|
||||
const slicerButtonRect = await page
|
||||
.locator('.note-slicer-button')
|
||||
.boundingBox();
|
||||
assertRectExist(slicerButtonRect);
|
||||
|
||||
const buttonRectMiddle = slicerButtonRect.y + slicerButtonRect.height / 2;
|
||||
|
||||
expect(buttonRectMiddle).toBeGreaterThan(
|
||||
firstBlockRect.y + firstBlockRect.height
|
||||
);
|
||||
expect(buttonRectMiddle).toBeGreaterThan(secondBlockRect.y);
|
||||
});
|
||||
});
|
||||
140
blocksuite/tests-legacy/edgeless/note/undo-redo.spec.ts
Normal file
140
blocksuite/tests-legacy/edgeless/note/undo-redo.spec.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
activeNoteInEdgeless,
|
||||
click,
|
||||
copyByKeyboard,
|
||||
countBlock,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
fillLine,
|
||||
focusRichText,
|
||||
getNoteRect,
|
||||
initEmptyEdgelessState,
|
||||
initSixParagraphs,
|
||||
pasteByKeyboard,
|
||||
redoByClick,
|
||||
redoByKeyboard,
|
||||
selectNoteInEdgeless,
|
||||
switchEditorMode,
|
||||
triggerComponentToolbarAction,
|
||||
type,
|
||||
undoByClick,
|
||||
undoByKeyboard,
|
||||
waitNextFrame,
|
||||
zoomResetByKeyboard,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { assertRectEqual } from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test('undo/redo should work correctly after clipping', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await initSixParagraphs(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await expect(page.locator('affine-edgeless-note')).toHaveCount(1);
|
||||
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting');
|
||||
|
||||
const button = page.locator('.note-slicer-button');
|
||||
await button.click();
|
||||
await expect(page.locator('affine-edgeless-note')).toHaveCount(2);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await expect(page.locator('affine-edgeless-note')).toHaveCount(1);
|
||||
await redoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await expect(page.locator('affine-edgeless-note')).toHaveCount(2);
|
||||
});
|
||||
|
||||
test('undo/redo should work correctly after resizing', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const { noteId } = await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
await activeNoteInEdgeless(page, noteId);
|
||||
await waitNextFrame(page, 400);
|
||||
// current implementation may be a little inefficient
|
||||
await fillLine(page, true);
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page, 400);
|
||||
await selectNoteInEdgeless(page, noteId);
|
||||
|
||||
const initRect = await getNoteRect(page, noteId);
|
||||
const rightHandle = page.locator('.handle[aria-label="right"] .resize');
|
||||
const box = await rightHandle.boundingBox();
|
||||
if (box === null) throw new Error();
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: box.x + 5, y: box.y + 5 },
|
||||
{ x: box.x + 105, y: box.y + 5 }
|
||||
);
|
||||
const draggedRect = await getNoteRect(page, noteId);
|
||||
assertRectEqual(draggedRect, {
|
||||
x: initRect.x,
|
||||
y: initRect.y,
|
||||
w: initRect.w + 100,
|
||||
h: draggedRect.h, // not assert `h` here
|
||||
});
|
||||
expect(draggedRect.h).toBe(initRect.h);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
const undoRect = await getNoteRect(page, noteId);
|
||||
assertRectEqual(undoRect, initRect);
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
const redoRect = await getNoteRect(page, noteId);
|
||||
assertRectEqual(redoRect, draggedRect);
|
||||
});
|
||||
|
||||
test('continuous undo and redo (note block add operation) should work', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await switchEditorMode(page);
|
||||
await click(page, { x: 260, y: 450 });
|
||||
await copyByKeyboard(page);
|
||||
|
||||
let count = await countBlock(page, 'affine-edgeless-note');
|
||||
expect(count).toBe(1);
|
||||
|
||||
await page.mouse.move(100, 100);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page, 1000);
|
||||
|
||||
await page.mouse.move(200, 200);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page, 1000);
|
||||
|
||||
await page.mouse.move(300, 300);
|
||||
await pasteByKeyboard(page, false);
|
||||
await waitNextFrame(page, 1000);
|
||||
|
||||
count = await countBlock(page, 'affine-edgeless-note');
|
||||
expect(count).toBe(4);
|
||||
|
||||
await undoByClick(page);
|
||||
count = await countBlock(page, 'affine-edgeless-note');
|
||||
expect(count).toBe(3);
|
||||
|
||||
await undoByClick(page);
|
||||
count = await countBlock(page, 'affine-edgeless-note');
|
||||
expect(count).toBe(2);
|
||||
|
||||
await redoByClick(page);
|
||||
count = await countBlock(page, 'affine-edgeless-note');
|
||||
expect(count).toBe(3);
|
||||
|
||||
await redoByClick(page);
|
||||
count = await countBlock(page, 'affine-edgeless-note');
|
||||
expect(count).toBe(4);
|
||||
});
|
||||
263
blocksuite/tests-legacy/edgeless/pan.spec.ts
Normal file
263
blocksuite/tests-legacy/edgeless/pan.spec.ts
Normal file
@@ -0,0 +1,263 @@
|
||||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
|
||||
import {
|
||||
activeNoteInEdgeless,
|
||||
addNote,
|
||||
assertEdgelessTool,
|
||||
locatorEdgelessToolButton,
|
||||
multiTouchDown,
|
||||
multiTouchMove,
|
||||
multiTouchUp,
|
||||
setEdgelessTool,
|
||||
switchEditorMode,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
addBasicRectShapeElement,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
toggleEditorReadonly,
|
||||
type,
|
||||
waitForInlineEditorStateUpdated,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessSelectedRect,
|
||||
assertNotHasClass,
|
||||
assertRichTexts,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('pan tool basic', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await addBasicRectShapeElement(page, start, end);
|
||||
|
||||
await setEdgelessTool(page, 'pan');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: start.x + 5,
|
||||
y: start.y + 5,
|
||||
},
|
||||
{
|
||||
x: start.x + 25,
|
||||
y: start.y + 25,
|
||||
}
|
||||
);
|
||||
await setEdgelessTool(page, 'default');
|
||||
|
||||
await page.mouse.click(start.x + 25, start.y + 25);
|
||||
await assertEdgelessSelectedRect(page, [120, 120, 100, 100]);
|
||||
});
|
||||
|
||||
test('pan tool shortcut', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await addBasicRectShapeElement(page, start, end);
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y + 5);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
await page.keyboard.down('Space');
|
||||
await assertEdgelessTool(page, 'pan');
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: start.x + 5,
|
||||
y: start.y + 5,
|
||||
},
|
||||
{
|
||||
x: start.x + 25,
|
||||
y: start.y + 25,
|
||||
}
|
||||
);
|
||||
|
||||
await page.keyboard.up('Space');
|
||||
await assertEdgelessSelectedRect(page, [120, 120, 100, 100]);
|
||||
});
|
||||
|
||||
// FIXME(@doouding): Failed on CI
|
||||
test.skip('pan tool with middle button', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const start = { x: 100, y: 100 };
|
||||
const end = { x: 200, y: 200 };
|
||||
await addBasicRectShapeElement(page, start, end);
|
||||
|
||||
await page.mouse.click(start.x + 5, start.y + 5);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 400,
|
||||
y: 400,
|
||||
},
|
||||
{
|
||||
x: 420,
|
||||
y: 420,
|
||||
},
|
||||
{
|
||||
button: 'middle',
|
||||
}
|
||||
);
|
||||
|
||||
await assertEdgelessTool(page, 'default');
|
||||
await assertEdgelessSelectedRect(page, [120, 120, 100, 100]);
|
||||
});
|
||||
|
||||
test('pan tool shortcut should revert to the previous tool on keyup', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await page.mouse.click(100, 100);
|
||||
|
||||
await setEdgelessTool(page, 'brush');
|
||||
{
|
||||
await page.keyboard.down('Space');
|
||||
await assertEdgelessTool(page, 'pan');
|
||||
|
||||
await page.keyboard.up('Space');
|
||||
await assertEdgelessTool(page, 'brush');
|
||||
}
|
||||
});
|
||||
|
||||
test('pan tool shortcut does not affect other tools while using the tool', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
// Test if while drawing shortcut does not switch to pan tool
|
||||
await setEdgelessTool(page, 'brush');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: 100, y: 110 },
|
||||
{ x: 200, y: 300 },
|
||||
{
|
||||
click: true,
|
||||
beforeMouseUp: async () => {
|
||||
await page.keyboard.down('Space');
|
||||
await assertEdgelessTool(page, 'brush');
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await setEdgelessTool(page, 'eraser');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: 100, y: 110 },
|
||||
{ x: 200, y: 300 },
|
||||
{
|
||||
click: true,
|
||||
beforeMouseUp: async () => {
|
||||
await page.keyboard.down('Space');
|
||||
await assertEdgelessTool(page, 'eraser');
|
||||
},
|
||||
}
|
||||
);
|
||||
// Maybe add other tools too
|
||||
});
|
||||
|
||||
test('pan tool shortcut when user is editing', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const ids = await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await setEdgelessTool(page, 'default');
|
||||
|
||||
await activeNoteInEdgeless(page, ids.noteId);
|
||||
await waitForInlineEditorStateUpdated(page);
|
||||
|
||||
await type(page, 'hello');
|
||||
await assertRichTexts(page, ['hello']);
|
||||
|
||||
await page.keyboard.down('Space');
|
||||
const defaultButton = await locatorEdgelessToolButton(page, 'pan', false);
|
||||
await assertNotHasClass(defaultButton, 'pan');
|
||||
await waitNextFrame(page);
|
||||
});
|
||||
|
||||
test.describe('pan tool in readonly mode', () => {
|
||||
async function setupReadonlyEdgeless(page: Page) {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
const noteId = await addNote(page, 'hello world', 100, 200);
|
||||
await page.mouse.click(50, 100);
|
||||
|
||||
const edgelessNote = page.locator(
|
||||
`affine-edgeless-note[data-block-id="${noteId}"]`
|
||||
);
|
||||
const originalBoundingBox = await edgelessNote.boundingBox();
|
||||
expect(originalBoundingBox).not.toBeNull();
|
||||
const { x: originalX, y: originalY } = originalBoundingBox!;
|
||||
|
||||
// Toggle readonly mode
|
||||
await toggleEditorReadonly(page);
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
return { edgelessNote, originalX, originalY };
|
||||
}
|
||||
|
||||
async function assertPanned(
|
||||
edgelessNote: Locator,
|
||||
originalX: number,
|
||||
originalY: number
|
||||
) {
|
||||
const newBoundingBox = await edgelessNote.boundingBox();
|
||||
expect(newBoundingBox).not.toBeNull();
|
||||
const { x: newX, y: newY } = newBoundingBox!;
|
||||
|
||||
expect(newX).toBeGreaterThan(originalX);
|
||||
expect(newY).toBeGreaterThan(originalY);
|
||||
}
|
||||
|
||||
test('can be used by keyboard', async ({ page }) => {
|
||||
const { edgelessNote, originalX, originalY } =
|
||||
await setupReadonlyEdgeless(page);
|
||||
|
||||
await page.keyboard.down('Space');
|
||||
await assertEdgelessTool(page, 'pan');
|
||||
|
||||
// Pan the viewport
|
||||
await dragBetweenCoords(page, { x: 300, y: 300 }, { x: 400, y: 400 });
|
||||
|
||||
await assertPanned(edgelessNote, originalX, originalY);
|
||||
});
|
||||
|
||||
test('can be used by multi-touch', async ({ page }) => {
|
||||
const { edgelessNote, originalX, originalY } =
|
||||
await setupReadonlyEdgeless(page);
|
||||
|
||||
// Pan the viewport using multi-touch
|
||||
const from = [
|
||||
{ x: 300, y: 300 },
|
||||
{ x: 400, y: 300 },
|
||||
];
|
||||
const to = [
|
||||
{ x: 350, y: 350 },
|
||||
{ x: 450, y: 350 },
|
||||
];
|
||||
await multiTouchDown(page, from);
|
||||
await multiTouchMove(page, from, to);
|
||||
await multiTouchUp(page, to);
|
||||
|
||||
await assertPanned(edgelessNote, originalX, originalY);
|
||||
});
|
||||
});
|
||||
127
blocksuite/tests-legacy/edgeless/paste-block.spec.ts
Normal file
127
blocksuite/tests-legacy/edgeless/paste-block.spec.ts
Normal file
@@ -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');
|
||||
});
|
||||
});
|
||||
249
blocksuite/tests-legacy/edgeless/presentation.spec.ts
Normal file
249
blocksuite/tests-legacy/edgeless/presentation.spec.ts
Normal file
@@ -0,0 +1,249 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import {
|
||||
assertEdgelessTool,
|
||||
createFrame,
|
||||
createNote,
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
enterPresentationMode,
|
||||
locatorPresentationToolbarButton,
|
||||
setEdgelessTool,
|
||||
Shape,
|
||||
toggleFramePanel,
|
||||
} from 'utils/actions/edgeless.js';
|
||||
import {
|
||||
copyByKeyboard,
|
||||
pasteByKeyboard,
|
||||
pressEscape,
|
||||
selectAllBlocksByKeyboard,
|
||||
} from 'utils/actions/keyboard.js';
|
||||
import { waitNextFrame } from 'utils/actions/misc.js';
|
||||
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('presentation', () => {
|
||||
test('should render note when enter presentation mode', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await createNote(page, [300, 100], 'hello');
|
||||
|
||||
// Frame shape
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenViewCoords(page, [80, 80], [220, 220]);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
// Frame note
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenViewCoords(page, [240, 0], [800, 200]);
|
||||
|
||||
expect(await page.locator('affine-frame').count()).toBe(2);
|
||||
|
||||
await enterPresentationMode(page);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
const nextButton = locatorPresentationToolbarButton(page, 'next');
|
||||
await nextButton.click();
|
||||
const edgelessNote = page.locator('affine-edgeless-note');
|
||||
await expect(edgelessNote).toBeVisible();
|
||||
|
||||
const prevButton = locatorPresentationToolbarButton(page, 'previous');
|
||||
await prevButton.click();
|
||||
await expect(edgelessNote).toBeHidden();
|
||||
|
||||
await waitNextFrame(page, 300);
|
||||
await nextButton.click();
|
||||
await expect(edgelessNote).toBeVisible();
|
||||
});
|
||||
|
||||
test('should exit presentation mode when press escape', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createNote(page, [300, 100], 'hello');
|
||||
|
||||
// Frame note
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenViewCoords(page, [240, 0], [800, 200]);
|
||||
|
||||
expect(await page.locator('affine-frame').count()).toBe(1);
|
||||
|
||||
await enterPresentationMode(page);
|
||||
await waitNextFrame(page, 300);
|
||||
|
||||
await assertEdgelessTool(page, 'frameNavigator');
|
||||
const navigatorBlackBackground = page.locator(
|
||||
'.edgeless-navigator-black-background'
|
||||
);
|
||||
await expect(navigatorBlackBackground).toBeVisible();
|
||||
|
||||
await pressEscape(page);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
await assertEdgelessTool(page, 'default');
|
||||
await expect(navigatorBlackBackground).toBeHidden();
|
||||
});
|
||||
|
||||
test('should be able to adjust order of presentation in toolbar', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
|
||||
await createFrame(page, [100, 100], [100, 200]);
|
||||
await createFrame(page, [200, 100], [300, 200]);
|
||||
await createFrame(page, [300, 100], [400, 200]);
|
||||
await createFrame(page, [400, 100], [500, 200]);
|
||||
|
||||
await enterPresentationMode(page);
|
||||
|
||||
await page.locator('.edgeless-frame-order-button').click();
|
||||
const frameItems = page.locator(
|
||||
'edgeless-frame-order-menu .item.draggable'
|
||||
);
|
||||
const dragIndicators = page.locator(
|
||||
'edgeless-frame-order-menu .drag-indicator'
|
||||
);
|
||||
|
||||
await expect(frameItems).toHaveCount(4);
|
||||
await expect(frameItems.nth(0)).toHaveText('Frame 1');
|
||||
await expect(frameItems.nth(1)).toHaveText('Frame 2');
|
||||
await expect(frameItems.nth(2)).toHaveText('Frame 3');
|
||||
await expect(frameItems.nth(3)).toHaveText('Frame 4');
|
||||
|
||||
// 1 2 3 4
|
||||
await frameItems.nth(2).dragTo(dragIndicators.nth(0));
|
||||
// 3 1 2 4
|
||||
await frameItems.nth(3).dragTo(dragIndicators.nth(2));
|
||||
// 3 1 4 2
|
||||
await frameItems.nth(1).dragTo(dragIndicators.nth(3));
|
||||
// 3 4 1 2
|
||||
|
||||
await expect(frameItems).toHaveCount(4);
|
||||
await expect(frameItems.nth(0)).toHaveText('Frame 3');
|
||||
await expect(frameItems.nth(1)).toHaveText('Frame 4');
|
||||
await expect(frameItems.nth(2)).toHaveText('Frame 1');
|
||||
await expect(frameItems.nth(3)).toHaveText('Frame 2');
|
||||
|
||||
const currentFrame = page.locator('.edgeless-frame-navigator-title');
|
||||
const nextButton = locatorPresentationToolbarButton(page, 'next');
|
||||
|
||||
await expect(currentFrame).toHaveText('Frame 3');
|
||||
await nextButton.click();
|
||||
await expect(currentFrame).toHaveText('Frame 4');
|
||||
await nextButton.click();
|
||||
await expect(currentFrame).toHaveText('Frame 1');
|
||||
await nextButton.click();
|
||||
await expect(currentFrame).toHaveText('Frame 2');
|
||||
});
|
||||
|
||||
test('should be able to adjust order of presentation in frame panel', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
|
||||
await createFrame(page, [100, 100], [100, 200]);
|
||||
await createFrame(page, [200, 100], [300, 200]);
|
||||
await createFrame(page, [300, 100], [400, 200]);
|
||||
await createFrame(page, [400, 100], [500, 200]);
|
||||
|
||||
// await enterPresentationMode(page);
|
||||
|
||||
await toggleFramePanel(page);
|
||||
|
||||
// await page.locator('.edgeless-frame-order-button').click();
|
||||
const frameCards = page.locator('affine-frame-card .frame-card-body');
|
||||
const frameTitles = page.locator('affine-frame-card-title .card-title');
|
||||
|
||||
await expect(frameTitles).toHaveCount(4);
|
||||
await expect(frameTitles.nth(0)).toHaveText('Frame 1');
|
||||
await expect(frameTitles.nth(1)).toHaveText('Frame 2');
|
||||
await expect(frameTitles.nth(2)).toHaveText('Frame 3');
|
||||
await expect(frameTitles.nth(3)).toHaveText('Frame 4');
|
||||
|
||||
const drag = async (from: number, to: number) => {
|
||||
const startBBox = await frameCards.nth(from).boundingBox();
|
||||
expect(startBBox).not.toBeNull();
|
||||
if (startBBox === null) return;
|
||||
|
||||
const endBBox = await frameTitles.nth(to).boundingBox();
|
||||
expect(endBBox).not.toBeNull();
|
||||
if (endBBox === null) return;
|
||||
|
||||
await page.mouse.move(
|
||||
startBBox.x + startBBox.width / 2,
|
||||
startBBox.y + startBBox.height / 2
|
||||
);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(endBBox.x + endBBox.width / 2, endBBox.y, {
|
||||
steps: 2,
|
||||
});
|
||||
await page.mouse.up();
|
||||
};
|
||||
|
||||
// 1 2 3 4
|
||||
await drag(2, 0);
|
||||
// 3 1 2 4
|
||||
await drag(3, 2);
|
||||
// 3 1 4 2
|
||||
await drag(1, 3);
|
||||
// 3 4 1 2
|
||||
|
||||
await expect(frameTitles).toHaveCount(4);
|
||||
await expect(frameTitles.nth(0)).toHaveText('Frame 3');
|
||||
await expect(frameTitles.nth(1)).toHaveText('Frame 4');
|
||||
await expect(frameTitles.nth(2)).toHaveText('Frame 1');
|
||||
await expect(frameTitles.nth(3)).toHaveText('Frame 2');
|
||||
|
||||
await enterPresentationMode(page);
|
||||
await page.locator('.edgeless-frame-order-button').click();
|
||||
const frameItems = page.locator(
|
||||
'edgeless-frame-order-menu .item.draggable'
|
||||
);
|
||||
|
||||
await expect(frameItems).toHaveCount(4);
|
||||
await expect(frameItems.nth(0)).toHaveText('Frame 3');
|
||||
await expect(frameItems.nth(1)).toHaveText('Frame 4');
|
||||
await expect(frameItems.nth(2)).toHaveText('Frame 1');
|
||||
await expect(frameItems.nth(3)).toHaveText('Frame 2');
|
||||
|
||||
const currentFrame = page.locator('.edgeless-frame-navigator-title');
|
||||
const nextButton = locatorPresentationToolbarButton(page, 'next');
|
||||
|
||||
await expect(currentFrame).toHaveText('Frame 3');
|
||||
await nextButton.click();
|
||||
await expect(currentFrame).toHaveText('Frame 4');
|
||||
await nextButton.click();
|
||||
await expect(currentFrame).toHaveText('Frame 1');
|
||||
await nextButton.click();
|
||||
await expect(currentFrame).toHaveText('Frame 2');
|
||||
});
|
||||
|
||||
test('duplicate frames should keep the presentation orders', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
|
||||
await createFrame(page, [100, 100], [100, 200]);
|
||||
await createFrame(page, [200, 100], [300, 200]);
|
||||
await createFrame(page, [300, 100], [400, 200]);
|
||||
await createFrame(page, [400, 100], [500, 200]);
|
||||
|
||||
await selectAllBlocksByKeyboard(page);
|
||||
await copyByKeyboard(page);
|
||||
await pasteByKeyboard(page);
|
||||
|
||||
await enterPresentationMode(page);
|
||||
await page.locator('.edgeless-frame-order-button').click();
|
||||
const frameItems = page.locator(
|
||||
'edgeless-frame-order-menu .item.draggable'
|
||||
);
|
||||
|
||||
await expect(frameItems).toHaveCount(8);
|
||||
await expect(frameItems.nth(0)).toHaveText('Frame 1');
|
||||
await expect(frameItems.nth(1)).toHaveText('Frame 2');
|
||||
await expect(frameItems.nth(2)).toHaveText('Frame 3');
|
||||
await expect(frameItems.nth(3)).toHaveText('Frame 4');
|
||||
await expect(frameItems.nth(4)).toHaveText('Frame 1');
|
||||
await expect(frameItems.nth(5)).toHaveText('Frame 2');
|
||||
await expect(frameItems.nth(6)).toHaveText('Frame 3');
|
||||
await expect(frameItems.nth(7)).toHaveText('Frame 4');
|
||||
});
|
||||
});
|
||||
448
blocksuite/tests-legacy/edgeless/reordering.spec.ts
Normal file
448
blocksuite/tests-legacy/edgeless/reordering.spec.ts
Normal file
@@ -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,
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
199
blocksuite/tests-legacy/edgeless/resizing.spec.ts
Normal file
199
blocksuite/tests-legacy/edgeless/resizing.spec.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
import {
|
||||
switchEditorMode,
|
||||
zoomResetByKeyboard,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import {
|
||||
addBasicBrushElement,
|
||||
addBasicRectShapeElement,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
resizeElementByHandle,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessSelectedReactCursor,
|
||||
assertEdgelessSelectedRect,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('resizing shapes and aspect ratio will be maintained', () => {
|
||||
test('positive adjustment', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicBrushElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await page.mouse.click(110, 110);
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 104, 104]);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 210, y: 110 },
|
||||
{ x: 310, y: 210 }
|
||||
);
|
||||
await page.mouse.click(220, 120);
|
||||
await assertEdgelessSelectedRect(page, [210, 110, 100, 100]);
|
||||
|
||||
await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 130 });
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 212, 112]);
|
||||
|
||||
await resizeElementByHandle(page, { x: 50, y: 50 });
|
||||
await assertEdgelessSelectedRect(page, [148, 124.19, 162, 85.81]);
|
||||
|
||||
await page.mouse.move(160, 160);
|
||||
await assertEdgelessSelectedRect(page, [148, 124.19, 162, 85.81]);
|
||||
|
||||
await page.mouse.move(260, 160);
|
||||
await assertEdgelessSelectedRect(page, [148, 124.19, 162, 85.81]);
|
||||
});
|
||||
|
||||
test('negative adjustment', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicBrushElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await page.mouse.click(110, 110);
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 104, 104]);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 210, y: 110 },
|
||||
{ x: 310, y: 210 }
|
||||
);
|
||||
await page.mouse.click(220, 120);
|
||||
await assertEdgelessSelectedRect(page, [210, 110, 100, 100]);
|
||||
|
||||
await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 130 });
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 212, 112]);
|
||||
|
||||
await resizeElementByHandle(page, { x: 400, y: 300 }, 'top-left', 30);
|
||||
await assertEdgelessSelectedRect(page, [310, 210, 356, 188]);
|
||||
|
||||
await page.mouse.move(450, 300);
|
||||
await assertEdgelessSelectedRect(page, [310, 210, 356, 188]);
|
||||
|
||||
await page.mouse.move(320, 220);
|
||||
await assertEdgelessSelectedRect(page, [310, 210, 356, 188]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('cursor style', () => {
|
||||
test('editor is aligned at the start of viewport', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 200, y: 200 },
|
||||
{ x: 300, y: 300 }
|
||||
);
|
||||
await page.mouse.click(250, 250);
|
||||
await assertEdgelessSelectedRect(page, [200, 200, 100, 100]);
|
||||
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top',
|
||||
cursor: 'ns-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'right',
|
||||
cursor: 'ew-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom',
|
||||
cursor: 'ns-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'left',
|
||||
cursor: 'ew-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top-left',
|
||||
cursor: 'nwse-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top-right',
|
||||
cursor: 'nesw-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom-left',
|
||||
cursor: 'nesw-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom-right',
|
||||
cursor: 'nwse-resize',
|
||||
});
|
||||
});
|
||||
|
||||
test('editor is not aligned at the start of viewport', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
|
||||
await page.addStyleTag({
|
||||
content: 'body { padding: 100px 150px; }',
|
||||
});
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 200, y: 200 },
|
||||
{ x: 300, y: 300 }
|
||||
);
|
||||
await page.mouse.click(250, 250);
|
||||
await assertEdgelessSelectedRect(page, [200, 200, 100, 100]);
|
||||
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top',
|
||||
cursor: 'ns-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'right',
|
||||
cursor: 'ew-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom',
|
||||
cursor: 'ns-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'left',
|
||||
cursor: 'ew-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top-left',
|
||||
cursor: 'nwse-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top-right',
|
||||
cursor: 'nesw-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom-left',
|
||||
cursor: 'nesw-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom-right',
|
||||
cursor: 'nwse-resize',
|
||||
});
|
||||
});
|
||||
});
|
||||
227
blocksuite/tests-legacy/edgeless/rotation.spec.ts
Normal file
227
blocksuite/tests-legacy/edgeless/rotation.spec.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import {
|
||||
addBasicRectShapeElement,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
resizeElementByHandle,
|
||||
rotateElementByHandle,
|
||||
switchEditorMode,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertEdgelessSelectedReactCursor,
|
||||
assertEdgelessSelectedRect,
|
||||
assertEdgelessSelectedRectRotation,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('rotation', () => {
|
||||
test('angle adjustment by four corners', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
|
||||
await rotateElementByHandle(page, 45, 'top-left');
|
||||
await assertEdgelessSelectedRectRotation(page, 45);
|
||||
|
||||
await rotateElementByHandle(page, 45, 'top-right');
|
||||
await assertEdgelessSelectedRectRotation(page, 90);
|
||||
|
||||
await rotateElementByHandle(page, 45, 'bottom-right');
|
||||
await assertEdgelessSelectedRectRotation(page, 135);
|
||||
|
||||
await rotateElementByHandle(page, 45, 'bottom-left');
|
||||
await assertEdgelessSelectedRectRotation(page, 180);
|
||||
});
|
||||
|
||||
test('angle snap', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
|
||||
await page.keyboard.down('Shift');
|
||||
|
||||
await rotateElementByHandle(page, 5);
|
||||
await assertEdgelessSelectedRectRotation(page, 0);
|
||||
|
||||
await rotateElementByHandle(page, 10);
|
||||
await assertEdgelessSelectedRectRotation(page, 15);
|
||||
|
||||
await rotateElementByHandle(page, 10);
|
||||
await assertEdgelessSelectedRectRotation(page, 30);
|
||||
|
||||
await rotateElementByHandle(page, 10);
|
||||
await assertEdgelessSelectedRectRotation(page, 45);
|
||||
|
||||
await rotateElementByHandle(page, 5);
|
||||
await assertEdgelessSelectedRectRotation(page, 45);
|
||||
|
||||
await page.keyboard.up('Shift');
|
||||
});
|
||||
|
||||
test('single shape', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
await rotateElementByHandle(page, 45, 'top-right');
|
||||
await assertEdgelessSelectedRectRotation(page, 45);
|
||||
});
|
||||
|
||||
test('multiple shapes', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 200, y: 100 },
|
||||
{ x: 300, y: 200 }
|
||||
);
|
||||
|
||||
await dragBetweenCoords(page, { x: 90, y: 90 }, { x: 310, y: 110 });
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 200, 100]);
|
||||
|
||||
await rotateElementByHandle(page, 90, 'bottom-right');
|
||||
await assertEdgelessSelectedRectRotation(page, 0);
|
||||
await assertEdgelessSelectedRect(page, [150, 50, 100, 200]);
|
||||
});
|
||||
|
||||
test('combination with resizing', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
await rotateElementByHandle(page, 90, 'bottom-left');
|
||||
await assertEdgelessSelectedRectRotation(page, 90);
|
||||
|
||||
await resizeElementByHandle(page, { x: 10, y: -10 }, 'bottom-right');
|
||||
await assertEdgelessSelectedRect(page, [110, 100, 90, 90]);
|
||||
|
||||
await rotateElementByHandle(page, -90, 'bottom-right');
|
||||
await assertEdgelessSelectedRectRotation(page, 0);
|
||||
|
||||
await resizeElementByHandle(page, { x: 10, y: 10 }, 'bottom-right');
|
||||
await assertEdgelessSelectedRect(page, [110, 100, 100, 100]);
|
||||
});
|
||||
|
||||
test('combination with resizing for multiple shapes', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 200, y: 100 },
|
||||
{ x: 300, y: 200 }
|
||||
);
|
||||
|
||||
await dragBetweenCoords(page, { x: 90, y: 90 }, { x: 310, y: 110 });
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 200, 100]);
|
||||
|
||||
await rotateElementByHandle(page, 90, 'bottom-left');
|
||||
await assertEdgelessSelectedRectRotation(page, 0);
|
||||
await assertEdgelessSelectedRect(page, [150, 50, 100, 200]);
|
||||
|
||||
await resizeElementByHandle(page, { x: -10, y: -20 }, 'bottom-right');
|
||||
await assertEdgelessSelectedRect(page, [150, 50, 90, 180]);
|
||||
|
||||
await rotateElementByHandle(page, -90, 'bottom-right');
|
||||
await assertEdgelessSelectedRectRotation(page, 0);
|
||||
await assertEdgelessSelectedRect(page, [105, 95, 180, 90]);
|
||||
|
||||
await resizeElementByHandle(page, { x: 20, y: 10 }, 'bottom-right');
|
||||
await assertEdgelessSelectedRect(page, [105, 95, 200, 100]);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('cursor style', () => {
|
||||
test('update resize cursor direction after rotating', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addBasicRectShapeElement(
|
||||
page,
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 }
|
||||
);
|
||||
|
||||
await rotateElementByHandle(page, 45, 'top-left');
|
||||
await assertEdgelessSelectedRectRotation(page, 45);
|
||||
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top',
|
||||
cursor: 'nesw-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'right',
|
||||
cursor: 'nwse-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom',
|
||||
cursor: 'nesw-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'left',
|
||||
cursor: 'nwse-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top-right',
|
||||
cursor: 'ew-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'top-left',
|
||||
cursor: 'ns-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom-right',
|
||||
cursor: 'ns-resize',
|
||||
});
|
||||
await assertEdgelessSelectedReactCursor(page, {
|
||||
mode: 'resize',
|
||||
handle: 'bottom-left',
|
||||
cursor: 'ew-resize',
|
||||
});
|
||||
});
|
||||
});
|
||||
94
blocksuite/tests-legacy/edgeless/selection/connector.spec.ts
Normal file
94
blocksuite/tests-legacy/edgeless/selection/connector.spec.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import * as actions from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
addBasicConnectorElement,
|
||||
createConnectorElement,
|
||||
createShapeElement,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
initEmptyEdgelessState,
|
||||
Shape,
|
||||
switchEditorMode,
|
||||
toModelCoord,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/index.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test.describe('select multiple connectors', () => {
|
||||
test('should show single selection rect', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicConnectorElement(
|
||||
page,
|
||||
{ x: 100, y: 200 },
|
||||
{ x: 300, y: 200 }
|
||||
);
|
||||
await addBasicConnectorElement(
|
||||
page,
|
||||
{ x: 100, y: 230 },
|
||||
{ x: 300, y: 230 }
|
||||
);
|
||||
await addBasicConnectorElement(
|
||||
page,
|
||||
{ x: 100, y: 260 },
|
||||
{ x: 300, y: 260 }
|
||||
);
|
||||
|
||||
await dragBetweenCoords(page, { x: 50, y: 50 }, { x: 400, y: 290 });
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(
|
||||
await page
|
||||
.locator('.affine-edgeless-selected-rect')
|
||||
.locator('.element-handle')
|
||||
.count()
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
test('should disable resize when a connector is already connected', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
const start = await toModelCoord(page, [100, 0]);
|
||||
const end = await toModelCoord(page, [200, 100]);
|
||||
await createShapeElement(page, start, end, Shape.Diamond);
|
||||
const c1 = await toModelCoord(page, [200, 50]);
|
||||
const c2 = await toModelCoord(page, [450, 50]);
|
||||
await createConnectorElement(page, c1, c2);
|
||||
|
||||
await addBasicConnectorElement(
|
||||
page,
|
||||
{ x: 250, y: 200 },
|
||||
{ x: 450, y: 200 }
|
||||
);
|
||||
await addBasicConnectorElement(
|
||||
page,
|
||||
{ x: 250, y: 230 },
|
||||
{ x: 450, y: 230 }
|
||||
);
|
||||
await addBasicConnectorElement(
|
||||
page,
|
||||
{ x: 250, y: 260 },
|
||||
{ x: 450, y: 260 }
|
||||
);
|
||||
|
||||
await dragBetweenCoords(page, { x: 500, y: 20 }, { x: 400, y: 290 });
|
||||
await waitNextFrame(page);
|
||||
|
||||
const selectedRectLocalor = page.locator('.affine-edgeless-selected-rect');
|
||||
expect(await selectedRectLocalor.locator('.element-handle').count()).toBe(
|
||||
0
|
||||
);
|
||||
expect(
|
||||
await selectedRectLocalor.locator('.handle').locator('.resize').count()
|
||||
).toBe(0);
|
||||
});
|
||||
});
|
||||
265
blocksuite/tests-legacy/edgeless/selection/keyboard.spec.ts
Normal file
265
blocksuite/tests-legacy/edgeless/selection/keyboard.spec.ts
Normal file
@@ -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);
|
||||
});
|
||||
466
blocksuite/tests-legacy/edgeless/selection/selection.spec.ts
Normal file
466
blocksuite/tests-legacy/edgeless/selection/selection.spec.ts
Normal file
@@ -0,0 +1,466 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import * as actions from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
getNoteBoundBoxInEdgeless,
|
||||
setEdgelessTool,
|
||||
switchEditorMode,
|
||||
} from '../../utils/actions/edgeless.js';
|
||||
import {
|
||||
addBasicBrushElement,
|
||||
addBasicRectShapeElement,
|
||||
click,
|
||||
clickInCenter,
|
||||
dragBetweenCoords,
|
||||
enterPlaygroundRoom,
|
||||
getBoundingRect,
|
||||
initEmptyEdgelessState,
|
||||
initThreeParagraphs,
|
||||
pressEnter,
|
||||
waitNextFrame,
|
||||
} from '../../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockCount,
|
||||
assertEdgelessRemoteSelectedModelRect,
|
||||
assertEdgelessRemoteSelectedRect,
|
||||
assertEdgelessSelectedModelRect,
|
||||
assertEdgelessSelectedRect,
|
||||
assertSelectionInNote,
|
||||
} from '../../utils/asserts.js';
|
||||
import { test } from '../../utils/playwright.js';
|
||||
|
||||
test('should update rect of selection when resizing viewport', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await actions.switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 130 });
|
||||
|
||||
const selectedRectClass = '.affine-edgeless-selected-rect';
|
||||
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
await actions.decreaseZoomLevel(page);
|
||||
await waitNextFrame(page);
|
||||
await actions.decreaseZoomLevel(page);
|
||||
await waitNextFrame(page);
|
||||
const selectedRectInZoom = await getBoundingRect(page, selectedRectClass);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
selectedRectInZoom.x,
|
||||
selectedRectInZoom.y,
|
||||
50,
|
||||
50,
|
||||
]);
|
||||
|
||||
await actions.switchEditorEmbedMode(page);
|
||||
await waitNextFrame(page);
|
||||
const selectedRectInEmbed = await getBoundingRect(page, selectedRectClass);
|
||||
await assertEdgelessSelectedRect(page, [
|
||||
selectedRectInEmbed.x,
|
||||
selectedRectInEmbed.y,
|
||||
50,
|
||||
50,
|
||||
]);
|
||||
|
||||
await actions.switchEditorEmbedMode(page);
|
||||
await actions.increaseZoomLevel(page);
|
||||
await waitNextFrame(page);
|
||||
await actions.increaseZoomLevel(page);
|
||||
await waitNextFrame(page);
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
});
|
||||
|
||||
test('should update react of remote selection when resizing viewport', async ({
|
||||
context,
|
||||
page: pageA,
|
||||
}) => {
|
||||
const room = await enterPlaygroundRoom(pageA);
|
||||
await initEmptyEdgelessState(pageA);
|
||||
await actions.switchEditorMode(pageA);
|
||||
await actions.zoomResetByKeyboard(pageA);
|
||||
|
||||
const pageB = await context.newPage();
|
||||
await enterPlaygroundRoom(pageB, {
|
||||
room,
|
||||
noInit: true,
|
||||
});
|
||||
await actions.switchEditorMode(pageB);
|
||||
await actions.zoomResetByKeyboard(pageB);
|
||||
|
||||
await actions.createShapeElement(
|
||||
pageA,
|
||||
[0, 0],
|
||||
[100, 100],
|
||||
actions.Shape.Square
|
||||
);
|
||||
const point = await actions.toViewCoord(pageA, [50, 50]);
|
||||
await click(pageA, { x: point[0], y: point[1] });
|
||||
await click(pageB, { x: point[0], y: point[1] });
|
||||
|
||||
await assertEdgelessSelectedModelRect(pageB, [0, 0, 100, 100]);
|
||||
await assertEdgelessRemoteSelectedModelRect(pageB, [0, 0, 100, 100]);
|
||||
|
||||
// to 50%
|
||||
await actions.decreaseZoomLevel(pageB);
|
||||
await waitNextFrame(pageB);
|
||||
await actions.decreaseZoomLevel(pageB);
|
||||
await waitNextFrame(pageB);
|
||||
|
||||
const selectedRectInZoom = await getBoundingRect(
|
||||
pageB,
|
||||
'.affine-edgeless-selected-rect'
|
||||
);
|
||||
await assertEdgelessRemoteSelectedRect(pageB, [
|
||||
selectedRectInZoom.x,
|
||||
selectedRectInZoom.y,
|
||||
50,
|
||||
50,
|
||||
]);
|
||||
});
|
||||
|
||||
test('select multiple shapes and translate', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicBrushElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await page.mouse.click(110, 110);
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 104, 104]);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 210, y: 110 }, { x: 310, y: 210 });
|
||||
await page.mouse.click(220, 120);
|
||||
await assertEdgelessSelectedRect(page, [210, 110, 100, 100]);
|
||||
|
||||
await dragBetweenCoords(page, { x: 120, y: 90 }, { x: 220, y: 130 });
|
||||
await assertEdgelessSelectedRect(page, [98, 98, 212, 112]);
|
||||
|
||||
await dragBetweenCoords(page, { x: 120, y: 120 }, { x: 150, y: 150 });
|
||||
await assertEdgelessSelectedRect(page, [125, 128, 212, 112]);
|
||||
|
||||
await page.mouse.click(160, 160);
|
||||
await assertEdgelessSelectedRect(page, [125, 128, 104, 104]);
|
||||
|
||||
await page.mouse.click(250, 150);
|
||||
await assertEdgelessSelectedRect(page, [237, 140, 100, 100]);
|
||||
});
|
||||
|
||||
test('selection box of shape element sync on fast dragging', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
await setEdgelessTool(page, 'shape');
|
||||
await dragBetweenCoords(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
await setEdgelessTool(page, 'default');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: 110, y: 110 },
|
||||
{ x: 660, y: 460 },
|
||||
{ click: true }
|
||||
);
|
||||
|
||||
await assertEdgelessSelectedRect(page, [650, 446, 100, 100]);
|
||||
});
|
||||
|
||||
test('when the selection is always a note, it should remain in an active state', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
const ids = await initEmptyEdgelessState(page);
|
||||
await initThreeParagraphs(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
const bound = await getNoteBoundBoxInEdgeless(page, ids.noteId);
|
||||
|
||||
await setEdgelessTool(page, 'note');
|
||||
const newNoteX = bound.x;
|
||||
const newNoteY = bound.y + bound.height + 100;
|
||||
// add text
|
||||
await page.mouse.click(newNoteX, newNoteY);
|
||||
await waitNextFrame(page);
|
||||
await page.keyboard.type('hello');
|
||||
await pressEnter(page);
|
||||
// should wait for inline editor update and resizeObserver callback
|
||||
await waitNextFrame(page);
|
||||
// assert add text success
|
||||
await assertBlockCount(page, 'edgeless-note', 2);
|
||||
|
||||
await clickInCenter(page, bound);
|
||||
await clickInCenter(page, bound);
|
||||
await waitNextFrame(page);
|
||||
await assertSelectionInNote(page, ids.noteId, 'affine-edgeless-note');
|
||||
});
|
||||
|
||||
test('should auto panning when selection rectangle reaches viewport edges', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 200, y: 100 }, { x: 300, y: 200 });
|
||||
await page.mouse.click(210, 110);
|
||||
await assertEdgelessSelectedRect(page, [200, 100, 100, 100]);
|
||||
|
||||
const selectedRectClass = '.affine-edgeless-selected-rect';
|
||||
|
||||
// Panning to the left
|
||||
await setEdgelessTool(page, 'pan');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 600,
|
||||
y: 200,
|
||||
},
|
||||
{
|
||||
x: 200,
|
||||
y: 200,
|
||||
}
|
||||
);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.click(210, 110);
|
||||
let selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(300);
|
||||
await expect(selectedRect).toBeHidden();
|
||||
// Click to start selection and hold the mouse to trigger auto panning to the left
|
||||
await page.mouse.move(210, 110);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(0, 210, { steps: 20 });
|
||||
await page.waitForTimeout(500);
|
||||
await page.mouse.up();
|
||||
|
||||
// Expect to select the shape element
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(300);
|
||||
await expect(selectedRect).toBeVisible();
|
||||
|
||||
// Panning to the top
|
||||
await page.mouse.click(400, 600);
|
||||
await setEdgelessTool(page, 'pan');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 400,
|
||||
y: 600,
|
||||
},
|
||||
{
|
||||
x: 400,
|
||||
y: 100,
|
||||
}
|
||||
);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.click(600, 100);
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(300);
|
||||
await expect(selectedRect).toBeHidden();
|
||||
// Click to start selection and hold the mouse to trigger auto panning to the top
|
||||
await page.mouse.move(600, 100);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(400, 0, { steps: 20 });
|
||||
await page.waitForTimeout(500);
|
||||
await page.mouse.up();
|
||||
|
||||
// Expect to select the empty note
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(300);
|
||||
await expect(selectedRect).toBeVisible();
|
||||
|
||||
// Panning to the right
|
||||
await page.mouse.click(100, 600);
|
||||
await setEdgelessTool(page, 'pan');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 20,
|
||||
y: 600,
|
||||
},
|
||||
{
|
||||
x: 1000,
|
||||
y: 600,
|
||||
}
|
||||
);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.click(800, 600);
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(100);
|
||||
await expect(selectedRect).toBeHidden();
|
||||
// Click to start selection and hold the mouse to trigger auto panning to the right
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 800,
|
||||
y: 600,
|
||||
},
|
||||
{
|
||||
x: 1000,
|
||||
y: 200,
|
||||
},
|
||||
{
|
||||
beforeMouseUp: async () => {
|
||||
await page.waitForTimeout(600);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Expect to select the empty note
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(300);
|
||||
await expect(selectedRect).toBeVisible();
|
||||
|
||||
// Panning to the bottom
|
||||
await page.mouse.click(400, 100);
|
||||
await setEdgelessTool(page, 'pan');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 400,
|
||||
y: 100,
|
||||
},
|
||||
{
|
||||
x: 400,
|
||||
y: 850,
|
||||
},
|
||||
{
|
||||
click: true,
|
||||
}
|
||||
);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await waitNextFrame(page, 500);
|
||||
await page.mouse.click(400, 400);
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(100);
|
||||
await expect(selectedRect).toBeHidden();
|
||||
|
||||
// Click to start selection and hold the mouse to trigger auto panning to the right
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 800,
|
||||
y: 300,
|
||||
},
|
||||
{
|
||||
x: 820,
|
||||
y: 1150,
|
||||
},
|
||||
{
|
||||
click: true,
|
||||
beforeMouseUp: async () => {
|
||||
await page.waitForTimeout(500);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Expect to select the empty note
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(300);
|
||||
await expect(selectedRect).toBeVisible();
|
||||
});
|
||||
|
||||
test('should also update dragging area when viewport changes', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
// Panning to the top
|
||||
await page.mouse.click(400, 600);
|
||||
await setEdgelessTool(page, 'pan');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{
|
||||
x: 400,
|
||||
y: 600,
|
||||
},
|
||||
{
|
||||
x: 400,
|
||||
y: 100,
|
||||
}
|
||||
);
|
||||
await setEdgelessTool(page, 'default');
|
||||
await page.mouse.click(200, 300);
|
||||
|
||||
const selectedRectClass = '.affine-edgeless-selected-rect';
|
||||
let selectedRect = page.locator(selectedRectClass);
|
||||
await expect(selectedRect).toBeHidden();
|
||||
// set up initial dragging area
|
||||
await page.mouse.move(200, 300);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(600, 200, { steps: 20 });
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// wheel the viewport to the top
|
||||
await page.mouse.wheel(0, -300);
|
||||
await page.waitForTimeout(300);
|
||||
await page.mouse.up();
|
||||
|
||||
// Expect to select the empty note
|
||||
selectedRect = page.locator(selectedRectClass);
|
||||
await page.waitForTimeout(300);
|
||||
await expect(selectedRect).toBeVisible();
|
||||
await page.waitForTimeout(300);
|
||||
});
|
||||
|
||||
test('should select shapes while moving selection', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await actions.zoomResetByKeyboard(page);
|
||||
|
||||
await addBasicRectShapeElement(page, { x: 100, y: 100 }, { x: 200, y: 200 });
|
||||
|
||||
// Make the selection out side the rect and move the selection to the rect
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
// Make the selection not selecting the rect
|
||||
{ x: 70, y: 70 },
|
||||
{ x: 90, y: 90 },
|
||||
{
|
||||
beforeMouseUp: async () => {
|
||||
await page.keyboard.down('Space');
|
||||
// Move the selection over to the rect
|
||||
await page.mouse.move(120, 120);
|
||||
await page.keyboard.up('Space');
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 100, 100, 100]);
|
||||
|
||||
await addBasicBrushElement(page, { x: 210, y: 100 }, { x: 310, y: 300 });
|
||||
await page.mouse.click(211, 101);
|
||||
|
||||
// Make a wide selection and move it to select both of the shapes
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
// Make the selection above the spaces
|
||||
{ x: 70, y: 70 },
|
||||
{ x: 400, y: 90 },
|
||||
{
|
||||
beforeMouseUp: async () => {
|
||||
await page.keyboard.down('Space');
|
||||
// Move the selection over both of the shapes
|
||||
await page.mouse.move(400, 120);
|
||||
await page.keyboard.up('Space');
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await assertEdgelessSelectedRect(page, [100, 98, 212, 204]);
|
||||
});
|
||||
727
blocksuite/tests-legacy/edgeless/shape.spec.ts
Normal file
727
blocksuite/tests-legacy/edgeless/shape.spec.ts
Normal file
@@ -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');
|
||||
});
|
||||
});
|
||||
324
blocksuite/tests-legacy/edgeless/shortcut.spec.ts
Normal file
324
blocksuite/tests-legacy/edgeless/shortcut.spec.ts
Normal file
@@ -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]);
|
||||
});
|
||||
});
|
||||
45
blocksuite/tests-legacy/edgeless/snap.spec.ts
Normal file
45
blocksuite/tests-legacy/edgeless/snap.spec.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { undoByClick } from '../utils/actions/click.js';
|
||||
import {
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
Shape,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import { waitNextFrame } from '../utils/actions/misc.js';
|
||||
import { assertSelectedBound } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.describe('snap', () => {
|
||||
test('snap', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [300, 0], [300 + 100, 100], Shape.Square);
|
||||
|
||||
await assertSelectedBound(page, [300, 0, 100, 100]);
|
||||
|
||||
await dragBetweenViewCoords(page, [300 + 5, 50], [300 + 5, 50 + 5]);
|
||||
await assertSelectedBound(page, [300, 5, 100, 100]);
|
||||
|
||||
await undoByClick(page);
|
||||
await dragBetweenViewCoords(page, [300 + 5, 50], [300 + 5, 50 + 3]);
|
||||
await assertSelectedBound(page, [300, 0, 100, 100]);
|
||||
});
|
||||
|
||||
test('snapDistribute', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
|
||||
await createShapeElement(page, [0, 0], [100, 100], Shape.Square);
|
||||
await createShapeElement(page, [300, 0], [300 + 100, 100], Shape.Square);
|
||||
await createShapeElement(page, [144, 0], [144 + 100, 100], Shape.Square);
|
||||
|
||||
await assertSelectedBound(page, [144, 0, 100, 100]);
|
||||
await dragBetweenViewCoords(
|
||||
page,
|
||||
[144 + 100 - 9, 100 - 9],
|
||||
[144 + 100 - 9 + 3, 100 - 9]
|
||||
);
|
||||
await assertSelectedBound(page, [150, 0, 100, 100]);
|
||||
await waitNextFrame(page);
|
||||
});
|
||||
});
|
||||
316
blocksuite/tests-legacy/edgeless/text.spec.ts
Normal file
316
blocksuite/tests-legacy/edgeless/text.spec.ts
Normal file
@@ -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);
|
||||
});
|
||||
});
|
||||
268
blocksuite/tests-legacy/embed-synced-doc.spec.ts
Normal file
268
blocksuite/tests-legacy/embed-synced-doc.spec.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
import type { DatabaseBlockModel } from '@blocksuite/affine-model';
|
||||
import { assertExists } from '@blocksuite/global/utils';
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
import { switchEditorMode } from 'utils/actions/edgeless.js';
|
||||
import { getLinkedDocPopover } from 'utils/actions/linked-doc.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
initEmptyEdgelessState,
|
||||
initEmptyParagraphState,
|
||||
waitNextFrame,
|
||||
} from 'utils/actions/misc.js';
|
||||
|
||||
import { test } from './utils/playwright.js';
|
||||
|
||||
test.describe('Embed synced doc', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
});
|
||||
|
||||
async function createAndConvertToEmbedSyncedDoc(page: Page) {
|
||||
const { createLinkedDoc } = getLinkedDocPopover(page);
|
||||
const linkedDoc = await createLinkedDoc('page1');
|
||||
const lickedDocBox = await linkedDoc.boundingBox();
|
||||
assertExists(lickedDocBox);
|
||||
await page.mouse.move(
|
||||
lickedDocBox.x + lickedDocBox.width / 2,
|
||||
lickedDocBox.y + lickedDocBox.height / 2
|
||||
);
|
||||
|
||||
await waitNextFrame(page, 200);
|
||||
const referencePopup = page.locator('.affine-reference-popover-container');
|
||||
await expect(referencePopup).toBeVisible();
|
||||
|
||||
const switchButton = page.getByRole('button', { name: 'Switch view' });
|
||||
await switchButton.click();
|
||||
|
||||
const embedSyncedDocBtn = page.getByRole('button', { name: 'Embed view' });
|
||||
await expect(embedSyncedDocBtn).toBeVisible();
|
||||
|
||||
await embedSyncedDocBtn.click();
|
||||
await waitNextFrame(page, 200);
|
||||
|
||||
const embedSyncedBlock = page.locator('affine-embed-synced-doc-block');
|
||||
expect(await embedSyncedBlock.count()).toBe(1);
|
||||
}
|
||||
|
||||
test('can change linked doc to embed synced doc', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await createAndConvertToEmbedSyncedDoc(page);
|
||||
});
|
||||
|
||||
test('can change embed synced doc to card view', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await createAndConvertToEmbedSyncedDoc(page);
|
||||
|
||||
const syncedDoc = page.locator(`affine-embed-synced-doc-block`);
|
||||
const syncedDocBox = await syncedDoc.boundingBox();
|
||||
assertExists(syncedDocBox);
|
||||
await page.mouse.click(
|
||||
syncedDocBox.x + syncedDocBox.width / 2,
|
||||
syncedDocBox.y + syncedDocBox.height / 2
|
||||
);
|
||||
|
||||
await waitNextFrame(page, 200);
|
||||
const toolbar = page.locator('.embed-card-toolbar');
|
||||
await expect(toolbar).toBeVisible();
|
||||
|
||||
const switchBtn = toolbar.getByRole('button', { name: 'Switch view' });
|
||||
await expect(switchBtn).toBeVisible();
|
||||
|
||||
await switchBtn.click();
|
||||
await waitNextFrame(page, 200);
|
||||
|
||||
const cardBtn = toolbar.getByRole('button', { name: 'Card view' });
|
||||
await cardBtn.click();
|
||||
await waitNextFrame(page, 200);
|
||||
|
||||
const embedSyncedBlock = page.locator('affine-embed-linked-doc-block');
|
||||
expect(await embedSyncedBlock.count()).toBe(1);
|
||||
});
|
||||
|
||||
test.fixme(
|
||||
'drag embed synced doc to whiteboard should fit in height',
|
||||
async ({ page }) => {
|
||||
await initEmptyEdgelessState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await createAndConvertToEmbedSyncedDoc(page);
|
||||
|
||||
// Focus on the embed synced doc
|
||||
const embedSyncedBlock = page.locator('affine-embed-synced-doc-block');
|
||||
let embedSyncedBox = await embedSyncedBlock.boundingBox();
|
||||
assertExists(embedSyncedBox);
|
||||
await page.mouse.click(
|
||||
embedSyncedBox.x + embedSyncedBox.width / 2,
|
||||
embedSyncedBox.y + embedSyncedBox.height / 2
|
||||
);
|
||||
|
||||
// Switch to edgeless mode
|
||||
await switchEditorMode(page);
|
||||
await waitNextFrame(page, 200);
|
||||
|
||||
// Double click on note to enter edit status
|
||||
const noteBlock = page.locator('affine-edgeless-note');
|
||||
const noteBlockBox = await noteBlock.boundingBox();
|
||||
assertExists(noteBlockBox);
|
||||
await page.mouse.dblclick(noteBlockBox.x + 10, noteBlockBox.y + 10);
|
||||
await waitNextFrame(page, 200);
|
||||
|
||||
// Drag the embed synced doc to whiteboard
|
||||
embedSyncedBox = await embedSyncedBlock.boundingBox();
|
||||
assertExists(embedSyncedBox);
|
||||
const height = embedSyncedBox.height;
|
||||
await page.mouse.move(embedSyncedBox.x - 10, embedSyncedBox.y - 100);
|
||||
await page.mouse.move(embedSyncedBox.x - 10, embedSyncedBox.y + 10);
|
||||
await waitNextFrame(page);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(100, 200, { steps: 30 });
|
||||
await page.mouse.up();
|
||||
|
||||
// Check the height of the embed synced doc portal, it should be the same as the embed synced doc in note
|
||||
const EmbedSyncedDocBlock = page.locator(
|
||||
'affine-embed-edgeless-synced-doc-block'
|
||||
);
|
||||
const EmbedSyncedDocBlockBox = await EmbedSyncedDocBlock.boundingBox();
|
||||
const border = 1;
|
||||
assertExists(EmbedSyncedDocBlockBox);
|
||||
expect(EmbedSyncedDocBlockBox.height).toBeCloseTo(height + 2 * border, 1);
|
||||
}
|
||||
);
|
||||
|
||||
test('nested embed synced doc should be rendered as card when depth >=1', async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.evaluate(() => {
|
||||
const { doc, collection } = window;
|
||||
const rootId = doc.addBlock('affine:page', {
|
||||
title: new doc.Text(),
|
||||
});
|
||||
|
||||
const noteId = doc.addBlock('affine:note', {}, rootId);
|
||||
doc.addBlock('affine:paragraph', {}, noteId);
|
||||
|
||||
const doc2 = collection.createDoc({ id: 'doc2' });
|
||||
doc2.load();
|
||||
const rootId2 = doc2.addBlock('affine:page', {
|
||||
title: new doc.Text('Doc 2'),
|
||||
});
|
||||
|
||||
const noteId2 = doc2.addBlock('affine:note', {}, rootId2);
|
||||
doc2.addBlock(
|
||||
'affine:paragraph',
|
||||
{
|
||||
text: new doc.Text('Hello from Doc 2'),
|
||||
},
|
||||
noteId2
|
||||
);
|
||||
|
||||
const doc3 = collection.createDoc({ id: 'doc3' });
|
||||
doc3.load();
|
||||
const rootId3 = doc3.addBlock('affine:page', {
|
||||
title: new doc.Text('Doc 3'),
|
||||
});
|
||||
|
||||
const noteId3 = doc3.addBlock('affine:note', {}, rootId3);
|
||||
doc3.addBlock(
|
||||
'affine:paragraph',
|
||||
{
|
||||
text: new doc.Text('Hello from Doc 3'),
|
||||
},
|
||||
noteId3
|
||||
);
|
||||
|
||||
doc2.addBlock(
|
||||
'affine:embed-synced-doc',
|
||||
{
|
||||
pageId: 'doc3',
|
||||
},
|
||||
noteId2
|
||||
);
|
||||
doc.addBlock(
|
||||
'affine:embed-synced-doc',
|
||||
{
|
||||
pageId: 'doc2',
|
||||
},
|
||||
noteId
|
||||
);
|
||||
});
|
||||
expect(await page.locator('affine-embed-synced-doc-block').count()).toBe(2);
|
||||
expect(await page.locator('affine-paragraph').count()).toBe(2);
|
||||
expect(await page.locator('affine-embed-synced-doc-card').count()).toBe(1);
|
||||
expect(await page.locator('editor-host').count()).toBe(2);
|
||||
});
|
||||
|
||||
test.describe('synced doc should be readonly', () => {
|
||||
test('synced doc should be readonly', async ({ page }) => {
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await createAndConvertToEmbedSyncedDoc(page);
|
||||
const locator = page.locator('affine-embed-synced-doc-block');
|
||||
await locator.click();
|
||||
|
||||
const toolbar = page.locator('editor-toolbar');
|
||||
const openMenu = toolbar.getByRole('button', { name: 'Open' });
|
||||
await openMenu.click();
|
||||
|
||||
const button = toolbar.getByRole('button', { name: 'Open this doc' });
|
||||
await button.click();
|
||||
|
||||
await page.evaluate(async () => {
|
||||
const { collection } = window;
|
||||
const getDocCollection = () => {
|
||||
for (const [id, doc] of collection.docs.entries()) {
|
||||
if (id === 'doc:home') {
|
||||
continue;
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const doc2Collection = getDocCollection();
|
||||
const doc2 = doc2Collection!.getDoc();
|
||||
const [noteBlock] = doc2!.getBlocksByFlavour('affine:note');
|
||||
const noteId = noteBlock.id;
|
||||
|
||||
const databaseId = doc2.addBlock(
|
||||
'affine:database',
|
||||
{
|
||||
title: new doc2.Text('Database 1'),
|
||||
},
|
||||
noteId
|
||||
);
|
||||
const model = doc2.getBlockById(databaseId) as DatabaseBlockModel;
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
const databaseBlock = document.querySelector('affine-database');
|
||||
const databaseService = databaseBlock?.service;
|
||||
if (databaseService) {
|
||||
databaseService.databaseViewInitEmpty(
|
||||
model,
|
||||
databaseService.viewPresets.tableViewMeta.type
|
||||
);
|
||||
databaseService.applyColumnUpdate(model);
|
||||
}
|
||||
});
|
||||
|
||||
// go back to previous doc
|
||||
await page.evaluate(() => {
|
||||
const { collection, editor } = window;
|
||||
editor.doc = collection.getDoc('doc:home')!;
|
||||
});
|
||||
|
||||
const databaseFirstCell = page.locator(
|
||||
'.affine-database-column-header.database-row'
|
||||
);
|
||||
await databaseFirstCell.click({ force: true });
|
||||
const indicatorCount = await page
|
||||
.locator('affine-drag-indicator')
|
||||
.count();
|
||||
expect(indicatorCount).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
BIN
blocksuite/tests-legacy/fixtures/smile.png
Normal file
BIN
blocksuite/tests-legacy/fixtures/smile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 96 B |
1027
blocksuite/tests-legacy/format-bar.spec.ts
Normal file
1027
blocksuite/tests-legacy/format-bar.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
356
blocksuite/tests-legacy/fragments/frame-panel.spec.ts
Normal file
356
blocksuite/tests-legacy/fragments/frame-panel.spec.ts
Normal file
@@ -0,0 +1,356 @@
|
||||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { dragBetweenCoords } from 'utils/actions/drag.js';
|
||||
import {
|
||||
assertEdgelessNonSelectedRect,
|
||||
assertEdgelessSelectedRect,
|
||||
assertZoomLevel,
|
||||
} from 'utils/asserts.js';
|
||||
|
||||
import {
|
||||
addBasicShapeElement,
|
||||
addNote,
|
||||
createNote,
|
||||
createShapeElement,
|
||||
dragBetweenViewCoords,
|
||||
edgelessCommonSetup,
|
||||
enterPresentationMode,
|
||||
getZoomLevel,
|
||||
setEdgelessTool,
|
||||
Shape,
|
||||
switchEditorMode,
|
||||
toggleFramePanel,
|
||||
} from '../utils/actions/edgeless.js';
|
||||
import { waitNextFrame } from '../utils/actions/index.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
async function dragFrameCard(
|
||||
page: Page,
|
||||
fromCard: Locator,
|
||||
toCard: Locator,
|
||||
direction: 'up' | 'down' = 'down'
|
||||
) {
|
||||
const fromRect = await fromCard.boundingBox();
|
||||
const toRect = await toCard.boundingBox();
|
||||
// drag to the center of the toCard
|
||||
const center = { x: toRect!.width / 2, y: toRect!.height / 2 };
|
||||
const offset = direction === 'up' ? { x: 0, y: -20 } : { x: 0, y: 20 };
|
||||
await page.mouse.move(fromRect!.x + center.x, fromRect!.y + center.y);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(
|
||||
toRect!.x + center.x + offset.x,
|
||||
toRect!.y + center.y + offset.y,
|
||||
{ steps: 10 }
|
||||
);
|
||||
await page.mouse.up();
|
||||
}
|
||||
|
||||
test.describe('frame panel', () => {
|
||||
test('should display empty placeholder when no frames', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await toggleFramePanel(page);
|
||||
const frameCards = page.locator('affine-frame-card');
|
||||
expect(await frameCards.count()).toBe(0);
|
||||
|
||||
const placeholder = page.locator('.no-frame-placeholder');
|
||||
expect(await placeholder.isVisible()).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should display frame cards when there are frames', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await toggleFramePanel(page);
|
||||
|
||||
await addBasicShapeElement(
|
||||
page,
|
||||
{ x: 300, y: 300 },
|
||||
{ x: 350, y: 350 },
|
||||
Shape.Square
|
||||
);
|
||||
|
||||
await addNote(page, 'hello', 150, 500);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(page, { x: 250, y: 250 }, { x: 360, y: 360 });
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(page, { x: 100, y: 440 }, { x: 600, y: 600 });
|
||||
|
||||
const frames = page.locator('affine-frame');
|
||||
expect(await frames.count()).toBe(2);
|
||||
const frameCards = page.locator('affine-frame-card');
|
||||
expect(await frameCards.count()).toBe(2);
|
||||
});
|
||||
|
||||
test('should render edgeless note correctly in frame preview', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await toggleFramePanel(page);
|
||||
|
||||
await addNote(page, 'hello', 150, 500);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(page, { x: 100, y: 440 }, { x: 600, y: 600 });
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
const frames = page.locator('affine-frame');
|
||||
expect(await frames.count()).toBe(1);
|
||||
const frameCards = page.locator('affine-frame-card');
|
||||
expect(await frameCards.count()).toBe(1);
|
||||
const edgelessNote = page.locator('affine-frame-card affine-edgeless-note');
|
||||
expect(await edgelessNote.count()).toBe(1);
|
||||
});
|
||||
|
||||
test('should update panel when frames change', async ({ page }) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await toggleFramePanel(page);
|
||||
const frameCards = page.locator('affine-frame-card');
|
||||
expect(await frameCards.count()).toBe(0);
|
||||
|
||||
await addNote(page, 'hello', 150, 500);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(page, { x: 100, y: 440 }, { x: 600, y: 600 });
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(page, { x: 50, y: 300 }, { x: 120, y: 400 });
|
||||
await waitNextFrame(page);
|
||||
|
||||
const frames = page.locator('affine-frame');
|
||||
expect(await frames.count()).toBe(2);
|
||||
expect(await frameCards.count()).toBe(2);
|
||||
|
||||
await page.mouse.click(50, 300);
|
||||
await page.keyboard.press('Delete');
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(await frames.count()).toBe(1);
|
||||
expect(await frameCards.count()).toBe(1);
|
||||
});
|
||||
|
||||
test.describe('frame panel behavior after mode switch', () => {
|
||||
async function setupFrameTest(page: Page) {
|
||||
await edgelessCommonSetup(page);
|
||||
await toggleFramePanel(page);
|
||||
|
||||
await addNote(page, 'hello', 150, 500);
|
||||
await page.mouse.click(0, 0);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(
|
||||
page,
|
||||
{ x: 100, y: 440 },
|
||||
{ x: 640, y: 600 },
|
||||
{ steps: 10 }
|
||||
);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
const edgelessNote = page.locator(
|
||||
'affine-frame-card affine-edgeless-note'
|
||||
);
|
||||
expect(await edgelessNote.count()).toBe(1);
|
||||
|
||||
return edgelessNote;
|
||||
}
|
||||
|
||||
test('should render edgeless note correctly after mode switch', async ({
|
||||
page,
|
||||
}) => {
|
||||
const edgelessNote = await setupFrameTest(page);
|
||||
|
||||
const initialNoteRect = await edgelessNote.boundingBox();
|
||||
expect(initialNoteRect).not.toBeNull();
|
||||
|
||||
const {
|
||||
width: noteWidth,
|
||||
height: noteHeight,
|
||||
x: noteX,
|
||||
y: noteY,
|
||||
} = initialNoteRect!;
|
||||
|
||||
const checkNoteRect = async () => {
|
||||
expect(await edgelessNote.count()).toBe(1);
|
||||
|
||||
const newNoteRect = await edgelessNote.boundingBox();
|
||||
expect(newNoteRect).not.toBeNull();
|
||||
|
||||
expect(newNoteRect!.width).toBe(noteWidth);
|
||||
expect(newNoteRect!.height).toBe(noteHeight);
|
||||
expect(newNoteRect!.x).toBe(noteX);
|
||||
expect(newNoteRect!.y).toBe(noteY);
|
||||
};
|
||||
|
||||
await switchEditorMode(page);
|
||||
await checkNoteRect();
|
||||
|
||||
await switchEditorMode(page);
|
||||
await checkNoteRect();
|
||||
});
|
||||
|
||||
test('should update frame preview when note is moved', async ({ page }) => {
|
||||
const edgelessNote = await setupFrameTest(page);
|
||||
|
||||
const initialNoteRect = await edgelessNote.boundingBox();
|
||||
expect(initialNoteRect).not.toBeNull();
|
||||
|
||||
await switchEditorMode(page);
|
||||
await switchEditorMode(page);
|
||||
|
||||
async function moveNoteAndCheck(
|
||||
start: { x: number; y: number },
|
||||
end: { x: number; y: number },
|
||||
comparison: 'greaterThan' | 'lessThan'
|
||||
) {
|
||||
await page.mouse.move(start.x, start.y);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(end.x, end.y);
|
||||
await page.mouse.up();
|
||||
await waitNextFrame(page);
|
||||
|
||||
const newNoteRect = await edgelessNote.boundingBox();
|
||||
expect(newNoteRect).not.toBeNull();
|
||||
|
||||
if (comparison === 'greaterThan') {
|
||||
expect(newNoteRect!.x).toBeGreaterThan(initialNoteRect!.x);
|
||||
expect(newNoteRect!.y).toBeGreaterThan(initialNoteRect!.y);
|
||||
} else {
|
||||
expect(newNoteRect!.x).toBeLessThan(initialNoteRect!.x);
|
||||
expect(newNoteRect!.y).toBeLessThan(initialNoteRect!.y);
|
||||
}
|
||||
}
|
||||
|
||||
// Move the note to the right
|
||||
await moveNoteAndCheck(
|
||||
{ x: 150, y: 500 },
|
||||
{ x: 200, y: 550 },
|
||||
'greaterThan'
|
||||
);
|
||||
|
||||
// Move the note back to the left
|
||||
await moveNoteAndCheck(
|
||||
{ x: 200, y: 550 },
|
||||
{ x: 100, y: 450 },
|
||||
'lessThan'
|
||||
);
|
||||
|
||||
// Move the note diagonally
|
||||
await moveNoteAndCheck(
|
||||
{ x: 100, y: 450 },
|
||||
{ x: 250, y: 600 },
|
||||
'greaterThan'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('select and de-select frame', () => {
|
||||
async function setupFrameTest(page: Page) {
|
||||
await edgelessCommonSetup(page);
|
||||
await toggleFramePanel(page);
|
||||
|
||||
await addNote(page, 'hello', 150, 500);
|
||||
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(page, { x: 100, y: 440 }, { x: 640, y: 600 });
|
||||
await waitNextFrame(page);
|
||||
|
||||
const frames = page.locator('affine-frame');
|
||||
const frameCards = page.locator('affine-frame-card');
|
||||
expect(await frames.count()).toBe(1);
|
||||
expect(await frameCards.count()).toBe(1);
|
||||
|
||||
return { frames, frameCards };
|
||||
}
|
||||
|
||||
test('by click on frame card', async ({ page }) => {
|
||||
const { frameCards } = await setupFrameTest(page);
|
||||
|
||||
// click on the first frame card
|
||||
await frameCards.nth(0).click();
|
||||
await assertEdgelessSelectedRect(page, [100, 440, 540, 160]);
|
||||
|
||||
await frameCards.nth(0).click();
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
|
||||
test('by click on blank area', async ({ page }) => {
|
||||
const { frameCards } = await setupFrameTest(page);
|
||||
|
||||
// click on the first frame card
|
||||
await frameCards.nth(0).click();
|
||||
await assertEdgelessSelectedRect(page, [100, 440, 540, 160]);
|
||||
|
||||
const framePanel = page.locator('.frame-panel-container');
|
||||
const panelRect = await framePanel.boundingBox();
|
||||
expect(panelRect).not.toBeNull();
|
||||
const { x, y, width, height } = panelRect!;
|
||||
await page.mouse.click(x + width / 2, y + height / 2);
|
||||
await assertEdgelessNonSelectedRect(page);
|
||||
});
|
||||
});
|
||||
|
||||
test('should fit the viewport to the frame when double click frame card', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await toggleFramePanel(page);
|
||||
|
||||
await assertZoomLevel(page, 100);
|
||||
|
||||
await addNote(page, 'hello', 150, 500);
|
||||
await page.mouse.click(0, 0);
|
||||
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenCoords(page, { x: 100, y: 440 }, { x: 600, y: 600 });
|
||||
await waitNextFrame(page);
|
||||
|
||||
const frameCards = page.locator('affine-frame-card');
|
||||
await frameCards.nth(0).dblclick();
|
||||
|
||||
const zoomLevel = await getZoomLevel(page);
|
||||
expect(zoomLevel).toBeGreaterThan(100);
|
||||
});
|
||||
|
||||
test('should reorder frames when drag and drop frame card', async ({
|
||||
page,
|
||||
}) => {
|
||||
await edgelessCommonSetup(page);
|
||||
await createShapeElement(page, [100, 100], [200, 200], Shape.Square);
|
||||
await createNote(page, [300, 100], 'hello');
|
||||
|
||||
// Frame shape
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenViewCoords(page, [80, 80], [220, 220]);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
// Frame note
|
||||
await setEdgelessTool(page, 'frame');
|
||||
await dragBetweenViewCoords(page, [240, 0], [800, 200]);
|
||||
|
||||
expect(await page.locator('affine-frame').count()).toBe(2);
|
||||
|
||||
await toggleFramePanel(page);
|
||||
|
||||
const frameCards = page.locator('affine-frame-card');
|
||||
expect(await frameCards.count()).toBe(2);
|
||||
|
||||
// Drag the first frame card to the second
|
||||
await dragFrameCard(page, frameCards.nth(0), frameCards.nth(1));
|
||||
|
||||
await enterPresentationMode(page);
|
||||
await waitNextFrame(page, 100);
|
||||
|
||||
// Check if frame contains note now is the first
|
||||
const edgelessNote = page.locator(
|
||||
'affine-edgeless-root affine-edgeless-note'
|
||||
);
|
||||
await expect(edgelessNote).toBeVisible();
|
||||
});
|
||||
});
|
||||
361
blocksuite/tests-legacy/fragments/outline/outline-panel.spec.ts
Normal file
361
blocksuite/tests-legacy/fragments/outline/outline-panel.spec.ts
Normal file
@@ -0,0 +1,361 @@
|
||||
import { NoteDisplayMode } from '@blocksuite/affine-model';
|
||||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import {
|
||||
addNote,
|
||||
changeNoteDisplayModeWithId,
|
||||
switchEditorMode,
|
||||
triggerComponentToolbarAction,
|
||||
zoomResetByKeyboard,
|
||||
} from 'utils/actions/edgeless.js';
|
||||
import { pressBackspace, pressEnter, type } from 'utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichTextEnd,
|
||||
focusTitle,
|
||||
getEditorHostLocator,
|
||||
initEmptyEdgelessState,
|
||||
initEmptyParagraphState,
|
||||
waitNextFrame,
|
||||
} from 'utils/actions/misc.js';
|
||||
import { assertRichTexts } from 'utils/asserts.js';
|
||||
|
||||
import { test } from '../../utils/playwright.js';
|
||||
import {
|
||||
createHeadingsWithGap,
|
||||
getVerticalCenterFromLocator,
|
||||
} from './utils.js';
|
||||
|
||||
test.describe('toc-panel', () => {
|
||||
async function toggleTocPanel(page: Page) {
|
||||
await page.click('sl-button:text("Test Operations")');
|
||||
await page.click('sl-menu-item:text("Toggle Outline Panel")');
|
||||
await waitNextFrame(page);
|
||||
const panel = page.locator('affine-outline-panel');
|
||||
await expect(panel).toBeVisible();
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
function getHeading(panel: Locator, level: number) {
|
||||
return panel.locator(`affine-outline-panel-body .h${level} > span`);
|
||||
}
|
||||
|
||||
function getTitle(panel: Locator) {
|
||||
return panel.locator(`affine-outline-panel-body .title`);
|
||||
}
|
||||
|
||||
async function toggleNoteSorting(page: Page) {
|
||||
const enableSortingButton = page.locator(
|
||||
'.outline-panel-header-container .note-sorting-button'
|
||||
);
|
||||
await enableSortingButton.click();
|
||||
}
|
||||
|
||||
async function dragNoteCard(page: Page, fromCard: Locator, toCard: Locator) {
|
||||
const fromRect = await fromCard.boundingBox();
|
||||
const toRect = await toCard.boundingBox();
|
||||
|
||||
await page.mouse.move(fromRect!.x + 10, fromRect!.y + 10);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(toRect!.x + 5, toRect!.y + 5, { steps: 10 });
|
||||
await page.mouse.up();
|
||||
}
|
||||
|
||||
test('should display placeholder when no headings', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const panel = await toggleTocPanel(page);
|
||||
|
||||
const noHeadingPlaceholder = panel.locator('.note-placeholder');
|
||||
|
||||
await focusTitle(page);
|
||||
await type(page, 'Title');
|
||||
await focusRichTextEnd(page);
|
||||
await type(page, 'Hello World');
|
||||
|
||||
await expect(noHeadingPlaceholder).toBeVisible();
|
||||
});
|
||||
|
||||
test('should not display empty when there are only empty headings', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const panel = await toggleTocPanel(page);
|
||||
|
||||
await focusTitle(page);
|
||||
await type(page, 'Title');
|
||||
await focusRichTextEnd(page);
|
||||
|
||||
// heading 1 to 6
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
await type(page, `${'#'.repeat(i)} `);
|
||||
await pressEnter(page);
|
||||
await expect(getHeading(panel, i)).toBeHidden();
|
||||
}
|
||||
|
||||
// Title also should be hidden
|
||||
await expect(getTitle(panel)).toBeHidden();
|
||||
});
|
||||
|
||||
test('should display title and headings when there are non-empty headings in editor', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const panel = await toggleTocPanel(page);
|
||||
|
||||
await focusRichTextEnd(page);
|
||||
|
||||
// heading 1 to 6
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
await type(page, `${'#'.repeat(i)} `);
|
||||
await type(page, `Heading ${i}`);
|
||||
await pressEnter(page);
|
||||
|
||||
const heading = getHeading(panel, i);
|
||||
await expect(heading).toBeVisible();
|
||||
await expect(heading).toContainText(`Heading ${i}`);
|
||||
}
|
||||
|
||||
const title = getTitle(panel);
|
||||
await expect(title).toBeHidden();
|
||||
await focusTitle(page);
|
||||
await type(page, 'Title');
|
||||
await expect(title).toHaveText('Title');
|
||||
|
||||
// heading 1 to 6
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
const heading = getHeading(panel, i);
|
||||
await expect(heading).toBeVisible();
|
||||
await expect(heading).toContainText(`Heading ${i}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('should update headings', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const panel = await toggleTocPanel(page);
|
||||
|
||||
await focusRichTextEnd(page);
|
||||
|
||||
const h1 = getHeading(panel, 1);
|
||||
|
||||
await type(page, '# Heading 1');
|
||||
await expect(h1).toContainText('Heading 1');
|
||||
|
||||
await pressBackspace(page, 'Heading 1'.length);
|
||||
await expect(h1).toBeHidden();
|
||||
await type(page, 'Hello World');
|
||||
await expect(h1).toContainText('Hello World');
|
||||
|
||||
const title = getTitle(panel);
|
||||
|
||||
await focusTitle(page);
|
||||
await type(page, 'Title');
|
||||
await expect(title).toContainText('Title');
|
||||
|
||||
await pressBackspace(page, 2);
|
||||
await expect(title).toContainText('Tit');
|
||||
});
|
||||
|
||||
test('should add padding to sub-headings', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const panel = await toggleTocPanel(page);
|
||||
|
||||
await focusRichTextEnd(page);
|
||||
|
||||
await type(page, '# Heading 1');
|
||||
await pressEnter(page);
|
||||
|
||||
await type(page, '## Heading 2');
|
||||
await pressEnter(page);
|
||||
|
||||
const h1 = getHeading(panel, 1);
|
||||
const h2 = getHeading(panel, 2);
|
||||
|
||||
const h1Rect = await h1.boundingBox();
|
||||
const h2Rect = await h2.boundingBox();
|
||||
|
||||
expect(h1Rect).not.toBeNull();
|
||||
expect(h2Rect).not.toBeNull();
|
||||
|
||||
expect(h1Rect!.x).toBeLessThan(h2Rect!.x);
|
||||
});
|
||||
|
||||
test('should highlight heading when scroll to area before viewport center', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const editor = getEditorHostLocator(page);
|
||||
const panel = await toggleTocPanel(page);
|
||||
|
||||
await focusRichTextEnd(page);
|
||||
const headings = await createHeadingsWithGap(page);
|
||||
await editor.locator('.inline-editor').first().scrollIntoViewIfNeeded();
|
||||
|
||||
const viewportCenter = await getVerticalCenterFromLocator(
|
||||
page.locator('body')
|
||||
);
|
||||
|
||||
const activeHeadingContainer = panel.locator(
|
||||
'affine-outline-panel-body .active'
|
||||
);
|
||||
|
||||
for (let i = 0; i < headings.length; i++) {
|
||||
const lastHeadingCenter = await getVerticalCenterFromLocator(headings[i]);
|
||||
await page.mouse.wheel(0, lastHeadingCenter - viewportCenter + 50);
|
||||
await waitNextFrame(page);
|
||||
await expect(activeHeadingContainer).toContainText(`Heading ${i + 1}`);
|
||||
}
|
||||
});
|
||||
|
||||
test('should scroll to heading and highlight heading when click item in outline panel', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const panel = await toggleTocPanel(page);
|
||||
|
||||
await focusRichTextEnd(page);
|
||||
const headings = await createHeadingsWithGap(page);
|
||||
const activeHeadingContainer = panel.locator(
|
||||
'affine-outline-panel-body .active'
|
||||
);
|
||||
|
||||
const headingsInPanel = Array.from({ length: 6 }, (_, i) =>
|
||||
getHeading(panel, i + 1)
|
||||
);
|
||||
|
||||
await headingsInPanel[2].click();
|
||||
await expect(headings[2]).toBeVisible();
|
||||
await expect(activeHeadingContainer).toContainText('Heading 3');
|
||||
});
|
||||
|
||||
test('should scroll to title when click title in outline panel', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const panel = await toggleTocPanel(page);
|
||||
|
||||
await focusTitle(page);
|
||||
await type(page, 'Title');
|
||||
|
||||
await focusRichTextEnd(page);
|
||||
await createHeadingsWithGap(page);
|
||||
|
||||
const title = page.locator('doc-title');
|
||||
const titleInPanel = getTitle(panel);
|
||||
|
||||
await expect(title).not.toBeInViewport();
|
||||
await titleInPanel.click();
|
||||
await waitNextFrame(page, 50);
|
||||
await expect(title).toBeVisible();
|
||||
});
|
||||
|
||||
test('should update notes when change note display mode from note toolbar', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
const noteId = await addNote(page, 'hello', 300, 300);
|
||||
await page.mouse.click(100, 100);
|
||||
|
||||
await toggleTocPanel(page);
|
||||
await toggleNoteSorting(page);
|
||||
const docVisibleCard = page.locator(
|
||||
'.card-container[data-invisible="false"]'
|
||||
);
|
||||
const docInvisibleCard = page.locator(
|
||||
'.card-container[data-invisible="true"]'
|
||||
);
|
||||
|
||||
await expect(docVisibleCard).toHaveCount(1);
|
||||
await expect(docInvisibleCard).toHaveCount(1);
|
||||
|
||||
await changeNoteDisplayModeWithId(
|
||||
page,
|
||||
noteId,
|
||||
NoteDisplayMode.DocAndEdgeless
|
||||
);
|
||||
|
||||
await expect(docVisibleCard).toHaveCount(2);
|
||||
await expect(docInvisibleCard).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('should reorder notes when drag and drop note in outline panel', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
const note1 = await addNote(page, 'hello', 300, 300);
|
||||
const note2 = await addNote(page, 'world', 300, 500);
|
||||
await page.mouse.click(100, 100);
|
||||
|
||||
await changeNoteDisplayModeWithId(
|
||||
page,
|
||||
note1,
|
||||
NoteDisplayMode.DocAndEdgeless
|
||||
);
|
||||
await changeNoteDisplayModeWithId(
|
||||
page,
|
||||
note2,
|
||||
NoteDisplayMode.DocAndEdgeless
|
||||
);
|
||||
|
||||
await toggleTocPanel(page);
|
||||
await toggleNoteSorting(page);
|
||||
const docVisibleCard = page.locator(
|
||||
'.card-container[data-invisible="false"]'
|
||||
);
|
||||
|
||||
await expect(docVisibleCard).toHaveCount(3);
|
||||
await assertRichTexts(page, ['', 'hello', 'world']);
|
||||
|
||||
const noteCard3 = docVisibleCard.nth(2);
|
||||
const noteCard1 = docVisibleCard.nth(0);
|
||||
|
||||
await dragNoteCard(page, noteCard3, noteCard1);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['world', '', 'hello']);
|
||||
});
|
||||
|
||||
test('should update notes after slicing note', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await switchEditorMode(page);
|
||||
await zoomResetByKeyboard(page);
|
||||
const note1 = await addNote(page, 'hello', 100, 300);
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
await page.mouse.click(100, 100);
|
||||
|
||||
await changeNoteDisplayModeWithId(
|
||||
page,
|
||||
note1,
|
||||
NoteDisplayMode.DocAndEdgeless
|
||||
);
|
||||
|
||||
await toggleTocPanel(page);
|
||||
await toggleNoteSorting(page);
|
||||
const docVisibleCard = page.locator(
|
||||
'.card-container[data-invisible="false"]'
|
||||
);
|
||||
|
||||
await expect(docVisibleCard).toHaveCount(2);
|
||||
|
||||
await triggerComponentToolbarAction(page, 'changeNoteSlicerSetting');
|
||||
await expect(page.locator('.note-slicer-button')).toBeVisible();
|
||||
await page.locator('.note-slicer-button').click();
|
||||
|
||||
await expect(docVisibleCard).toHaveCount(3);
|
||||
});
|
||||
});
|
||||
208
blocksuite/tests-legacy/fragments/outline/toc-viewer.spec.ts
Normal file
208
blocksuite/tests-legacy/fragments/outline/toc-viewer.spec.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import { noop } from '@blocksuite/global/utils';
|
||||
import type { OutlineViewer } from '@blocksuite/presets';
|
||||
import { expect, type Page } from '@playwright/test';
|
||||
import { addNote, switchEditorMode } from 'utils/actions/edgeless.js';
|
||||
import { pressEnter, type } from 'utils/actions/keyboard.js';
|
||||
import {
|
||||
enterPlaygroundRoom,
|
||||
focusRichTextEnd,
|
||||
focusTitle,
|
||||
getEditorLocator,
|
||||
initEmptyEdgelessState,
|
||||
initEmptyParagraphState,
|
||||
waitNextFrame,
|
||||
} from 'utils/actions/misc.js';
|
||||
|
||||
import { test } from '../../utils/playwright.js';
|
||||
import {
|
||||
createHeadingsWithGap,
|
||||
getVerticalCenterFromLocator,
|
||||
} from './utils.js';
|
||||
|
||||
test.describe('toc-viewer', () => {
|
||||
async function toggleTocViewer(page: Page) {
|
||||
await page.click('sl-button:text("Test Operations")');
|
||||
await page.click('sl-menu-item:text("Enable Outline Viewer")');
|
||||
await waitNextFrame(page);
|
||||
const viewer = page.locator('affine-outline-viewer');
|
||||
return viewer;
|
||||
}
|
||||
|
||||
function getIndicators(page: Page) {
|
||||
return page.locator('affine-outline-viewer .outline-viewer-indicator');
|
||||
}
|
||||
|
||||
test('should display highlight indicators when non-empty headings exists', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await toggleTocViewer(page);
|
||||
|
||||
await focusRichTextEnd(page);
|
||||
|
||||
const indicators = getIndicators(page);
|
||||
|
||||
// heading 1 to 6
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
await type(page, `${'#'.repeat(i)} `);
|
||||
await type(page, `Heading ${i}`);
|
||||
await pressEnter(page);
|
||||
|
||||
await expect(indicators.nth(i - 1)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('should be hidden when only empty headings exists', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await toggleTocViewer(page);
|
||||
|
||||
await focusTitle(page);
|
||||
await type(page, 'Title');
|
||||
await focusRichTextEnd(page);
|
||||
|
||||
const indicators = getIndicators(page);
|
||||
|
||||
// heading 1 to 6
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
await type(page, `${'#'.repeat(i)} `);
|
||||
await pressEnter(page);
|
||||
await expect(indicators).toHaveCount(0);
|
||||
}
|
||||
});
|
||||
|
||||
test('should display outline content when hovering over indicators', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await toggleTocViewer(page);
|
||||
|
||||
await focusRichTextEnd(page);
|
||||
|
||||
await type(page, '# Heading 1');
|
||||
await pressEnter(page);
|
||||
|
||||
const indicator = getIndicators(page).first();
|
||||
await indicator.hover({ force: true });
|
||||
|
||||
const items = page.locator('.outline-viewer-item');
|
||||
await expect(items).toHaveCount(2);
|
||||
await expect(items.nth(0)).toContainText(['Table of Contents']);
|
||||
await expect(items.nth(1)).toContainText(['Heading 1']);
|
||||
});
|
||||
|
||||
test('should highlight indicator when scrolling', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await toggleTocViewer(page);
|
||||
await focusRichTextEnd(page);
|
||||
|
||||
const editor = getEditorLocator(page);
|
||||
const indicators = getIndicators(page);
|
||||
const headings = await createHeadingsWithGap(page);
|
||||
await editor.locator('.inline-editor').first().scrollIntoViewIfNeeded();
|
||||
|
||||
const viewportCenter = await getVerticalCenterFromLocator(
|
||||
page.locator('body')
|
||||
);
|
||||
for (let i = 0; i < headings.length; i++) {
|
||||
const lastHeadingCenter = await getVerticalCenterFromLocator(headings[i]);
|
||||
await page.mouse.wheel(0, lastHeadingCenter - viewportCenter + 50);
|
||||
await expect(indicators.nth(i)).toHaveClass(/active/);
|
||||
}
|
||||
});
|
||||
|
||||
test('should highlight indicator when click item in outline panel', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const viewer = await toggleTocViewer(page);
|
||||
|
||||
await focusRichTextEnd(page);
|
||||
const headings = await createHeadingsWithGap(page);
|
||||
|
||||
const indicators = getIndicators(page);
|
||||
await indicators.first().hover({ force: true });
|
||||
|
||||
const headingsInPanel = Array.from({ length: 6 }, (_, i) =>
|
||||
viewer.locator(`.h${i + 1} > span`)
|
||||
);
|
||||
|
||||
await headingsInPanel[2].click();
|
||||
await expect(headings[2]).toBeVisible();
|
||||
await expect(indicators.nth(2)).toHaveClass(/active/);
|
||||
});
|
||||
|
||||
test('should hide in edgeless mode', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
await toggleTocViewer(page);
|
||||
|
||||
const indicators = getIndicators(page);
|
||||
|
||||
await focusRichTextEnd(page);
|
||||
await type(page, '# Heading 1');
|
||||
await pressEnter(page);
|
||||
|
||||
await expect(indicators).toHaveCount(1);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
await expect(indicators).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('should hide edgeless-only note headings', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyEdgelessState(page);
|
||||
const viewer = await toggleTocViewer(page);
|
||||
|
||||
await focusRichTextEnd(page);
|
||||
|
||||
await type(page, '# Heading 1');
|
||||
await pressEnter(page);
|
||||
|
||||
await type(page, '## Heading 2');
|
||||
await pressEnter(page);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
await addNote(page, '# Edgeless', 300, 300);
|
||||
|
||||
await switchEditorMode(page);
|
||||
|
||||
const indicators = getIndicators(page);
|
||||
await expect(indicators).toHaveCount(2);
|
||||
|
||||
await indicators.first().hover({ force: true });
|
||||
|
||||
await expect(viewer).toBeVisible();
|
||||
const hiddenTitle = viewer.locator('.hidden-title');
|
||||
await expect(hiddenTitle).toBeHidden();
|
||||
});
|
||||
|
||||
test('outline panel toggle button', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
const viewer = await toggleTocViewer(page);
|
||||
|
||||
await focusRichTextEnd(page);
|
||||
await createHeadingsWithGap(page);
|
||||
|
||||
const toggleButton = viewer.locator(
|
||||
'[data-testid="toggle-outline-panel-button"]'
|
||||
);
|
||||
await expect(toggleButton).toHaveCount(0);
|
||||
await viewer.evaluate((el: OutlineViewer) => {
|
||||
el.toggleOutlinePanel = () => {
|
||||
noop();
|
||||
};
|
||||
});
|
||||
|
||||
await waitNextFrame(page);
|
||||
await expect(toggleButton).toHaveCount(1);
|
||||
await expect(toggleButton).toBeVisible();
|
||||
});
|
||||
});
|
||||
27
blocksuite/tests-legacy/fragments/outline/utils.ts
Normal file
27
blocksuite/tests-legacy/fragments/outline/utils.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { pressEnter, type } from 'utils/actions/keyboard.js';
|
||||
import { getEditorHostLocator } from 'utils/actions/misc.js';
|
||||
|
||||
export async function getVerticalCenterFromLocator(locator: Locator) {
|
||||
const rect = await locator.boundingBox();
|
||||
return rect!.y + rect!.height / 2;
|
||||
}
|
||||
|
||||
export async function createHeadingsWithGap(page: Page) {
|
||||
// heading 1 to 6
|
||||
const editor = getEditorHostLocator(page);
|
||||
|
||||
const headings: Locator[] = [];
|
||||
await pressEnter(page, 10);
|
||||
for (let i = 1; i <= 6; i++) {
|
||||
await type(page, `${'#'.repeat(i)} `);
|
||||
await type(page, `Heading ${i}`);
|
||||
const heading = editor.locator(`.h${i}`);
|
||||
await expect(heading).toBeVisible();
|
||||
headings.push(heading);
|
||||
await pressEnter(page, 10);
|
||||
}
|
||||
await pressEnter(page, 10);
|
||||
|
||||
return headings;
|
||||
}
|
||||
93
blocksuite/tests-legacy/hotkey/bracket.spec.ts
Normal file
93
blocksuite/tests-legacy/hotkey/bracket.spec.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
dragBetweenIndices,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getPageSnapshot,
|
||||
initEmptyCodeBlockState,
|
||||
initEmptyParagraphState,
|
||||
initThreeParagraphs,
|
||||
resetHistory,
|
||||
type,
|
||||
undoByClick,
|
||||
} from '../utils/actions/index.js';
|
||||
import { assertRichTexts } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('should bracket complete works', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '([{');
|
||||
// type without selection should not trigger bracket complete
|
||||
await assertRichTexts(page, ['([{']);
|
||||
|
||||
await dragBetweenIndices(page, [0, 1], [0, 2]);
|
||||
await type(page, '(');
|
||||
await assertRichTexts(page, ['(([){']);
|
||||
|
||||
await type(page, ')');
|
||||
// Should not trigger bracket complete when type right bracket
|
||||
await assertRichTexts(page, ['(()){']);
|
||||
});
|
||||
|
||||
test('bracket complete should not work when selecting mutiple lines', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
|
||||
// 1(23 45)6 789
|
||||
await dragBetweenIndices(page, [0, 1], [1, 2]);
|
||||
await type(page, '(');
|
||||
await assertRichTexts(page, ['1(6', '789']);
|
||||
});
|
||||
|
||||
test('should bracket complete with backtick works', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello world');
|
||||
|
||||
await dragBetweenIndices(page, [0, 2], [0, 5]);
|
||||
await resetHistory(page);
|
||||
await type(page, '`');
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
|
||||
await undoByClick(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_undo.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('auto delete bracket right', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '(');
|
||||
await assertRichTexts(page, ['()']);
|
||||
await type(page, '(');
|
||||
await assertRichTexts(page, ['(())']);
|
||||
await page.keyboard.press('Backspace');
|
||||
await assertRichTexts(page, ['()']);
|
||||
await page.keyboard.press('Backspace');
|
||||
await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test('skip redundant right bracket', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyCodeBlockState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, '(');
|
||||
await assertRichTexts(page, ['()']);
|
||||
await type(page, ')');
|
||||
await assertRichTexts(page, ['()']);
|
||||
await type(page, ')');
|
||||
await assertRichTexts(page, ['())']);
|
||||
});
|
||||
474
blocksuite/tests-legacy/hotkey/hotkey.spec.ts
Normal file
474
blocksuite/tests-legacy/hotkey/hotkey.spec.ts
Normal file
@@ -0,0 +1,474 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
dragBetweenIndices,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getPageSnapshot,
|
||||
initEmptyParagraphState,
|
||||
initThreeParagraphs,
|
||||
inlineCode,
|
||||
MODIFIER_KEY,
|
||||
pressArrowDown,
|
||||
pressArrowLeft,
|
||||
pressArrowRight,
|
||||
pressArrowUp,
|
||||
pressEnter,
|
||||
pressForwardDelete,
|
||||
pressShiftTab,
|
||||
pressTab,
|
||||
readClipboardText,
|
||||
redoByClick,
|
||||
redoByKeyboard,
|
||||
resetHistory,
|
||||
setInlineRangeInSelectedRichText,
|
||||
SHIFT_KEY,
|
||||
SHORT_KEY,
|
||||
strikethrough,
|
||||
type,
|
||||
undoByClick,
|
||||
undoByKeyboard,
|
||||
updateBlockType,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockChildrenIds,
|
||||
assertRichTextInlineRange,
|
||||
assertRichTextModelType,
|
||||
assertRichTexts,
|
||||
assertTextFormat,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('rich-text hotkey scope on single press', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
await assertRichTexts(page, ['hello', 'world']);
|
||||
|
||||
await dragBetweenIndices(page, [0, 0], [1, 5]);
|
||||
await page.keyboard.press('Backspace');
|
||||
await assertRichTexts(page, ['']);
|
||||
});
|
||||
|
||||
test('single line rich-text inline code hotkey', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await dragBetweenIndices(page, [0, 0], [0, 5]);
|
||||
await inlineCode(page);
|
||||
await assertTextFormat(page, 0, 5, { code: true });
|
||||
|
||||
// undo
|
||||
await undoByKeyboard(page);
|
||||
await assertTextFormat(page, 0, 5, {});
|
||||
// redo
|
||||
await redoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await assertTextFormat(page, 0, 5, { code: true });
|
||||
|
||||
// the format should be removed after trigger the hotkey again
|
||||
await inlineCode(page);
|
||||
await assertTextFormat(page, 0, 5, {});
|
||||
});
|
||||
|
||||
test('type character jump out code node', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'Hello');
|
||||
await setInlineRangeInSelectedRichText(page, 0, 5);
|
||||
await inlineCode(page);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_1.json`
|
||||
);
|
||||
await focusRichText(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+ArrowRight`);
|
||||
await type(page, 'block suite');
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_2.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('single line rich-text strikethrough hotkey', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await dragBetweenIndices(page, [0, 0], [0, 5]);
|
||||
await strikethrough(page);
|
||||
await assertTextFormat(page, 0, 5, { strike: true });
|
||||
|
||||
await undoByClick(page);
|
||||
await assertTextFormat(page, 0, 5, {});
|
||||
|
||||
await redoByClick(page);
|
||||
await assertTextFormat(page, 0, 5, { strike: true });
|
||||
|
||||
await waitNextFrame(page);
|
||||
// the format should be removed after trigger the hotkey again
|
||||
await strikethrough(page);
|
||||
await assertTextFormat(page, 0, 5, {});
|
||||
});
|
||||
|
||||
test('use formatted cursor with hotkey', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'aaa');
|
||||
// format italic
|
||||
await page.keyboard.press(`${SHORT_KEY}+i`, { delay: 50 });
|
||||
await type(page, 'bbb');
|
||||
// format bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 });
|
||||
await type(page, 'ccc');
|
||||
// unformat italic
|
||||
await page.keyboard.press(`${SHORT_KEY}+i`, { delay: 50 });
|
||||
await type(page, 'ddd');
|
||||
// unformat bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 });
|
||||
await type(page, 'eee');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
// format bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 });
|
||||
await type(page, 'fff');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_bold.json`
|
||||
);
|
||||
|
||||
await pressArrowLeft(page);
|
||||
await pressArrowRight(page);
|
||||
await type(page, 'ggg');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_bold_ggg.json`
|
||||
);
|
||||
|
||||
await setInlineRangeInSelectedRichText(page, 3, 0);
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'hhh');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_bold_hhh.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('use formatted cursor with hotkey at empty line', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
// format bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 });
|
||||
await type(page, 'aaa');
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_bold.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('should single line format hotkey work', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await dragBetweenIndices(page, [0, 1], [0, 4]);
|
||||
|
||||
// bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 });
|
||||
// italic
|
||||
await page.keyboard.press(`${SHORT_KEY}+i`, { delay: 50 });
|
||||
// underline
|
||||
await page.keyboard.press(`${SHORT_KEY}+u`, { delay: 50 });
|
||||
// strikethrough
|
||||
await page.keyboard.press(`${SHORT_KEY}+Shift+s`, { delay: 50 });
|
||||
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
// bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 });
|
||||
// italic
|
||||
await page.keyboard.press(`${SHORT_KEY}+i`, { delay: 50 });
|
||||
// underline
|
||||
await page.keyboard.press(`${SHORT_KEY}+u`, { delay: 50 });
|
||||
// strikethrough
|
||||
await page.keyboard.press(`${SHORT_KEY}+Shift+s`, { delay: 50 });
|
||||
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_finial.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('should hotkey work in paragraph', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page, 0);
|
||||
await type(page, 'hello');
|
||||
|
||||
// XXX wait for group to be updated
|
||||
await page.waitForTimeout(10);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+1`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+6`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_press_6.json`
|
||||
);
|
||||
await page.waitForTimeout(50);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+8`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_press_8.json`
|
||||
);
|
||||
await page.waitForTimeout(50);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+9`);
|
||||
await waitNextFrame(page, 200);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_press_9.json`
|
||||
);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+0`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_press_0.json`
|
||||
);
|
||||
await page.waitForTimeout(50);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+d`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_press_d.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('format list to h1', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page, 0);
|
||||
await updateBlockType(page, 'affine:list', 'bulleted');
|
||||
await type(page, 'aa');
|
||||
await focusRichText(page, 0);
|
||||
await updateBlockType(page, 'affine:paragraph', 'h1');
|
||||
await assertRichTextModelType(page, 'h1');
|
||||
await undoByClick(page);
|
||||
await assertRichTextModelType(page, 'bulleted');
|
||||
await redoByClick(page);
|
||||
await assertRichTextModelType(page, 'h1');
|
||||
});
|
||||
|
||||
test('should cut work single line', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await resetHistory(page);
|
||||
await dragBetweenIndices(page, [0, 1], [0, 4]);
|
||||
// cut
|
||||
await page.keyboard.press(`${SHORT_KEY}+x`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
await undoByKeyboard(page);
|
||||
const text = await readClipboardText(page);
|
||||
expect(text).toBe('ell');
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_undo.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('should ctrl+enter create new block', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '123');
|
||||
await pressArrowLeft(page, 2);
|
||||
await pressEnter(page);
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['1', '23']);
|
||||
await page.keyboard.press(`${SHORT_KEY}+Enter`);
|
||||
await assertRichTexts(page, ['1', '23', '']);
|
||||
});
|
||||
|
||||
test('should left/right key navigator works', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await focusRichText(page, 0);
|
||||
await assertRichTextInlineRange(page, 0, 3);
|
||||
await page.keyboard.press(`${SHORT_KEY}+ArrowLeft`, { delay: 50 });
|
||||
await assertRichTextInlineRange(page, 0, 0);
|
||||
await pressArrowLeft(page);
|
||||
await assertRichTextInlineRange(page, 0, 0);
|
||||
await page.keyboard.press(`${SHORT_KEY}+ArrowRight`, { delay: 50 });
|
||||
await assertRichTextInlineRange(page, 0, 3);
|
||||
await pressArrowRight(page);
|
||||
await assertRichTextInlineRange(page, 1, 0);
|
||||
await pressArrowLeft(page);
|
||||
await assertRichTextInlineRange(page, 0, 3);
|
||||
await pressArrowRight(page, 4);
|
||||
await assertRichTextInlineRange(page, 1, 3);
|
||||
await pressArrowRight(page);
|
||||
await assertRichTextInlineRange(page, 2, 0);
|
||||
await pressArrowLeft(page);
|
||||
await assertRichTextInlineRange(page, 1, 3);
|
||||
});
|
||||
|
||||
test('should up/down key navigator works', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await focusRichText(page, 0);
|
||||
await assertRichTextInlineRange(page, 0, 3);
|
||||
await pressArrowDown(page);
|
||||
await assertRichTextInlineRange(page, 1, 3);
|
||||
await pressArrowDown(page);
|
||||
await assertRichTextInlineRange(page, 2, 3);
|
||||
await page.keyboard.press(`${SHORT_KEY}+ArrowLeft`, { delay: 50 });
|
||||
await assertRichTextInlineRange(page, 2, 0);
|
||||
await pressArrowUp(page);
|
||||
await assertRichTextInlineRange(page, 1, 0);
|
||||
await pressArrowRight(page);
|
||||
await pressArrowUp(page);
|
||||
await assertRichTextInlineRange(page, 0, 1);
|
||||
await pressArrowDown(page);
|
||||
await assertRichTextInlineRange(page, 1, 1);
|
||||
});
|
||||
|
||||
test('should support ctrl/cmd+shift+l convert to linked doc', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
|
||||
await dragBetweenIndices(
|
||||
page,
|
||||
[2, 3],
|
||||
[0, 0],
|
||||
{ x: 20, y: 20 },
|
||||
{ x: 0, y: 0 }
|
||||
);
|
||||
|
||||
await waitNextFrame(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${SHIFT_KEY}+l`);
|
||||
|
||||
const linkedDocCard = page.locator('affine-embed-linked-doc-block');
|
||||
await expect(linkedDocCard).toBeVisible();
|
||||
|
||||
const title = page.locator('.affine-embed-linked-doc-content-title-text');
|
||||
expect(await title.innerText()).toBe('Untitled');
|
||||
|
||||
const noteContent = page.locator('.affine-embed-linked-doc-content-note');
|
||||
expect(await noteContent.innerText()).toBe('123');
|
||||
});
|
||||
|
||||
test('should forwardDelete works when delete single character', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page, 0);
|
||||
await type(page, 'hello');
|
||||
await pressArrowLeft(page, 5);
|
||||
await pressForwardDelete(page);
|
||||
await assertRichTexts(page, ['ello']);
|
||||
});
|
||||
|
||||
test.describe('keyboard operation to move block up or down', () => {
|
||||
test('common paragraph', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
await pressEnter(page);
|
||||
await type(page, 'foo');
|
||||
await pressEnter(page);
|
||||
await type(page, 'bar');
|
||||
await assertRichTexts(page, ['hello', 'world', 'foo', 'bar']);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowUp`);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowUp`);
|
||||
await assertRichTexts(page, ['hello', 'bar', 'world', 'foo']);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowDown`);
|
||||
await assertRichTexts(page, ['hello', 'world', 'bar', 'foo']);
|
||||
});
|
||||
|
||||
test('with indent', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await pressTab(page);
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'world');
|
||||
await pressEnter(page);
|
||||
await pressShiftTab(page);
|
||||
await waitNextFrame(page);
|
||||
await type(page, 'foo');
|
||||
await assertRichTexts(page, ['hello', 'world', 'foo']);
|
||||
await assertBlockChildrenIds(page, '2', ['3']);
|
||||
await pressArrowUp(page, 2);
|
||||
await waitNextFrame(page);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowDown`);
|
||||
await waitNextFrame(page);
|
||||
await assertRichTexts(page, ['foo', 'hello', 'world']);
|
||||
await assertBlockChildrenIds(page, '1', ['4', '2']);
|
||||
await assertBlockChildrenIds(page, '2', ['3']);
|
||||
});
|
||||
|
||||
test('keep cursor', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
await pressEnter(page);
|
||||
await type(page, 'foo');
|
||||
await assertRichTexts(page, ['hello', 'world', 'foo']);
|
||||
await assertRichTextInlineRange(page, 2, 3);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowUp`);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowUp`);
|
||||
await assertRichTextInlineRange(page, 0, 3);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowDown`);
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+ArrowDown`);
|
||||
await assertRichTextInlineRange(page, 2, 3);
|
||||
});
|
||||
});
|
||||
|
||||
test('Enter key should as expected after setting heading by shortkey', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/4987',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
await type(page, 'hello');
|
||||
await page.keyboard.press(`${SHORT_KEY}+${MODIFIER_KEY}+1`);
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}.json`
|
||||
);
|
||||
});
|
||||
176
blocksuite/tests-legacy/hotkey/multiline.spec.ts
Normal file
176
blocksuite/tests-legacy/hotkey/multiline.spec.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
dragBetweenIndices,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
getPageSnapshot,
|
||||
initEmptyParagraphState,
|
||||
initThreeParagraphs,
|
||||
inlineCode,
|
||||
pressArrowLeft,
|
||||
pressArrowUp,
|
||||
pressEnter,
|
||||
pressForwardDelete,
|
||||
pressShiftEnter,
|
||||
readClipboardText,
|
||||
redoByClick,
|
||||
resetHistory,
|
||||
setInlineRangeInSelectedRichText,
|
||||
SHORT_KEY,
|
||||
type,
|
||||
undoByClick,
|
||||
undoByKeyboard,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockSelections,
|
||||
assertRichTextInlineRange,
|
||||
assertRichTexts,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('should multiple line format hotkey work', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
// 0 1 2
|
||||
// 1|23 456 78|9
|
||||
await dragBetweenIndices(page, [0, 1], [2, 2]);
|
||||
|
||||
// bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`);
|
||||
// italic
|
||||
await page.keyboard.press(`${SHORT_KEY}+i`);
|
||||
// underline
|
||||
await page.keyboard.press(`${SHORT_KEY}+u`);
|
||||
// strikethrough
|
||||
await page.keyboard.press(`${SHORT_KEY}+Shift+S`);
|
||||
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
// bold
|
||||
await page.keyboard.press(`${SHORT_KEY}+b`, { delay: 50 });
|
||||
// italic
|
||||
await page.keyboard.press(`${SHORT_KEY}+i`, { delay: 50 });
|
||||
// underline
|
||||
await page.keyboard.press(`${SHORT_KEY}+u`, { delay: 50 });
|
||||
// strikethrough
|
||||
await page.keyboard.press(`${SHORT_KEY}+Shift+s`, { delay: 50 });
|
||||
|
||||
await waitNextFrame(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_finial.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('multi line rich-text inline code hotkey', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
|
||||
// 0 1 2
|
||||
// 1|23 456 78|9
|
||||
await dragBetweenIndices(page, [0, 1], [2, 2]);
|
||||
await inlineCode(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
|
||||
await undoByClick(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_undo.json`
|
||||
);
|
||||
|
||||
await redoByClick(page);
|
||||
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_redo.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('should cut work multiple line', async ({ page }, testInfo) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await resetHistory(page);
|
||||
// 0 1 2
|
||||
// 1|23 456 78|9
|
||||
await dragBetweenIndices(page, [0, 1], [2, 2]);
|
||||
// cut
|
||||
await page.keyboard.press(`${SHORT_KEY}+x`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_init.json`
|
||||
);
|
||||
await undoByKeyboard(page);
|
||||
const text = await readClipboardText(page);
|
||||
expect(text).toBe(`23 456 78`);
|
||||
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
||||
`${testInfo.title}_undo.json`
|
||||
);
|
||||
});
|
||||
|
||||
test('arrow up and down behavior on multiline text blocks when previous is non-text', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page);
|
||||
|
||||
await pressEnter(page);
|
||||
await pressArrowUp(page);
|
||||
await type(page, '--- ');
|
||||
await pressEnter(page);
|
||||
|
||||
await focusRichText(page);
|
||||
await type(page, '124');
|
||||
await pressShiftEnter(page);
|
||||
await type(page, '1234');
|
||||
|
||||
await pressArrowUp(page);
|
||||
await waitNextFrame(page, 100);
|
||||
await assertRichTextInlineRange(page, 0, 3);
|
||||
|
||||
await pressArrowUp(page);
|
||||
await assertBlockSelections(page, ['4']);
|
||||
});
|
||||
|
||||
test('should forwardDelete works when delete multi characters', async ({
|
||||
page,
|
||||
}) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/3122',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusRichText(page, 0);
|
||||
await type(page, 'hello');
|
||||
await pressArrowLeft(page, 5);
|
||||
await setInlineRangeInSelectedRichText(page, 1, 3);
|
||||
await pressForwardDelete(page);
|
||||
await assertRichTexts(page, ['ho']);
|
||||
});
|
||||
|
||||
test('should drag multiple block and input text works', async ({ page }) => {
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2982',
|
||||
});
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await initThreeParagraphs(page);
|
||||
await dragBetweenIndices(page, [0, 1], [2, 1]);
|
||||
await type(page, 'ab');
|
||||
await assertRichTexts(page, ['1ab89']);
|
||||
await undoByKeyboard(page);
|
||||
await assertRichTexts(page, ['123', '456', '789']);
|
||||
});
|
||||
43
blocksuite/tests-legacy/hotkey/title.spec.ts
Normal file
43
blocksuite/tests-legacy/hotkey/title.spec.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
cutByKeyboard,
|
||||
dragOverTitle,
|
||||
enterPlaygroundRoom,
|
||||
focusRichText,
|
||||
focusTitle,
|
||||
initEmptyParagraphState,
|
||||
pasteByKeyboard,
|
||||
pressEnter,
|
||||
type,
|
||||
} from '../utils/actions/index.js';
|
||||
import { assertRichTexts, assertTitle } from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test('should cut in title works', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
|
||||
await focusTitle(page);
|
||||
await type(page, 'hello');
|
||||
await assertTitle(page, 'hello');
|
||||
|
||||
await dragOverTitle(page);
|
||||
await cutByKeyboard(page);
|
||||
await assertTitle(page, '');
|
||||
|
||||
await focusRichText(page);
|
||||
await pasteByKeyboard(page);
|
||||
await assertRichTexts(page, ['hello']);
|
||||
});
|
||||
|
||||
test('enter in title should move cursor in new paragraph block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initEmptyParagraphState(page);
|
||||
await focusTitle(page);
|
||||
await type(page, 'hello');
|
||||
await assertTitle(page, 'hello');
|
||||
await pressEnter(page);
|
||||
await type(page, 'world');
|
||||
await assertRichTexts(page, ['world', '']);
|
||||
});
|
||||
154
blocksuite/tests-legacy/image/image.spec.ts
Normal file
154
blocksuite/tests-legacy/image/image.spec.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import '../utils/declare-test-window.js';
|
||||
|
||||
import type { Page } from '@playwright/test';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
activeEmbed,
|
||||
copyByKeyboard,
|
||||
dragEmbedResizeByTopLeft,
|
||||
dragEmbedResizeByTopRight,
|
||||
enterPlaygroundRoom,
|
||||
initImageState,
|
||||
moveToImage,
|
||||
pasteByKeyboard,
|
||||
pressArrowLeft,
|
||||
pressEnter,
|
||||
redoByClick,
|
||||
redoByKeyboard,
|
||||
type,
|
||||
undoByKeyboard,
|
||||
waitNextFrame,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertImageOption,
|
||||
assertImageSize,
|
||||
assertRichDragButton,
|
||||
assertRichImage,
|
||||
assertRichTextInlineRange,
|
||||
assertRichTexts,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
async function focusCaption(page: Page) {
|
||||
await page.click(
|
||||
'.affine-image-toolbar-container .image-toolbar-button.caption'
|
||||
);
|
||||
}
|
||||
|
||||
test('can drag resize image by left menu', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page);
|
||||
await assertRichImage(page, 1);
|
||||
|
||||
await activeEmbed(page);
|
||||
await assertRichDragButton(page);
|
||||
await assertImageSize(page, { width: 752, height: 564 });
|
||||
|
||||
await dragEmbedResizeByTopLeft(page);
|
||||
await waitNextFrame(page);
|
||||
await assertImageSize(page, { width: 358, height: 268 });
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await assertImageSize(page, { width: 752, height: 564 });
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await waitNextFrame(page);
|
||||
await assertImageSize(page, { width: 358, height: 268 });
|
||||
});
|
||||
|
||||
test('can drag resize image by right menu', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page);
|
||||
await assertRichImage(page, 1);
|
||||
|
||||
await activeEmbed(page);
|
||||
await assertRichDragButton(page);
|
||||
await assertImageSize(page, { width: 752, height: 564 });
|
||||
|
||||
await dragEmbedResizeByTopRight(page);
|
||||
await assertImageSize(page, { width: 338, height: 253 });
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertImageSize(page, { width: 752, height: 564 });
|
||||
|
||||
await redoByKeyboard(page);
|
||||
await assertImageSize(page, { width: 338, height: 253 });
|
||||
});
|
||||
|
||||
test('can click and delete image', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page);
|
||||
await assertRichImage(page, 1);
|
||||
|
||||
await activeEmbed(page);
|
||||
await page.keyboard.press('Backspace');
|
||||
await assertRichImage(page, 0);
|
||||
|
||||
await undoByKeyboard(page);
|
||||
await assertRichImage(page, 1);
|
||||
|
||||
await redoByClick(page);
|
||||
await assertRichImage(page, 0);
|
||||
});
|
||||
|
||||
test('can click and copy image', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page);
|
||||
await assertRichImage(page, 1);
|
||||
|
||||
await activeEmbed(page);
|
||||
await copyByKeyboard(page);
|
||||
await pressEnter(page);
|
||||
await waitNextFrame(page);
|
||||
|
||||
await pasteByKeyboard(page);
|
||||
await waitNextFrame(page, 200);
|
||||
await assertRichImage(page, 2);
|
||||
});
|
||||
|
||||
test('enter shortcut on focusing embed block and its caption', async ({
|
||||
page,
|
||||
}) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page);
|
||||
await assertRichImage(page, 1);
|
||||
|
||||
await moveToImage(page);
|
||||
await assertImageOption(page);
|
||||
|
||||
const caption = page.locator('affine-image block-caption-editor textarea');
|
||||
await focusCaption(page);
|
||||
await type(page, '123');
|
||||
|
||||
test.info().annotations.push({
|
||||
type: 'issue',
|
||||
description: 'https://github.com/toeverything/blocksuite/issues/2495',
|
||||
});
|
||||
|
||||
// blur
|
||||
await page.mouse.click(0, 500);
|
||||
await caption.click({ position: { x: 0, y: 0 } });
|
||||
await type(page, 'abc');
|
||||
await expect(caption).toHaveValue('abc123');
|
||||
});
|
||||
|
||||
test('should support the enter key of image caption', async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page);
|
||||
await assertRichImage(page, 1);
|
||||
|
||||
await moveToImage(page);
|
||||
await assertImageOption(page);
|
||||
|
||||
const caption = page.locator('affine-image block-caption-editor textarea');
|
||||
await focusCaption(page);
|
||||
await type(page, 'abc123');
|
||||
await pressArrowLeft(page, 3);
|
||||
await pressEnter(page);
|
||||
await expect(caption).toHaveValue('abc');
|
||||
|
||||
await assertRichTexts(page, ['123']);
|
||||
await assertRichTextInlineRange(page, 0, 0, 0);
|
||||
});
|
||||
79
blocksuite/tests-legacy/image/keymap.spec.ts
Normal file
79
blocksuite/tests-legacy/image/keymap.spec.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
activeEmbed,
|
||||
enterPlaygroundRoom,
|
||||
initImageState,
|
||||
pressArrowDown,
|
||||
pressArrowUp,
|
||||
pressBackspace,
|
||||
pressEnter,
|
||||
type,
|
||||
} from '../utils/actions/index.js';
|
||||
import {
|
||||
assertBlockCount,
|
||||
assertBlockSelections,
|
||||
assertRichImage,
|
||||
assertRichTextInlineRange,
|
||||
assertRichTexts,
|
||||
} from '../utils/asserts.js';
|
||||
import { test } from '../utils/playwright.js';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await enterPlaygroundRoom(page);
|
||||
await initImageState(page, true);
|
||||
await assertRichImage(page, 1);
|
||||
});
|
||||
|
||||
test('press enter will create new block when click and select image', async ({
|
||||
page,
|
||||
}) => {
|
||||
await activeEmbed(page);
|
||||
await pressEnter(page);
|
||||
await type(page, 'aa');
|
||||
await assertRichTexts(page, ['', 'aa']);
|
||||
});
|
||||
|
||||
test('press backspace after image block can select image block', async ({
|
||||
page,
|
||||
}) => {
|
||||
await activeEmbed(page);
|
||||
await pressEnter(page);
|
||||
await assertRichTextInlineRange(page, 1, 0);
|
||||
await assertBlockCount(page, 'paragraph', 2);
|
||||
await pressBackspace(page);
|
||||
await assertBlockSelections(page, ['3']);
|
||||
await assertBlockCount(page, 'paragraph', 1);
|
||||
});
|
||||
|
||||
test('press enter when image is selected should move next paragraph and should placeholder', async ({
|
||||
page,
|
||||
}) => {
|
||||
await activeEmbed(page);
|
||||
await pressEnter(page);
|
||||
|
||||
const placeholder = page.locator('.affine-paragraph-placeholder.visible');
|
||||
await expect(placeholder).toBeVisible();
|
||||
});
|
||||
|
||||
test('press arrow up when image is selected should move to previous paragraph', async ({
|
||||
page,
|
||||
}) => {
|
||||
await activeEmbed(page);
|
||||
await pressArrowUp(page);
|
||||
await assertRichTextInlineRange(page, 0, 0);
|
||||
await type(page, 'aa');
|
||||
await assertRichTexts(page, ['aa']);
|
||||
});
|
||||
|
||||
test('press arrow down when image is selected should move to previous paragraph', async ({
|
||||
page,
|
||||
}) => {
|
||||
await activeEmbed(page);
|
||||
await pressEnter(page);
|
||||
await type(page, 'aa');
|
||||
await activeEmbed(page);
|
||||
await pressArrowDown(page);
|
||||
await type(page, 'bb');
|
||||
await assertRichTexts(page, ['', 'bbaa']);
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user