From 61ce1123ae8769712da263789fb2362167d006e2 Mon Sep 17 00:00:00 2001 From: L-Sun Date: Mon, 21 Apr 2025 08:54:04 +0000 Subject: [PATCH] refactor(editor): split synced-doc-block tests to serveral files (#11845) This PR splits `synced-doc-block.spec.ts` into multiple files for better organization. --- blocksuite/playground/apps/starter/main.ts | 2 + tests/blocksuite/e2e/embed-synced-doc.spec.ts | 276 ------------------ .../e2e/embed-synced-doc/edgeless.spec.ts | 66 +++++ .../e2e/embed-synced-doc/render.spec.ts | 83 ++++++ .../e2e/embed-synced-doc/toolbar.spec.ts | 73 +++++ .../blocksuite/e2e/embed-synced-doc/utils.ts | 110 +++++++ .../e2e/utils/declare-test-window.ts | 1 + 7 files changed, 335 insertions(+), 276 deletions(-) delete mode 100644 tests/blocksuite/e2e/embed-synced-doc.spec.ts create mode 100644 tests/blocksuite/e2e/embed-synced-doc/edgeless.spec.ts create mode 100644 tests/blocksuite/e2e/embed-synced-doc/render.spec.ts create mode 100644 tests/blocksuite/e2e/embed-synced-doc/toolbar.spec.ts create mode 100644 tests/blocksuite/e2e/embed-synced-doc/utils.ts diff --git a/blocksuite/playground/apps/starter/main.ts b/blocksuite/playground/apps/starter/main.ts index 92885168c..e47772841 100644 --- a/blocksuite/playground/apps/starter/main.ts +++ b/blocksuite/playground/apps/starter/main.ts @@ -8,6 +8,7 @@ import * as globalUtils from '@blocksuite/affine/global/utils'; import * as services from '@blocksuite/affine/shared/services'; import * as blockStd from '@blocksuite/affine/std'; import * as store from '@blocksuite/affine/store'; +import * as affineModel from '@blocksuite/affine-model'; import * as editor from '@blocksuite/integration-test'; import { effects as presetsEffects } from '@blocksuite/integration-test/effects'; @@ -47,6 +48,7 @@ async function main() { services, editor, blockStd: blockStd, + affineModel: affineModel, }), }); await prepareTestApp(collection); diff --git a/tests/blocksuite/e2e/embed-synced-doc.spec.ts b/tests/blocksuite/e2e/embed-synced-doc.spec.ts deleted file mode 100644 index 674a67034..000000000 --- a/tests/blocksuite/e2e/embed-synced-doc.spec.ts +++ /dev/null @@ -1,276 +0,0 @@ -import type { DatabaseBlockModel } from '@blocksuite/affine/model'; -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(); - if (!lickedDocBox) { - throw new Error('lickedDocBox is not found'); - } - await page.mouse.move( - lickedDocBox.x + lickedDocBox.width / 2, - lickedDocBox.y + lickedDocBox.height / 2 - ); - - await waitNextFrame(page, 200); - const toolbar = page.locator('affine-toolbar-widget editor-toolbar'); - await expect(toolbar).toBeVisible(); - - const switchButton = toolbar.getByRole('button', { name: 'Switch view' }); - await switchButton.click(); - - const embedSyncedDocBtn = toolbar.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(); - if (!syncedDocBox) { - throw new Error('syncedDocBox is not found'); - } - await page.mouse.click( - syncedDocBox.x + syncedDocBox.width / 2, - syncedDocBox.y + syncedDocBox.height / 2 - ); - - await waitNextFrame(page, 200); - const toolbar = page.locator('affine-toolbar-widget editor-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(); - if (!embedSyncedBox) { - throw new Error('embedSyncedBox is not found'); - } - 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(); - if (!noteBlockBox) { - throw new Error('noteBlockBox is not found'); - } - 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(); - if (!embedSyncedBox) { - throw new Error('embedSyncedBox is not found'); - } - 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; - if (!EmbedSyncedDocBlockBox) { - throw new Error('EmbedSyncedDocBlockBox is not found'); - } - expect(EmbedSyncedDocBlockBox.height).toBeCloseTo(height + 2 * border, 1); - } - ); - - test('nested embed synced doc should be rendered as card when depth >=1', async ({ - page, - }) => { - await page.evaluate(() => { - const { doc, collection } = window; - const rootId = doc.addBlock('affine:page', { - title: new window.$blocksuite.store.Text(), - }); - - const noteId = doc.addBlock('affine:note', {}, rootId); - doc.addBlock('affine:paragraph', {}, noteId); - - const doc2 = collection.createDoc('doc2').getStore(); - doc2.load(); - const rootId2 = doc2.addBlock('affine:page', { - title: new window.$blocksuite.store.Text('Doc 2'), - }); - - const noteId2 = doc2.addBlock('affine:note', {}, rootId2); - doc2.addBlock( - 'affine:paragraph', - { - text: new window.$blocksuite.store.Text('Hello from Doc 2'), - }, - noteId2 - ); - - const doc3 = collection.createDoc('doc3').getStore(); - doc3.load(); - const rootId3 = doc3.addBlock('affine:page', { - title: new window.$blocksuite.store.Text('Doc 3'), - }); - - const noteId3 = doc3.addBlock('affine:note', {}, rootId3); - doc3.addBlock( - 'affine:paragraph', - { - text: new window.$blocksuite.store.Text('Hello from Doc 3'), - }, - noteId3 - ); - - doc2.addBlock( - 'affine:embed-synced-doc', - { - pageId: 'doc3', - }, - noteId2 - ); - doc.addBlock( - 'affine:embed-synced-doc', - { - pageId: 'doc2', - }, - noteId - ); - }); - expect(await page.locator('affine-embed-synced-doc-block').count()).toBe(2); - expect(await page.locator('affine-paragraph').count()).toBe(2); - expect(await page.locator('affine-embed-synced-doc-card').count()).toBe(1); - expect(await page.locator('editor-host').count()).toBe(2); - }); - - test.describe('synced doc should be readonly', () => { - test('synced doc should be readonly', async ({ page }) => { - await initEmptyParagraphState(page); - await focusRichText(page); - await createAndConvertToEmbedSyncedDoc(page); - - const locator = page.locator('affine-embed-synced-doc-block'); - await expect(locator).toBeVisible(); - await locator.click(); - - const toolbar = page.locator('affine-toolbar-widget editor-toolbar'); - const openMenu = toolbar.getByRole('button', { name: 'Open doc' }); - await openMenu.click(); - - const button = toolbar.getByRole('button', { name: 'Open this doc' }); - await button.click(); - - await page.evaluate(async () => { - const { collection } = window; - const getDocCollection = () => { - for (const [id, doc] of collection.docs.entries()) { - if (id === 'doc:home') { - continue; - } - return doc; - } - return null; - }; - - const doc2Collection = getDocCollection(); - const doc2 = doc2Collection!.getStore(); - const [noteBlock] = doc2!.getBlocksByFlavour('affine:note'); - const noteId = noteBlock.id; - - const databaseId = doc2.addBlock( - 'affine:database', - { - title: new window.$blocksuite.store.Text('Database 1'), - }, - noteId - ); - const model = doc2.getModelById(databaseId) as DatabaseBlockModel; - const datasource = - new window.$blocksuite.blocks.database.DatabaseBlockDataSource(model); - datasource.viewManager.viewAdd('table'); - }); - - // go back to previous doc - await page.evaluate(() => { - const { collection, editor } = window; - editor.doc = collection.getDoc('doc:home')!.getStore(); - }); - - const databaseFirstCell = page.locator( - '.affine-database-column-header.database-row' - ); - await databaseFirstCell.click({ force: true }); - const selectedCount = await page - .locator('.affine-embed-synced-doc-container.selected') - .count(); - expect(selectedCount).toBe(1); - }); - }); -}); diff --git a/tests/blocksuite/e2e/embed-synced-doc/edgeless.spec.ts b/tests/blocksuite/e2e/embed-synced-doc/edgeless.spec.ts new file mode 100644 index 000000000..d00196fa5 --- /dev/null +++ b/tests/blocksuite/e2e/embed-synced-doc/edgeless.spec.ts @@ -0,0 +1,66 @@ +import { expect } from '@playwright/test'; + +import { switchEditorMode } from '../utils/actions/edgeless.js'; +import { enterPlaygroundRoom, waitNextFrame } from '../utils/actions/misc.js'; +import { test } from '../utils/playwright.js'; +import { initEmbedSyncedDocState } from './utils.js'; + +test.describe('Embed synced doc', () => { + test.beforeEach(async ({ page }) => { + await enterPlaygroundRoom(page); + }); + + test.fixme( + 'drag embed synced doc to whiteboard should fit in height', + async ({ page }) => { + await initEmbedSyncedDocState(page, [ + { title: 'Root Doc', content: 'hello root doc' }, + { title: 'Page 1', content: 'hello page 1' }, + ]); + + // Switch to edgeless mode + await switchEditorMode(page); + + // Double click on note to enter edit status + const noteBlock = page.locator('affine-edgeless-note'); + await noteBlock.dblclick(); + + // Drag the embed synced doc to whiteboard + const embedSyncedBlockInNote = page.locator( + 'affine-embed-synced-doc-block' + ); + const embedSyncedBoxInNote = await embedSyncedBlockInNote.boundingBox(); + if (!embedSyncedBoxInNote) { + throw new Error('embedSyncedBoxInNote is not found'); + } + const height = embedSyncedBoxInNote.height; + await page.mouse.move( + embedSyncedBoxInNote.x - 10, + embedSyncedBoxInNote.y - 100 + ); + await page.mouse.move( + embedSyncedBoxInNote.x - 10, + embedSyncedBoxInNote.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 EmbedSyncedDocBlockInCanvas = page.locator( + 'affine-embed-edgeless-synced-doc-block' + ); + const EmbedSyncedDocBlockBoxInCanvas = + await EmbedSyncedDocBlockInCanvas.boundingBox(); + const border = 1; + if (!EmbedSyncedDocBlockBoxInCanvas) { + throw new Error('EmbedSyncedDocBlockBoxInCanvas is not found'); + } + expect(EmbedSyncedDocBlockBoxInCanvas.height).toBeCloseTo( + height + 2 * border, + 1 + ); + } + ); +}); diff --git a/tests/blocksuite/e2e/embed-synced-doc/render.spec.ts b/tests/blocksuite/e2e/embed-synced-doc/render.spec.ts new file mode 100644 index 000000000..32a35afa8 --- /dev/null +++ b/tests/blocksuite/e2e/embed-synced-doc/render.spec.ts @@ -0,0 +1,83 @@ +import type { DatabaseBlockModel } from '@blocksuite/affine/model'; +import { expect } from '@playwright/test'; + +import { enterPlaygroundRoom } from '../utils/actions'; +import { test } from '../utils/playwright'; +import { initEmbedSyncedDocState } from './utils'; + +test.describe('embed-synced-doc render', () => { + test.beforeEach(async ({ page }) => { + await enterPlaygroundRoom(page); + }); + + test('nested embed synced doc should be rendered as card when depth >=1', async ({ + page, + }) => { + await initEmbedSyncedDocState( + page, + [ + { title: 'Root', content: 'Hello from Root' }, + { title: 'Doc 2', content: 'Hello from Doc 2' }, + { title: 'Doc 3', content: 'Hello from Doc 3' }, + ], + { chain: true } + ); + + 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('synced doc should be readonly', async ({ page }) => { + const [_, embedDocId] = await initEmbedSyncedDocState(page, [ + { title: 'Root', content: 'Hello from Root' }, + { title: 'Doc 2', content: 'Hello from Doc 2' }, + ]); + + const locator = page.locator('affine-embed-synced-doc-block'); + await expect(locator).toBeVisible(); + await locator.click(); + + const toolbar = page.locator('affine-toolbar-widget editor-toolbar'); + const openMenu = toolbar.getByRole('button', { name: 'Open doc' }); + await openMenu.click(); + + const button = toolbar.getByRole('button', { name: 'Open this doc' }); + await button.click(); + + await page.evaluate(async embedDocId => { + const { collection } = window; + const doc2 = collection.getDoc(embedDocId)!.getStore(); + const [noteBlock] = doc2!.getBlocksByFlavour('affine:note'); + const noteId = noteBlock.id; + + const databaseId = doc2.addBlock( + 'affine:database', + { + title: new window.$blocksuite.store.Text('Database 1'), + }, + noteId + ); + const model = doc2.getModelById(databaseId) as DatabaseBlockModel; + const datasource = + new window.$blocksuite.blocks.database.DatabaseBlockDataSource(model); + datasource.viewManager.viewAdd('table'); + }, embedDocId); + + // go back to previous doc + await page.evaluate(() => { + const { collection, editor } = window; + editor.doc = collection.getDoc('doc:home')!.getStore(); + }); + + const databaseFirstCell = page.locator( + '.affine-database-column-header.database-row' + ); + await databaseFirstCell.click({ force: true }); + const selectedCount = await page + .locator('.affine-embed-synced-doc-container.selected') + .count(); + expect(selectedCount).toBe(1); + }); +}); diff --git a/tests/blocksuite/e2e/embed-synced-doc/toolbar.spec.ts b/tests/blocksuite/e2e/embed-synced-doc/toolbar.spec.ts new file mode 100644 index 000000000..02beb8c3d --- /dev/null +++ b/tests/blocksuite/e2e/embed-synced-doc/toolbar.spec.ts @@ -0,0 +1,73 @@ +import { expect } from '@playwright/test'; + +import { + enterPlaygroundRoom, + focusRichText, + initEmptyParagraphState, + waitNextFrame, +} from '../utils/actions'; +import { getLinkedDocPopover } from '../utils/actions/linked-doc'; +import { test } from '../utils/playwright'; +import { initEmbedSyncedDocState } from './utils'; + +test.describe('embed-synced-doc toolbar', () => { + test.beforeEach(async ({ page }) => { + await enterPlaygroundRoom(page); + }); + + test('can change linked doc to embed synced doc', async ({ page }) => { + await initEmptyParagraphState(page); + await focusRichText(page); + + const { createLinkedDoc } = getLinkedDocPopover(page); + const linkedDoc = await createLinkedDoc('page1'); + await linkedDoc.hover(); + + const toolbar = page.locator('affine-toolbar-widget editor-toolbar'); + await expect(toolbar).toBeVisible(); + + const switchButton = toolbar.getByRole('button', { name: 'Switch view' }); + await switchButton.click(); + + const embedSyncedDocBtn = toolbar.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 embed synced doc to card view', async ({ page }) => { + await initEmbedSyncedDocState(page, [ + { title: 'Root', content: 'Hello from Root' }, + { title: 'Doc 2', content: 'Hello from Doc 2' }, + ]); + + const syncedDoc = page.locator('affine-embed-synced-doc-block'); + await syncedDoc.click(); + + await waitNextFrame(page, 500); + const toolbar = page.locator( + // TODO(@L-Sun): simplify this selector after that toolbar widget are disabled in preview rendering is ready + 'affine-page-root > div > affine-toolbar-widget editor-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); + }); +}); diff --git a/tests/blocksuite/e2e/embed-synced-doc/utils.ts b/tests/blocksuite/e2e/embed-synced-doc/utils.ts new file mode 100644 index 000000000..6e3dde588 --- /dev/null +++ b/tests/blocksuite/e2e/embed-synced-doc/utils.ts @@ -0,0 +1,110 @@ +import type { + EmbedSyncedDocBlockProps, + NoteBlockModel, + ParagraphProps, + RootBlockProps, +} from '@blocksuite/affine-model'; +import type { Store } from '@blocksuite/store'; +import type { Page } from '@playwright/test'; +/** + * using page.evaluate to init the embed synced doc state + * @param page - playwright page + * @param data - the data to init the embed synced doc state + * @param option.chain - doc1 -> doc2 -> doc3 -> ..., if chain is false, doc1 will be the parent of remaining docs + * @returns the ids of created docs + */ +export async function initEmbedSyncedDocState( + page: Page, + data: { title: string; content: string }[], + option?: { + chain?: boolean; + } +) { + if (data.length === 0) { + throw new Error('Data is empty'); + } + + return await page.evaluate( + ({ data, option }) => { + const createDoc = async ( + docId: string, + title: string, + content: string + ) => { + const { collection, $blocksuite } = window; + const Text = $blocksuite.store.Text; + + const store = ( + collection.getDoc(docId) ?? collection.createDoc(docId) + ).getStore(); + store.load(); + + const rootId = store.addBlock('affine:page', { + title: new Text(title), + } satisfies Partial); + + store.addBlock('affine:surface', {}, rootId); + + const noteId = store.addBlock('affine:note', {}, rootId); + store.addBlock( + 'affine:paragraph', + { + text: new Text(content), + } satisfies Partial, + noteId + ); + }; + + const getVisibleNote = (store: Store) => { + const note = store + .getModelsByFlavour('affine:note') + .filter((note): note is NoteBlockModel => { + return ( + note instanceof NoteBlockModel && + note.props.displayMode === NoteDisplayMode.DocAndEdgeless + ); + })[0]; + return note ?? null; + }; + + const docIds = data.map(({ title, content }, index) => { + const id = index === 0 ? window.doc.id : `embed-doc-${index}`; + createDoc(id, title, content); + return id; + }); + + const { NoteBlockModel, NoteDisplayMode } = + window.$blocksuite.affineModel; + + let prevId = window.doc.id; + for (let index = 1; index < docIds.length; index++) { + const docId = docIds[index]; + + const store = window.collection + .getDoc(option?.chain ? prevId : docIds[0]) + ?.getStore(); + if (!store) { + throw new Error('Store not found'); + } + + const note = getVisibleNote(store); + if (!note) { + throw new Error(`Note not found in ${docId}`); + } + + store.addBlock( + 'affine:embed-synced-doc', + { + pageId: docId, + } satisfies Partial, + note + ); + + prevId = docId; + } + + return docIds; + }, + { data, option } + ); +} diff --git a/tests/blocksuite/e2e/utils/declare-test-window.ts b/tests/blocksuite/e2e/utils/declare-test-window.ts index a1f6bc548..bda9aac3c 100644 --- a/tests/blocksuite/e2e/utils/declare-test-window.ts +++ b/tests/blocksuite/e2e/utils/declare-test-window.ts @@ -27,6 +27,7 @@ declare global { services: typeof import('@blocksuite/affine/shared/services'); editor: typeof import('@blocksuite/integration-test'); blockStd: typeof import('@blocksuite/affine/std'); + affineModel: typeof import('@blocksuite/affine-model'); }; collection: Workspace; doc: Store;