From 02c41acbd79b276c89d183500361b474f5b2b005 Mon Sep 17 00:00:00 2001 From: Yue Wu Date: Thu, 19 Sep 2024 18:36:25 +0800 Subject: [PATCH] fix: duplication occurs when card view is switched to embed view --- .../embed-card-toolbar/embed-card-toolbar.ts | 37 +++-- tests/bookmark.spec.ts | 156 ++++++++++++++++-- .../bookmark.spec.ts/embed-figma.json | 54 ++++++ .../bookmark.spec.ts/embed-youtube.json | 59 +++++++ .../bookmark.spec.ts/horizontal-figma.json | 56 +++++++ .../bookmark.spec.ts/horizontal-youtube.json | 56 +++++++ tests/utils/ignore.ts | 28 ++++ 7 files changed, 418 insertions(+), 28 deletions(-) create mode 100644 tests/snapshots/bookmark.spec.ts/embed-figma.json create mode 100644 tests/snapshots/bookmark.spec.ts/embed-youtube.json create mode 100644 tests/snapshots/bookmark.spec.ts/horizontal-figma.json create mode 100644 tests/snapshots/bookmark.spec.ts/horizontal-youtube.json create mode 100644 tests/utils/ignore.ts diff --git a/packages/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts b/packages/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts index 338e93e83ade..0846bb9a8151 100644 --- a/packages/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts +++ b/packages/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts @@ -32,7 +32,6 @@ import { } from '@blocksuite/affine-shared/services'; import { getHostName } from '@blocksuite/affine-shared/utils'; import { WidgetComponent } from '@blocksuite/block-std'; -import { assertExists } from '@blocksuite/global/utils'; import { type BlockModel, DocCollection } from '@blocksuite/store'; import { autoUpdate, computePosition, flip, offset } from '@floating-ui/dom'; import { html, nothing, type TemplateResult } from 'lit'; @@ -260,7 +259,8 @@ export class EmbedCardToolbar extends WidgetComponent< return; } - const { doc, url, style, caption } = this.focusModel; + const targetModel = this.focusModel; + const { doc, url, style, caption } = targetModel; let targetFlavour = 'affine:bookmark', targetStyle = style; @@ -277,9 +277,9 @@ export class EmbedCardToolbar extends WidgetComponent< )[0]; } - const parent = doc.getParent(this.focusModel); - assertExists(parent); - const index = parent.children.indexOf(this.focusModel); + const parent = doc.getParent(targetModel); + if (!parent) return; + const index = parent.children.indexOf(targetModel); doc.addBlock( targetFlavour as never, @@ -288,7 +288,7 @@ export class EmbedCardToolbar extends WidgetComponent< index ); this.std.selection.setGroup('note', []); - doc.deleteBlock(this.focusModel); + doc.deleteBlock(targetModel); } private _convertToEmbedView() { @@ -309,7 +309,8 @@ export class EmbedCardToolbar extends WidgetComponent< return; } - const { doc, url, style, caption } = this.focusModel; + const targetModel = this.focusModel; + const { doc, url, style, caption } = targetModel; if (!this._embedOptions || this._embedOptions.viewType !== 'embed') { return; @@ -320,9 +321,9 @@ export class EmbedCardToolbar extends WidgetComponent< ? style : styles.filter(style => style !== 'vertical' && style !== 'cube')[0]; - const parent = doc.getParent(this.focusModel); - assertExists(parent); - const index = parent.children.indexOf(this.focusModel); + const parent = doc.getParent(targetModel); + if (!parent) return; + const index = parent.children.indexOf(targetModel); doc.addBlock( flavour as never, @@ -332,7 +333,7 @@ export class EmbedCardToolbar extends WidgetComponent< ); this.std.selection.setGroup('note', []); - doc.deleteBlock(this.focusModel); + doc.deleteBlock(targetModel); } private _copyUrl() { @@ -478,15 +479,15 @@ export class EmbedCardToolbar extends WidgetComponent< return; } - const { doc } = this.focusModel; - const parent = doc.getParent(this.focusModel); - const index = parent?.children.indexOf(this.focusModel); + const targetModel = this.focusModel; + const { doc, title, caption, url } = targetModel; + const parent = doc.getParent(targetModel); + const index = parent?.children.indexOf(targetModel); const yText = new DocCollection.Y.Text(); - const insert = - this.focusModel.title || this.focusModel.caption || this.focusModel.url; + const insert = title || caption || url; yText.insert(0, insert); - yText.format(0, insert.length, { link: this.focusModel.url }); + yText.format(0, insert.length, { link: url }); const text = new doc.Text(yText); doc.addBlock( 'affine:paragraph', @@ -497,7 +498,7 @@ export class EmbedCardToolbar extends WidgetComponent< index ); - doc.deleteBlock(this.focusModel); + doc.deleteBlock(targetModel); } private _viewMenuButton() { diff --git a/tests/bookmark.spec.ts b/tests/bookmark.spec.ts index 74682bf1c0f2..ac1fb15f41ce 100644 --- a/tests/bookmark.spec.ts +++ b/tests/bookmark.spec.ts @@ -1,6 +1,8 @@ import type { Page } from '@playwright/test'; +import type { BlockSnapshot } from '@store/index.js'; import { expect } from '@playwright/test'; +import { ignoreSnapshotId } from 'utils/ignore.js'; import { getEmbedCardToolbar } from 'utils/query.js'; import { @@ -8,6 +10,7 @@ import { copyByKeyboard, dragBlockToPoint, enterPlaygroundRoom, + expectConsoleMessage, focusRichText, getPageSnapshot, initEmptyEdgelessState, @@ -41,7 +44,12 @@ import { import './utils/declare-test-window.js'; import { scoped, test } from './utils/playwright.js'; -const inputUrl = 'http://localhost'; +const LOCAL_HOST_URL = 'http://localhost'; + +const YOUTUBE_URL = 'https://www.youtube.com/watch?v=fakeid'; + +const FIGMA_URL = + 'https://www.figma.com/design/JuXs6uOAICwf4I4tps0xKZ/%F0%9F%AA%A9-2024-S4'; test.beforeEach(async ({ page }) => { await page.route( @@ -54,7 +62,10 @@ test.beforeEach(async ({ page }) => { ); }); -const createBookmarkBlockBySlashMenu = async (page: Page) => { +const createBookmarkBlockBySlashMenu = async ( + page: Page, + url = LOCAL_HOST_URL +) => { await enterPlaygroundRoom(page); await initEmptyParagraphState(page); await focusRichText(page); @@ -62,7 +73,7 @@ const createBookmarkBlockBySlashMenu = async (page: Page) => { await type(page, '/link', 100); await pressEnter(page); await page.waitForTimeout(100); - await type(page, inputUrl); + await type(page, url); await pressEnter(page); }; @@ -92,8 +103,8 @@ test( await initEmptyParagraphState(page); await focusRichText(page); - await type(page, inputUrl); - await setInlineRangeInSelectedRichText(page, 0, inputUrl.length); + await type(page, LOCAL_HOST_URL); + await setInlineRangeInSelectedRichText(page, 0, LOCAL_HOST_URL.length); await copyByKeyboard(page); await focusRichText(page); await type(page, '/link'); @@ -112,7 +123,7 @@ test( await enterPlaygroundRoom(page); const ids = await initEmptyEdgelessState(page); await focusRichText(page); - await type(page, inputUrl); + await type(page, LOCAL_HOST_URL); await switchEditorMode(page); @@ -203,7 +214,7 @@ test('press backspace after bookmark block can select bookmark block', async ({ await type(page, '/link'); await pressEnter(page); await page.waitForTimeout(100); - await type(page, inputUrl); + await type(page, LOCAL_HOST_URL); await pressEnter(page); await focusRichText(page); @@ -273,7 +284,7 @@ test('indent bookmark block to paragraph', async ({ page }) => { await pressEnter(page); await type(page, '/link', 100); await pressEnter(page); - await type(page, inputUrl); + await type(page, LOCAL_HOST_URL); await pressEnter(page); await assertBlockChildrenIds(page, '1', ['2', '4']); @@ -301,7 +312,7 @@ test('indent bookmark block to list', async ({ page }) => { await pressEnter(page); await type(page, '/link', 100); await pressEnter(page); - await type(page, inputUrl); + await type(page, LOCAL_HOST_URL); await pressEnter(page); await assertBlockChildrenIds(page, '1', ['3', '5']); @@ -330,7 +341,7 @@ test('bookmark can be dragged from note to surface top level block', async ({ await type(page, '/link', 100); await pressEnter(page); await page.waitForTimeout(100); - await type(page, inputUrl); + await type(page, LOCAL_HOST_URL); await pressEnter(page); await switchEditorMode(page); @@ -341,3 +352,128 @@ test('bookmark can be dragged from note to surface top level block', async ({ await waitNextFrame(page); await assertParentBlockFlavour(page, '5', 'affine:surface'); }); + +test.describe('embed youtube card', () => { + test(scoped`create youtube card by slash menu`, async ({ page }) => { + expectConsoleMessage(page, `Unrecognized feature: 'web-share'.`); + expectConsoleMessage( + page, + 'Failed to load resource: the server responded with a status of 400 ()' + ); + expectConsoleMessage( + page, + 'Failed to load resource: the server responded with a status of 404 ()' + ); + 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: 'web-share'.`); + expectConsoleMessage( + page, + 'Failed to load resource: the server responded with a status of 400 ()' + ); + expectConsoleMessage( + page, + 'Failed to load resource: the server responded with a status of 404 ()' + ); + + 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: the server responded with a status of 403 ()' + ); + 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: the server responded with a status of 403 ()' + ); + 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'); + }); +}); diff --git a/tests/snapshots/bookmark.spec.ts/embed-figma.json b/tests/snapshots/bookmark.spec.ts/embed-figma.json new file mode 100644 index 000000000000..0f37f4edd47a --- /dev/null +++ b/tests/snapshots/bookmark.spec.ts/embed-figma.json @@ -0,0 +1,54 @@ +{ + "type": "block", + "id": "*", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,800,95]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:embed-figma", + "version": 1, + "props": { + "index": "a0", + "xywh": "[0,0,0,0]", + "rotate": 0, + "style": "figma", + "url": "https://www.figma.com/design/JuXs6uOAICwf4I4tps0xKZ/%F0%9F%AA%A9-2024-S4", + "caption": null, + "title": "Figma", + "description": "https://www.figma.com/design/JuXs6uOAICwf4I4tps0xKZ/%F0%9F%AA%A9-2024-S4" + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/bookmark.spec.ts/embed-youtube.json b/tests/snapshots/bookmark.spec.ts/embed-youtube.json new file mode 100644 index 000000000000..4d1c0e1f7e5a --- /dev/null +++ b/tests/snapshots/bookmark.spec.ts/embed-youtube.json @@ -0,0 +1,59 @@ +{ + "type": "block", + "id": "*", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,800,95]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:embed-youtube", + "version": 1, + "props": { + "index": "a0", + "xywh": "[0,0,0,0]", + "rotate": 0, + "style": "video", + "url": "https://www.youtube.com/watch?v=fakeid", + "caption": null, + "image": null, + "title": null, + "description": null, + "creator": null, + "creatorUrl": null, + "creatorImage": null, + "videoId": "fakeid" + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/bookmark.spec.ts/horizontal-figma.json b/tests/snapshots/bookmark.spec.ts/horizontal-figma.json new file mode 100644 index 000000000000..079ebfd4fdd5 --- /dev/null +++ b/tests/snapshots/bookmark.spec.ts/horizontal-figma.json @@ -0,0 +1,56 @@ +{ + "type": "block", + "id": "*", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,800,95]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:bookmark", + "version": 1, + "props": { + "style": "horizontal", + "url": "https://www.figma.com/design/JuXs6uOAICwf4I4tps0xKZ/%F0%9F%AA%A9-2024-S4", + "caption": null, + "description": null, + "icon": null, + "image": null, + "title": null, + "index": "a0", + "xywh": "[0,0,0,0]", + "rotate": 0 + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/bookmark.spec.ts/horizontal-youtube.json b/tests/snapshots/bookmark.spec.ts/horizontal-youtube.json new file mode 100644 index 000000000000..e6eb9372f06a --- /dev/null +++ b/tests/snapshots/bookmark.spec.ts/horizontal-youtube.json @@ -0,0 +1,56 @@ +{ + "type": "block", + "id": "*", + "flavour": "affine:page", + "version": 2, + "props": { + "title": { + "$blocksuite:internal:text$": true, + "delta": [] + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:note", + "version": 1, + "props": { + "xywh": "[0,0,800,95]", + "background": "--affine-note-background-white", + "index": "a0", + "hidden": false, + "displayMode": "both", + "edgeless": { + "style": { + "borderRadius": 8, + "borderSize": 4, + "borderStyle": "none", + "shadowType": "--affine-note-shadow-box" + } + } + }, + "children": [ + { + "type": "block", + "id": "*", + "flavour": "affine:bookmark", + "version": 1, + "props": { + "style": "horizontal", + "url": "https://www.youtube.com/watch?v=fakeid", + "caption": null, + "description": null, + "icon": null, + "image": null, + "title": null, + "index": "a0", + "xywh": "[0,0,0,0]", + "rotate": 0 + }, + "children": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/utils/ignore.ts b/tests/utils/ignore.ts new file mode 100644 index 000000000000..a0859d8b7581 --- /dev/null +++ b/tests/utils/ignore.ts @@ -0,0 +1,28 @@ +import type { BlockSnapshot } from '@store/index.js'; + +export function ignoreFields(target: unknown, keys: string[]): unknown { + if (Array.isArray(target)) { + return target.map((item: unknown) => ignoreFields(item, keys)); + } else if (typeof target === 'object' && target !== null) { + return Object.keys(target).reduce( + (acc: Record, key: string) => { + if (keys.includes(key)) { + acc[key] = '*'; + } else { + acc[key] = ignoreFields( + (target as Record)[key], + keys + ); + } + return acc; + }, + {} + ); + } + return target; +} + +export function ignoreSnapshotId(snapshot: BlockSnapshot) { + const ignored = ignoreFields(snapshot, ['id']); + return JSON.stringify(ignored, null, 2); +}