Files
upstream-affine/tests/blocksuite/e2e/attachment.spec.ts
upstream-mirror 702cc698a7
Some checks failed
Build & Test / Analyze (javascript, affine) (push) Failing after 4s
Build & Test / Analyze (typescript, affine) (push) Failing after 3s
Build & Test / Rust test filter (push) Failing after 4s
Build & Test / Check yarn binary (push) Failing after 3s
Build & Test / E2E BlockSuite Test (3) (push) Failing after 3s
Build & Test / Analyze (javascript, blocksuite) (push) Failing after 3s
Build & Test / Analyze (typescript, blocksuite) (push) Failing after 3s
Build & Test / Mobile native build filter (push) Failing after 3s
Build & Test / E2E BlockSuite Test (1) (push) Failing after 4s
Build & Test / E2E BlockSuite Test (2) (push) Failing after 4s
Build & Test / E2E Mobile Test (1) (push) Failing after 3s
Build & Test / Unit Test (1) (push) Has been skipped
Build & Test / Unit Test (2) (push) Has been skipped
Build & Test / Unit Test (3) (push) Has been skipped
Build & Test / Unit Test (4) (push) Has been skipped
Build & Test / Unit Test (5) (push) Has been skipped
Build & Test / Native Unit Test (push) Has been skipped
Build & Test / E2E BlockSuite Test (4) (push) Failing after 3s
Build & Test / E2E BlockSuite Test (5) (push) Failing after 3s
Build & Test / E2E BlockSuite Cross Browser Test (push) Failing after 3s
Build & Test / E2E Mobile Test (2) (push) Failing after 3s
Build & Test / Build AFFiNE native (x86_64-unknown-linux-gnu) (push) Failing after 3s
Build & Test / Build Server native (push) Failing after 3s
Build & Test / Check Git Status (push) Has been skipped
Build & Test / Server Test (0, 4) (push) Has been skipped
Build & Test / Server Test (1, 4) (push) Has been skipped
Build & Test / Server Test (2, 4) (push) Has been skipped
Build & Test / Server Test (3, 4) (push) Has been skipped
Build & Test / Server Test with Elasticsearch (push) Has been skipped
Build & Test / Build @affine/electron renderer (push) Failing after 3s
Build & Test / Copilot test filter (push) Failing after 4s
Build & Test / Lint Rust (push) Has been skipped
Build & Test / Server Test with Embedded Search (push) Has been skipped
Build & Test / Server Test with Manticore Search (push) Has been skipped
Build & Test / Server E2E Test (push) Has been skipped
Build & Test / Cloud Desktop E2E Test (push) Has been skipped
Build & Test / Cloud E2E Test 1/2 (push) Has been skipped
Build & Test / Cloud E2E Test 2/2 (push) Has been skipped
Build & Test / Build Android app (push) Has been skipped
Build & Test / Test Android app (push) Has been skipped
Build & Test / Run native tests (push) Has been skipped
Build & Test / Server Copilot Api Test (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (1, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (2, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (3, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (4, 5) (push) Has been skipped
Build & Test / Frontend Copilot E2E Test (5, 5) (push) Has been skipped
Release / Canary Gate (push) Has been skipped
Build & Test / Build AFFiNE native (aarch64-apple-darwin) (push) Has been cancelled
Build & Test / Lint (push) Has been cancelled
Build & Test / Typecheck (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-apple-darwin) (push) Has been cancelled
Build & Test / Desktop Test (ubuntu-latest, linux, x64, x86_64-unknown-linux-gnu, true) (push) Has been cancelled
Build & Test / Desktop Test (windows-latest, windows, x64, x86_64-pc-windows-msvc, true) (push) Has been cancelled
Build & Test / E2E Test (1) (push) Has been cancelled
Build & Test / E2E Test (2) (push) Has been cancelled
Build & Test / E2E Test (3) (push) Has been cancelled
Build & Test / E2E Test (4) (push) Has been cancelled
Build & Test / E2E Test (5) (push) Has been cancelled
Build & Test / Build AFFiNE native (aarch64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Build AFFiNE native (x86_64-pc-windows-msvc) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, arm64, aarch64-apple-darwin, true) (push) Has been cancelled
Build & Test / Desktop Test (macos-latest, macos, x64, x86_64-apple-darwin, false) (push) Has been cancelled
Build & Test / 3, 2, 1 Launch (push) Has been cancelled
Build & Test / Build iOS app (push) Has been cancelled
Copilot Test Automatically / Setup Test (push) Failing after 1s
Release / Prepare (push) Failing after 2s
Release / Release Cloud (push) Has been skipped
Release / Release Desktop (push) Has been skipped
Release / Release Docker Image (push) Has been skipped
Release / Release Mobile (push) Has been skipped
snapshot of https://github.com/toeverything/AFFiNE.git (canary, 2026-09-16; blobs > 131072B omitted)
2026-09-16 04:01:20 +05:00

465 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,
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 expect(renameInput).toBeVisible();
await renameInput.fill(newName);
await pressEnter(page);
await expect(renameInput).not.toBeVisible();
if (newName.length > 0) {
await expect.poll(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('44.73KB');
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 page.locator('affine-attachment').waitFor({ state: 'detached' });
// 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');
await expect.poll(getName).toBe('new-name.png');
await rename('');
await expect.poll(getName).toBe('.png');
await rename('abc');
await expect.poll(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('44.73KB');
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-attachment-container.focused');
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');
});