Files
AFFiNE-Mirror/tests/blocksuite/e2e/attachment.spec.ts
pengx17 fad49bb070 feat(editor): audio block (#10947)
AudioMedia entity for loading & controlling a single audio media
AudioMediaManagerService: Global audio state synchronization across tabs
AudioAttachmentService + AudioAttachmentBlock for manipulating AttachmentBlock in affine - e.g., filling transcription (using mock endpoint for now)
Added AudioBlock + AudioPlayer for rendering audio block in affine (new transcription block whose renderer is provided in affine)

fix AF-2292
fix AF-2337
2025-03-20 12:46:15 +00:00

464 lines
13 KiB
TypeScript

import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { sleep } from '@blocksuite/global/utils';
import { expect, type Page } from '@playwright/test';
import { dragBlockToPoint, popImageMoreMenu } from './utils/actions/drag.js';
import { switchEditorMode } from './utils/actions/edgeless.js';
import {
pressArrowDown,
pressArrowUp,
pressBackspace,
pressEnter,
pressEscape,
pressShiftTab,
pressTab,
redoByKeyboard,
SHORT_KEY,
type,
undoByKeyboard,
} from './utils/actions/keyboard.js';
import {
captureHistory,
enterPlaygroundRoom,
focusRichText,
getPageSnapshot,
initEmptyEdgelessState,
initEmptyParagraphState,
resetHistory,
waitNextFrame,
} from './utils/actions/misc.js';
import {
assertBlockChildrenIds,
assertBlockCount,
assertBlockFlavour,
assertBlockSelections,
assertKeyboardWorkInInput,
assertParentBlockFlavour,
assertRichImage,
assertRichTextInlineRange,
} from './utils/asserts.js';
import { test } from './utils/playwright.js';
const FILE_NAME = 'test-card-1.png';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const FILE_PATH = path.resolve(
__dirname,
`../../../blocksuite/playground/public/${FILE_NAME}`
);
function getAttachment(page: Page) {
const attachment = page.locator('affine-attachment');
const loading = attachment.locator('.affine-attachment-card.loading');
const toolbar = page.locator('affine-toolbar-widget editor-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.click();
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 }, testInfo) => {
await enterPlaygroundRoom(page);
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');
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}.json`
);
});
test('should undo/redo works for attachment', async ({ page }, testInfo) => {
await enterPlaygroundRoom(page);
await initEmptyParagraphState(page);
const { insertAttachment, waitLoading } = getAttachment(page);
await focusRichText(page);
await insertAttachment();
// Wait for the attachment to be uploaded
await waitLoading();
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_1.json`
);
await undoByKeyboard(page);
await waitNextFrame(page);
// The loading/error state should not be restored after undo
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_2.json`
);
await redoByKeyboard(page);
await waitNextFrame(page);
// The loading/error state should not be restored after undo
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_3.json`
);
});
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.click();
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 }, testInfo) => {
await enterPlaygroundRoom(page);
await initEmptyParagraphState(page);
const {
attachment,
insertAttachment,
waitLoading,
turnToEmbed,
turnImageToCard,
} = getAttachment(page);
await focusRichText(page);
await insertAttachment();
// Wait for the attachment to be uploaded
await waitLoading();
await attachment.click();
await turnToEmbed();
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_1.json`
);
await turnImageToCard();
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_2.json`
);
});
test('should attachment can be deleted', async ({ page }, testInfo) => {
await enterPlaygroundRoom(page);
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);
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}.json`
);
});
test(`support dragging attachment block directly`, async ({
page,
}, testInfo) => {
await enterPlaygroundRoom(page);
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');
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_1.json`
);
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);
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_2.json`
);
// 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);
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
`${testInfo.title}_3.json`
);
});
test('press backspace after attachment block can select attachment 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');
});