mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 12:28:42 +00:00
chore: migrate blocksuite test (#9222)
This commit is contained in:
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',
|
||||
},
|
||||
]);
|
||||
});
|
||||
Reference in New Issue
Block a user