mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
1335 lines
36 KiB
TypeScript
1335 lines
36 KiB
TypeScript
import type { DeltaInsert } from '@inline/types.js';
|
|
import { expect } from '@playwright/test';
|
|
|
|
import {
|
|
captureHistory,
|
|
dragBetweenIndices,
|
|
dragOverTitle,
|
|
enterPlaygroundRoom,
|
|
focusRichText,
|
|
focusTitle,
|
|
getIndexCoordinate,
|
|
getPageSnapshot,
|
|
initEmptyEdgelessState,
|
|
initEmptyParagraphState,
|
|
initThreeDividers,
|
|
initThreeParagraphs,
|
|
pressArrowDown,
|
|
pressArrowLeft,
|
|
pressArrowRight,
|
|
pressArrowUp,
|
|
pressBackspace,
|
|
pressBackspaceWithShortKey,
|
|
pressEnter,
|
|
pressForwardDelete,
|
|
pressShiftEnter,
|
|
pressShiftTab,
|
|
pressSpace,
|
|
pressTab,
|
|
redoByClick,
|
|
redoByKeyboard,
|
|
resetHistory,
|
|
setSelection,
|
|
SHORT_KEY,
|
|
switchReadonly,
|
|
type,
|
|
undoByClick,
|
|
undoByKeyboard,
|
|
updateBlockType,
|
|
waitDefaultPageLoaded,
|
|
waitNextFrame,
|
|
} from './utils/actions/index.js';
|
|
import {
|
|
assertBlockChildrenFlavours,
|
|
assertBlockChildrenIds,
|
|
assertBlockCount,
|
|
assertBlockSelections,
|
|
assertBlockType,
|
|
assertClassName,
|
|
assertDivider,
|
|
assertDocTitleFocus,
|
|
assertRichTextInlineRange,
|
|
assertRichTexts,
|
|
assertStoreMatchJSX,
|
|
assertTitle,
|
|
} from './utils/asserts.js';
|
|
import { test } from './utils/playwright.js';
|
|
|
|
test('init paragraph by page title enter at last', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await waitDefaultPageLoaded(page);
|
|
await focusTitle(page);
|
|
await type(page, 'hello');
|
|
await pressEnter(page);
|
|
await type(page, 'world');
|
|
|
|
await assertTitle(page, 'hello');
|
|
await assertRichTexts(page, ['world', '']);
|
|
|
|
//#region Fixes: https://github.com/toeverything/blocksuite/issues/1007
|
|
test.info().annotations.push({
|
|
type: 'issue',
|
|
description: 'https://github.com/toeverything/blocksuite/issues/1007',
|
|
});
|
|
await page.keyboard.press('ArrowLeft');
|
|
await focusTitle(page);
|
|
await pressEnter(page);
|
|
await assertRichTexts(page, ['', 'world', '']);
|
|
//#endregion
|
|
});
|
|
|
|
test('init paragraph by page title enter in middle', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await waitDefaultPageLoaded(page);
|
|
await focusTitle(page);
|
|
await type(page, 'hello');
|
|
await page.keyboard.press('ArrowLeft');
|
|
await page.keyboard.press('ArrowLeft');
|
|
await page.keyboard.press('ArrowLeft');
|
|
await pressEnter(page);
|
|
|
|
await assertTitle(page, 'he');
|
|
await assertRichTexts(page, ['llo', '']);
|
|
});
|
|
|
|
test('drag over paragraph title', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await waitDefaultPageLoaded(page);
|
|
await focusTitle(page);
|
|
await type(page, 'hello');
|
|
await assertTitle(page, 'hello');
|
|
await resetHistory(page);
|
|
|
|
await dragOverTitle(page);
|
|
await page.keyboard.press('Backspace', { delay: 100 });
|
|
await assertTitle(page, '');
|
|
|
|
await undoByKeyboard(page);
|
|
await assertTitle(page, 'hello');
|
|
});
|
|
|
|
test('backspace and arrow on title', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await waitDefaultPageLoaded(page);
|
|
await focusTitle(page);
|
|
await type(page, 'hello');
|
|
await assertTitle(page, 'hello');
|
|
await resetHistory(page);
|
|
|
|
await pressBackspace(page);
|
|
await assertTitle(page, 'hell');
|
|
|
|
await pressArrowLeft(page, 2);
|
|
await captureHistory(page);
|
|
await pressBackspace(page);
|
|
await assertTitle(page, 'hll');
|
|
|
|
await pressArrowDown(page);
|
|
await assertRichTextInlineRange(page, 0, 0, 0);
|
|
|
|
await undoByKeyboard(page);
|
|
await assertTitle(page, 'hell');
|
|
|
|
await redoByClick(page);
|
|
await assertTitle(page, 'hll');
|
|
});
|
|
|
|
for (const { initState, desc } of [
|
|
{
|
|
initState: initEmptyParagraphState,
|
|
desc: 'without surface',
|
|
},
|
|
{
|
|
initState: initEmptyEdgelessState,
|
|
desc: 'with surface',
|
|
},
|
|
]) {
|
|
test(`backspace on line start of the first block (${desc})`, async ({
|
|
page,
|
|
}) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initState(page);
|
|
await waitDefaultPageLoaded(page);
|
|
await focusTitle(page);
|
|
await type(page, 'hello');
|
|
await assertTitle(page, 'hello');
|
|
await resetHistory(page);
|
|
|
|
await focusRichText(page, 0);
|
|
await type(page, 'abc');
|
|
await page.keyboard.press('ArrowLeft');
|
|
await page.keyboard.press('ArrowLeft');
|
|
await page.keyboard.press('ArrowLeft');
|
|
await page.waitForTimeout(300);
|
|
await assertRichTextInlineRange(page, 0, 0, 0);
|
|
|
|
await pressBackspace(page);
|
|
await assertTitle(page, 'helloabc');
|
|
|
|
await pressEnter(page);
|
|
await assertTitle(page, 'hello');
|
|
await assertRichTexts(page, ['abc', '']);
|
|
|
|
await pressBackspace(page);
|
|
await assertTitle(page, 'helloabc');
|
|
await assertRichTexts(page, ['']);
|
|
await undoByClick(page);
|
|
await assertTitle(page, 'hello');
|
|
await assertRichTexts(page, ['abc', '']);
|
|
|
|
await redoByClick(page);
|
|
await assertTitle(page, 'helloabc');
|
|
await assertRichTexts(page, ['']);
|
|
});
|
|
|
|
test(`backspace on line start of the first empty block (${desc})`, async ({
|
|
page,
|
|
}) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initState(page);
|
|
await focusTitle(page);
|
|
|
|
await pressArrowDown(page);
|
|
await pressBackspace(page);
|
|
await assertBlockCount(page, 'paragraph', 1);
|
|
|
|
await pressArrowDown(page);
|
|
await assertRichTextInlineRange(page, 0, 0, 0);
|
|
});
|
|
}
|
|
|
|
test('append new paragraph block by enter', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await type(page, 'hello');
|
|
await assertRichTextInlineRange(page, 0, 5, 0);
|
|
|
|
await pressEnter(page);
|
|
await assertRichTexts(page, ['hello', '']);
|
|
await assertRichTextInlineRange(page, 1, 0, 0);
|
|
|
|
await undoByKeyboard(page);
|
|
await waitNextFrame(page);
|
|
await assertRichTexts(page, ['hello']);
|
|
await assertRichTextInlineRange(page, 0, 5, 0);
|
|
|
|
await redoByKeyboard(page);
|
|
await waitNextFrame(page);
|
|
await assertRichTexts(page, ['hello', '']);
|
|
await assertRichTextInlineRange(page, 1, 0, 0);
|
|
});
|
|
|
|
test('insert new paragraph block by enter', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await pressEnter(page);
|
|
await pressEnter(page);
|
|
await assertRichTexts(page, ['', '', '']);
|
|
|
|
await focusRichText(page, 1);
|
|
await type(page, 'hello');
|
|
await assertRichTexts(page, ['', 'hello', '']);
|
|
|
|
await pressEnter(page);
|
|
await type(page, 'world');
|
|
await assertRichTexts(page, ['', 'hello', 'world', '']);
|
|
await assertBlockChildrenFlavours(page, '1', [
|
|
'affine:paragraph',
|
|
'affine:paragraph',
|
|
'affine:paragraph',
|
|
'affine:paragraph',
|
|
]);
|
|
});
|
|
|
|
test('split paragraph block by enter', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
|
|
await type(page, 'hello');
|
|
await assertRichTexts(page, ['hello']);
|
|
|
|
await pressArrowLeft(page, 3);
|
|
await page.waitForTimeout(300);
|
|
await assertRichTextInlineRange(page, 0, 2, 0);
|
|
|
|
await pressEnter(page);
|
|
await assertRichTexts(page, ['he', 'llo']);
|
|
await assertBlockChildrenFlavours(page, '1', [
|
|
'affine:paragraph',
|
|
'affine:paragraph',
|
|
]);
|
|
await assertRichTextInlineRange(page, 1, 0, 0);
|
|
|
|
await undoByKeyboard(page);
|
|
await assertRichTexts(page, ['hello']);
|
|
|
|
await redoByKeyboard(page);
|
|
await assertRichTexts(page, ['he', 'llo']);
|
|
});
|
|
|
|
test('split paragraph block with selected text by enter', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
|
|
await type(page, 'hello');
|
|
await assertRichTexts(page, ['hello']);
|
|
// Avoid Yjs history manager merge two operations
|
|
await captureHistory(page);
|
|
|
|
// select 'll'
|
|
await page.keyboard.press('ArrowLeft');
|
|
await page.keyboard.down('Shift');
|
|
await page.waitForTimeout(100);
|
|
await page.keyboard.press('ArrowLeft');
|
|
await page.keyboard.press('ArrowLeft');
|
|
await page.waitForTimeout(300);
|
|
await page.keyboard.up('Shift');
|
|
await assertRichTextInlineRange(page, 0, 2, 2);
|
|
|
|
await pressEnter(page);
|
|
await assertRichTexts(page, ['he', 'o']);
|
|
await assertBlockChildrenFlavours(page, '1', [
|
|
'affine:paragraph',
|
|
'affine:paragraph',
|
|
]);
|
|
await assertRichTextInlineRange(page, 1, 0, 0);
|
|
|
|
await undoByKeyboard(page);
|
|
await assertRichTexts(page, ['hello']);
|
|
|
|
await redoByKeyboard(page);
|
|
await assertRichTexts(page, ['he', 'o']);
|
|
});
|
|
|
|
test('add multi line by soft enter', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
|
|
await type(page, 'hello');
|
|
await assertRichTexts(page, ['hello']);
|
|
|
|
await pressArrowLeft(page, 3);
|
|
await assertRichTextInlineRange(page, 0, 2, 0);
|
|
// Avoid Yjs history manager merge two operations
|
|
await captureHistory(page);
|
|
|
|
await pressShiftEnter(page);
|
|
await assertRichTexts(page, ['he\nllo']);
|
|
await assertRichTextInlineRange(page, 0, 3, 0);
|
|
|
|
await undoByKeyboard(page);
|
|
await assertRichTexts(page, ['hello']);
|
|
|
|
await redoByKeyboard(page);
|
|
await assertRichTexts(page, ['he\nllo']);
|
|
});
|
|
|
|
test('indent and unindent existing paragraph block', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await type(page, 'hello');
|
|
|
|
await pressEnter(page);
|
|
await focusRichText(page, 1);
|
|
await type(page, 'world');
|
|
await assertRichTexts(page, ['hello', 'world']);
|
|
|
|
// indent
|
|
await page.keyboard.press('Tab');
|
|
await assertRichTexts(page, ['hello', 'world']);
|
|
await assertBlockChildrenIds(page, '1', ['2']);
|
|
await assertBlockChildrenIds(page, '2', ['3']);
|
|
|
|
// unindent
|
|
await pressShiftTab(page);
|
|
await assertRichTexts(page, ['hello', 'world']);
|
|
await assertBlockChildrenIds(page, '1', ['2', '3']);
|
|
|
|
await undoByKeyboard(page);
|
|
await assertBlockChildrenIds(page, '1', ['2']);
|
|
|
|
await redoByKeyboard(page);
|
|
await assertBlockChildrenIds(page, '1', ['2', '3']);
|
|
});
|
|
|
|
test('remove all indent for a paragraph block', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await type(page, 'hello');
|
|
await pressEnter(page);
|
|
await pressTab(page);
|
|
await type(page, 'world');
|
|
await pressEnter(page);
|
|
await pressTab(page);
|
|
await type(page, 'foo');
|
|
await assertBlockChildrenIds(page, '3', ['4']);
|
|
await assertRichTexts(page, ['hello', 'world', 'foo']);
|
|
await pressBackspaceWithShortKey(page);
|
|
await assertRichTexts(page, ['hello', 'world', '']);
|
|
await pressBackspaceWithShortKey(page);
|
|
await assertBlockChildrenIds(page, '1', ['2', '4']);
|
|
await assertBlockChildrenIds(page, '2', ['3']);
|
|
});
|
|
|
|
test('update paragraph with children to head type', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await type(page, 'aaa');
|
|
|
|
await pressEnter(page);
|
|
await focusRichText(page, 1);
|
|
await type(page, 'bbb');
|
|
await pressEnter(page);
|
|
await focusRichText(page, 2);
|
|
await type(page, 'ccc');
|
|
await assertRichTexts(page, ['aaa', 'bbb', 'ccc']);
|
|
|
|
// aaa
|
|
// bbc
|
|
// ccc
|
|
await focusRichText(page, 1);
|
|
await page.keyboard.press('Tab');
|
|
await focusRichText(page, 2);
|
|
await page.keyboard.press('Tab');
|
|
await assertRichTexts(page, ['aaa', 'bbb', 'ccc']);
|
|
await assertBlockChildrenIds(page, '1', ['2']);
|
|
await assertBlockChildrenIds(page, '2', ['3', '4']);
|
|
|
|
await focusRichText(page);
|
|
await pressArrowLeft(page, 3);
|
|
|
|
await type(page, '# ');
|
|
|
|
await assertRichTexts(page, ['aaa', 'bbb', 'ccc']);
|
|
await assertBlockChildrenIds(page, '2', ['3', '4']);
|
|
|
|
await undoByKeyboard(page);
|
|
await assertRichTexts(page, ['# aaa', 'bbb', 'ccc']);
|
|
await assertBlockChildrenIds(page, '1', ['2']);
|
|
await assertBlockChildrenIds(page, '2', ['3', '4']);
|
|
|
|
await redoByKeyboard(page);
|
|
await assertRichTexts(page, ['aaa', 'bbb', 'ccc']);
|
|
await assertBlockChildrenIds(page, '2', ['3', '4']);
|
|
});
|
|
|
|
test('should indent and unindent works with children', async ({
|
|
page,
|
|
}, testInfo) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await initThreeParagraphs(page);
|
|
await pressEnter(page);
|
|
await type(page, '012');
|
|
await pressEnter(page);
|
|
await type(page, '345');
|
|
// 123
|
|
// 456
|
|
// 789
|
|
// 012
|
|
// 345
|
|
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_init.json`
|
|
);
|
|
//-- test indent --//
|
|
|
|
// Focus 789
|
|
await focusRichText(page, 2);
|
|
await pressTab(page);
|
|
// 123
|
|
// 456
|
|
// 789|
|
|
// 012
|
|
// 345
|
|
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_indent.json`
|
|
);
|
|
|
|
// Focus 012
|
|
await focusRichText(page, 3);
|
|
await pressTab(page);
|
|
// 123
|
|
// 456
|
|
// 789
|
|
// 012|
|
|
// 345
|
|
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_indent_2.json`
|
|
);
|
|
|
|
// Focus 456
|
|
await focusRichText(page, 1);
|
|
await pressTab(page);
|
|
// 123
|
|
// 456|
|
|
// 789
|
|
// 012
|
|
// 345
|
|
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_indent_3.json`
|
|
);
|
|
|
|
// Focus 345
|
|
await focusRichText(page, 4);
|
|
await pressTab(page, 3);
|
|
// 123
|
|
// 456
|
|
// 789
|
|
// 012
|
|
// 345|
|
|
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_indent_4.json`
|
|
);
|
|
//-- test unindent --//
|
|
|
|
// Focus 456
|
|
await focusRichText(page, 1);
|
|
await pressShiftTab(page);
|
|
// 123
|
|
// 456|
|
|
// 789
|
|
// 012
|
|
// 345
|
|
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_unindent_1.json`
|
|
);
|
|
|
|
// Focus 012
|
|
await focusRichText(page, 3);
|
|
await pressShiftTab(page);
|
|
// 123
|
|
// 456
|
|
// 789
|
|
// 012|
|
|
// 345
|
|
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_unindent_2.json`
|
|
);
|
|
|
|
// Focus 789
|
|
await focusRichText(page, 2);
|
|
await pressShiftTab(page);
|
|
// 123
|
|
// 456
|
|
// 789|
|
|
// 012
|
|
// 345
|
|
|
|
// Focus 345
|
|
await focusRichText(page, 4);
|
|
await pressShiftTab(page);
|
|
// 123
|
|
// 456
|
|
// 789
|
|
// 012
|
|
// 345|
|
|
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_unindent_3.json`
|
|
);
|
|
});
|
|
|
|
test('paragraph with child block should work at enter', async ({
|
|
page,
|
|
}, testInfo) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await type(page, '123');
|
|
await pressEnter(page);
|
|
await type(page, '456');
|
|
|
|
await focusRichText(page, 1);
|
|
await page.keyboard.press('Tab');
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_init.json`
|
|
);
|
|
await focusRichText(page, 0);
|
|
await pressEnter(page);
|
|
await type(page, '789');
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_final.json`
|
|
);
|
|
});
|
|
|
|
test('should delete paragraph block child can hold cursor in correct position', async ({
|
|
page,
|
|
}, testInfo) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await type(page, '123');
|
|
await pressEnter(page);
|
|
await pressTab(page);
|
|
await waitNextFrame(page);
|
|
await type(page, '4');
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_init.json`
|
|
);
|
|
|
|
await pressBackspace(page, 2);
|
|
await waitNextFrame(page);
|
|
await type(page, 'now');
|
|
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_final.json`
|
|
);
|
|
});
|
|
|
|
test('switch between paragraph types', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await type(page, 'hello');
|
|
|
|
const selector = '.affine-paragraph-rich-text-wrapper';
|
|
|
|
await updateBlockType(page, 'affine:paragraph', 'h1');
|
|
await assertClassName(page, selector, /h1/);
|
|
|
|
await updateBlockType(page, 'affine:paragraph', 'h2');
|
|
await assertClassName(page, selector, /h2/);
|
|
|
|
await updateBlockType(page, 'affine:paragraph', 'h3');
|
|
await assertClassName(page, selector, /h3/);
|
|
|
|
await undoByClick(page);
|
|
await assertClassName(page, selector, /h2/);
|
|
|
|
await undoByClick(page);
|
|
await assertClassName(page, selector, /h1/);
|
|
});
|
|
|
|
test('delete at start of paragraph block', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await type(page, 'hello');
|
|
|
|
await pressEnter(page);
|
|
await type(page, 'a');
|
|
|
|
await updateBlockType(page, 'affine:paragraph', 'h1');
|
|
await focusRichText(page, 1);
|
|
await assertBlockType(page, '2', 'text');
|
|
await assertBlockType(page, '3', 'h1');
|
|
|
|
await pressBackspace(page);
|
|
await pressBackspace(page);
|
|
await assertBlockType(page, '3', 'text');
|
|
await assertBlockChildrenIds(page, '1', ['2', '3']);
|
|
|
|
await page.keyboard.press('Backspace');
|
|
await assertBlockChildrenIds(page, '1', ['2']);
|
|
|
|
await undoByClick(page);
|
|
await assertBlockChildrenIds(page, '1', ['2', '3']);
|
|
});
|
|
|
|
test('delete at start of paragraph immediately following list', async ({
|
|
page,
|
|
}) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await type(page, 'hello');
|
|
|
|
await pressEnter(page);
|
|
await type(page, 'a');
|
|
|
|
await captureHistory(page);
|
|
|
|
await assertRichTexts(page, ['hello', 'a']);
|
|
await assertBlockChildrenIds(page, '1', ['2', '3']);
|
|
await assertBlockType(page, '3', 'text');
|
|
|
|
// text -> bulleted
|
|
await focusRichText(page, 1);
|
|
await updateBlockType(page, 'affine:list', 'bulleted');
|
|
await assertBlockType(page, '2', 'text');
|
|
await assertBlockType(page, '4', 'bulleted');
|
|
await pressBackspace(page, 2);
|
|
await waitNextFrame(page);
|
|
await assertBlockType(page, '5', 'text');
|
|
await assertBlockChildrenIds(page, '1', ['2', '5']);
|
|
await pressBackspace(page);
|
|
await assertBlockChildrenIds(page, '1', ['2']);
|
|
|
|
// reset
|
|
await undoByClick(page);
|
|
await undoByClick(page);
|
|
await assertRichTexts(page, ['hello', 'a']);
|
|
await assertBlockChildrenIds(page, '1', ['2', '3']);
|
|
await assertBlockType(page, '3', 'text');
|
|
|
|
// text -> numbered
|
|
await focusRichText(page, 1);
|
|
await updateBlockType(page, 'affine:list', 'numbered');
|
|
await assertBlockType(page, '2', 'text');
|
|
await assertBlockType(page, '6', 'numbered');
|
|
await pressBackspace(page, 2);
|
|
await waitNextFrame(page);
|
|
await assertBlockType(page, '7', 'text');
|
|
await assertBlockChildrenIds(page, '1', ['2', '7']);
|
|
await pressBackspace(page);
|
|
await assertBlockChildrenIds(page, '1', ['2']);
|
|
|
|
// reset
|
|
await undoByClick(page);
|
|
await undoByClick(page);
|
|
await assertRichTexts(page, ['hello', 'a']);
|
|
await assertBlockChildrenIds(page, '1', ['2', '3']);
|
|
await assertBlockType(page, '3', 'text');
|
|
|
|
// text -> todo
|
|
await focusRichText(page, 1);
|
|
await updateBlockType(page, 'affine:list', 'todo');
|
|
await assertBlockType(page, '2', 'text');
|
|
await assertBlockType(page, '8', 'todo');
|
|
await pressBackspace(page, 2);
|
|
await waitNextFrame(page);
|
|
await assertBlockType(page, '9', 'text');
|
|
await assertBlockChildrenIds(page, '1', ['2', '9']);
|
|
await pressBackspace(page);
|
|
await assertBlockChildrenIds(page, '1', ['2']);
|
|
});
|
|
|
|
test('delete at start of paragraph with content', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await type(page, '123');
|
|
|
|
await pressEnter(page);
|
|
await type(page, '456');
|
|
await assertRichTexts(page, ['123', '456']);
|
|
|
|
await captureHistory(page);
|
|
|
|
await pressArrowLeft(page, 3);
|
|
await assertRichTextInlineRange(page, 1, 0, 0);
|
|
|
|
await pressBackspace(page);
|
|
await assertRichTexts(page, ['123456']);
|
|
|
|
await undoByClick(page);
|
|
await assertRichTexts(page, ['123', '456']);
|
|
});
|
|
|
|
test('get focus from page title enter', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusTitle(page);
|
|
await type(page, 'hello');
|
|
await assertRichTexts(page, ['']);
|
|
|
|
await pressEnter(page);
|
|
await type(page, 'world');
|
|
await assertRichTexts(page, ['world', '']);
|
|
});
|
|
|
|
test('handling keyup when cursor located in first paragraph', async ({
|
|
page,
|
|
}) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusTitle(page);
|
|
await type(page, 'hello');
|
|
await assertRichTexts(page, ['']);
|
|
|
|
await pressEnter(page);
|
|
await type(page, 'world');
|
|
await assertRichTexts(page, ['world', '']);
|
|
await pressArrowUp(page);
|
|
await waitNextFrame(page);
|
|
await pressArrowUp(page);
|
|
await assertDocTitleFocus(page);
|
|
});
|
|
|
|
test('after deleting a text row, cursor should jump to the end of previous list row', async ({
|
|
page,
|
|
}) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await type(page, 'hello');
|
|
await assertRichTextInlineRange(page, 0, 5, 0);
|
|
|
|
await pressEnter(page);
|
|
await type(page, 'w');
|
|
await assertRichTexts(page, ['hello', 'w']);
|
|
await assertRichTextInlineRange(page, 1, 1, 0);
|
|
await pressArrowUp(page);
|
|
await pressArrowDown(page);
|
|
|
|
await pressArrowLeft(page);
|
|
await pressBackspace(page);
|
|
await assertRichTextInlineRange(page, 0, 5, 0);
|
|
});
|
|
|
|
test('press tab in paragraph children', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await waitDefaultPageLoaded(page);
|
|
await focusTitle(page);
|
|
await pressEnter(page);
|
|
await type(page, '1');
|
|
await pressEnter(page);
|
|
await pressTab(page);
|
|
await type(page, '2');
|
|
await pressEnter(page);
|
|
await pressTab(page);
|
|
await type(page, '3');
|
|
await page.keyboard.press('ArrowUp', { delay: 50 });
|
|
await page.keyboard.press('ArrowLeft', { delay: 50 });
|
|
await type(page, '- ');
|
|
await assertRichTexts(page, ['1', '2', '3', '']);
|
|
});
|
|
|
|
test('press left in first paragraph start should not change cursor position', async ({
|
|
page,
|
|
}) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await type(page, '1');
|
|
|
|
await pressArrowLeft(page, 2);
|
|
await type(page, 'l');
|
|
await assertRichTexts(page, ['l1']);
|
|
await assertTitle(page, '');
|
|
});
|
|
|
|
test('press arrow down should move caret to the start of line', async ({
|
|
page,
|
|
}) => {
|
|
await enterPlaygroundRoom(page);
|
|
await page.evaluate(() => {
|
|
const { doc } = window;
|
|
const rootId = doc.addBlock('affine:page', {
|
|
title: new doc.Text(),
|
|
});
|
|
const note = doc.addBlock('affine:note', {}, rootId);
|
|
doc.addBlock(
|
|
'affine:paragraph',
|
|
{
|
|
text: new doc.Text('0'.repeat(100)),
|
|
},
|
|
note
|
|
);
|
|
doc.addBlock(
|
|
'affine:paragraph',
|
|
{
|
|
text: new doc.Text('1'),
|
|
},
|
|
note
|
|
);
|
|
});
|
|
|
|
// Focus the empty child paragraph
|
|
await focusRichText(page, 1);
|
|
await pressArrowLeft(page);
|
|
await pressArrowUp(page);
|
|
await pressArrowDown(page);
|
|
await type(page, '2');
|
|
await assertRichTexts(page, ['0'.repeat(100), '21']);
|
|
});
|
|
|
|
test('press arrow up in the second line should move caret to the first line', async ({
|
|
page,
|
|
}) => {
|
|
await enterPlaygroundRoom(page);
|
|
await page.evaluate(() => {
|
|
const { doc } = window;
|
|
const rootId = doc.addBlock('affine:page', {
|
|
title: new doc.Text(),
|
|
});
|
|
const note = doc.addBlock('affine:note', {}, rootId);
|
|
const delta = Array.from({ length: 150 }, (_, i) => {
|
|
return i % 2 === 0
|
|
? { insert: 'i', attributes: { italic: true } }
|
|
: { insert: 'b', attributes: { bold: true } };
|
|
}) as DeltaInsert[];
|
|
const text = new doc.Text(delta);
|
|
doc.addBlock('affine:paragraph', { text }, note);
|
|
doc.addBlock('affine:paragraph', {}, note);
|
|
});
|
|
|
|
// Focus the empty paragraph
|
|
await focusRichText(page, 1);
|
|
await assertRichTexts(page, ['ib'.repeat(75), '']);
|
|
await pressArrowUp(page, 2);
|
|
await type(page, '0');
|
|
await assertTitle(page, '');
|
|
await assertRichTexts(page, ['0' + 'ib'.repeat(75), '']);
|
|
await pressArrowUp(page, 2);
|
|
|
|
// At title
|
|
await type(page, '1');
|
|
await assertTitle(page, '1');
|
|
await assertRichTexts(page, ['0' + 'ib'.repeat(75), '']);
|
|
|
|
// At the first line of the first paragraph
|
|
await pressArrowDown(page);
|
|
// At the second paragraph
|
|
await pressArrowDown(page, 3);
|
|
await pressArrowRight(page);
|
|
await type(page, '2');
|
|
|
|
await assertRichTexts(page, ['0' + 'ib'.repeat(75), '2']);
|
|
|
|
// Go to the start of the second paragraph
|
|
await pressArrowLeft(page);
|
|
await pressArrowUp(page);
|
|
await pressArrowDown(page);
|
|
// Should be inserted at the start of the second paragraph
|
|
await type(page, '3');
|
|
await assertRichTexts(page, ['0' + 'ib'.repeat(75), '32']);
|
|
});
|
|
|
|
test('press arrow down in indent line should not move caret to the start of line', async ({
|
|
page,
|
|
}) => {
|
|
await enterPlaygroundRoom(page);
|
|
await page.evaluate(() => {
|
|
const { doc } = window;
|
|
const rootId = doc.addBlock('affine:page', {
|
|
title: new doc.Text(),
|
|
});
|
|
const note = doc.addBlock('affine:note', {}, rootId);
|
|
const p1 = doc.addBlock('affine:paragraph', {}, note);
|
|
const p2 = doc.addBlock('affine:paragraph', {}, p1);
|
|
doc.addBlock('affine:paragraph', {}, p2);
|
|
doc.addBlock(
|
|
'affine:paragraph',
|
|
{
|
|
text: new doc.Text('0'),
|
|
},
|
|
note
|
|
);
|
|
});
|
|
|
|
// Focus the empty child paragraph
|
|
await focusRichText(page, 2);
|
|
await pressArrowDown(page, 2);
|
|
await pressArrowRight(page);
|
|
await waitNextFrame(page);
|
|
// Now the caret should be at the end of the last paragraph
|
|
await type(page, '1');
|
|
await assertRichTexts(page, ['', '', '', '01']);
|
|
|
|
await focusRichText(page, 2);
|
|
await waitNextFrame(page);
|
|
// Insert a new long text to wrap the line
|
|
await page.keyboard.insertText('0'.repeat(100));
|
|
await waitNextFrame(page);
|
|
|
|
await focusRichText(page, 1);
|
|
// Through long text
|
|
await pressArrowDown(page, 3);
|
|
await pressArrowRight(page);
|
|
await type(page, '2');
|
|
await assertRichTexts(page, ['', '', '0'.repeat(100), '012']);
|
|
});
|
|
|
|
test('should placeholder works', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
const placeholder = page.locator('.affine-paragraph-placeholder.visible');
|
|
await expect(placeholder).toBeVisible();
|
|
await expect(placeholder).toHaveCount(1);
|
|
await expect(placeholder).toContainText("Type '/' for commands");
|
|
|
|
await type(page, '1');
|
|
await expect(placeholder).not.toBeVisible();
|
|
await pressBackspace(page);
|
|
|
|
await expect(placeholder).toBeVisible();
|
|
await updateBlockType(page, 'affine:paragraph', 'h1');
|
|
|
|
await expect(placeholder).toBeVisible();
|
|
await expect(placeholder).toHaveText('Heading 1');
|
|
await updateBlockType(page, 'affine:paragraph', 'text');
|
|
await focusRichText(page, 0);
|
|
await expect(placeholder).toBeVisible();
|
|
await expect(placeholder).toContainText("Type '/' for commands");
|
|
|
|
await pressEnter(page);
|
|
await expect(placeholder).toHaveCount(1);
|
|
});
|
|
|
|
test('should placeholder not show when multiple blocks are selected', async ({
|
|
page,
|
|
}) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await pressEnter(page);
|
|
await assertRichTexts(page, ['', '']);
|
|
const coord = await getIndexCoordinate(page, [0, 0]);
|
|
// blur
|
|
await page.mouse.click(0, 0);
|
|
await page.mouse.move(coord.x - 26 - 24, coord.y - 10, { steps: 20 });
|
|
await page.mouse.down();
|
|
// ←
|
|
await page.mouse.move(coord.x + 20, coord.y + 50, { steps: 20 });
|
|
await page.mouse.up();
|
|
const placeholder = page.locator('.affine-paragraph-placeholder.visible');
|
|
await expect(placeholder).toBeHidden();
|
|
});
|
|
|
|
test.describe('press ArrowDown when cursor is at the last line of a block', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await page.evaluate(() => {
|
|
const { doc } = window;
|
|
const rootId = doc.addBlock('affine:page', {
|
|
title: new doc.Text(),
|
|
});
|
|
const note = doc.addBlock('affine:note', {}, rootId);
|
|
doc.addBlock(
|
|
'affine:paragraph',
|
|
{
|
|
text: new doc.Text('This is the 2nd last block.'),
|
|
},
|
|
note
|
|
);
|
|
doc.addBlock(
|
|
'affine:paragraph',
|
|
{
|
|
text: new doc.Text('This is the last block.'),
|
|
},
|
|
note
|
|
);
|
|
});
|
|
});
|
|
|
|
test('move cursor to next block if this block is _not_ the last block in the page', async ({
|
|
page,
|
|
}) => {
|
|
// Click at the top-left corner of the 2nd last block to place the cursor at its start
|
|
await focusRichText(page, 0, { clickPosition: { x: 0, y: 0 } });
|
|
// Cursor should have been moved to the start of the last block.
|
|
await pressArrowDown(page);
|
|
await type(page, "I'm here. ");
|
|
await assertRichTexts(page, [
|
|
'This is the 2nd last block.',
|
|
"I'm here. This is the last block.",
|
|
]);
|
|
});
|
|
test('move cursor to the end of line if the block is the last block in the page', async ({
|
|
page,
|
|
}) => {
|
|
// Click at the top-left corner of the last block to place the cursor at its start
|
|
await focusRichText(page, 1, { clickPosition: { x: 0, y: 0 } });
|
|
// Cursor should have been moved to the end of the only line.
|
|
await pressArrowDown(page, 2);
|
|
await pressArrowRight(page);
|
|
await type(page, " I'm here.");
|
|
await assertRichTexts(page, [
|
|
'This is the 2nd last block.',
|
|
"This is the last block. I'm here.",
|
|
]);
|
|
});
|
|
});
|
|
|
|
test('delete empty text paragraph block should keep children blocks when following custom blocks', async ({
|
|
page,
|
|
}, testInfo) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await type(page, '--- ');
|
|
await assertDivider(page, 1);
|
|
|
|
// Click blank area to add a paragraph block after divider
|
|
await page.mouse.click(100, 200);
|
|
await page.waitForTimeout(200);
|
|
|
|
// Add to paragraph blocks
|
|
await initThreeParagraphs(page);
|
|
await assertRichTexts(page, ['123', '456', '789']);
|
|
|
|
// Indent the second paragraph block
|
|
await focusRichText(page, 2);
|
|
await pressTab(page);
|
|
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_init.json`
|
|
);
|
|
|
|
// Delete the parent paragraph block
|
|
await focusRichText(page, 1);
|
|
await pressBackspace(page, 4);
|
|
|
|
await assertRichTexts(page, ['123', '789']);
|
|
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_final.json`
|
|
);
|
|
});
|
|
|
|
test('delete first paragraph with children should keep children blocks', async ({
|
|
page,
|
|
}) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await type(page, '123');
|
|
await pressEnter(page);
|
|
await pressTab(page);
|
|
await waitNextFrame(page);
|
|
await type(page, '456');
|
|
await setSelection(page, 2, 1, 2, 1);
|
|
await pressBackspace(page, 2);
|
|
await waitNextFrame(page);
|
|
await assertTitle(page, '23');
|
|
await assertRichTexts(page, ['456']);
|
|
});
|
|
|
|
test('paragraph indent and delete in line start', async ({
|
|
page,
|
|
}, testInfo) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
await type(page, 'abc');
|
|
await pressEnter(page);
|
|
await pressTab(page);
|
|
await type(page, 'efg');
|
|
await pressEnter(page);
|
|
await pressTab(page);
|
|
await type(page, 'hij');
|
|
await pressEnter(page);
|
|
await pressShiftTab(page);
|
|
await type(page, 'klm');
|
|
await pressEnter(page);
|
|
await type(page, 'nop');
|
|
await setSelection(page, 3, 1, 3, 1);
|
|
// abc
|
|
// e|fg
|
|
// hij
|
|
// klm
|
|
// nop
|
|
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_init.json`
|
|
);
|
|
|
|
await pressBackspace(page, 2);
|
|
await setSelection(page, 5, 1, 5, 1);
|
|
// abcfg
|
|
// hij
|
|
// k|lm
|
|
// nop
|
|
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_after_press_backspace.json`
|
|
);
|
|
|
|
await pressBackspace(page, 2);
|
|
await setSelection(page, 6, 1, 6, 1);
|
|
// abcfg
|
|
// hijlm
|
|
// n|op
|
|
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_after_press_backspace_2.json`
|
|
);
|
|
|
|
await pressBackspace(page, 2);
|
|
// abcfg
|
|
// hijlm
|
|
// |op
|
|
|
|
expect(await getPageSnapshot(page, true)).toMatchSnapshot(
|
|
`${testInfo.title}_after_press_backspace_3.json`
|
|
);
|
|
});
|
|
|
|
test('delete at the start of paragraph (multiple notes)', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await page.evaluate(() => {
|
|
const { doc } = window;
|
|
|
|
const rootId = doc.addBlock('affine:page', {
|
|
title: new doc.Text(),
|
|
});
|
|
doc.addBlock('affine:surface', {}, rootId);
|
|
|
|
['123', '456'].forEach(text => {
|
|
const noteId = doc.addBlock('affine:note', {}, rootId);
|
|
doc.addBlock(
|
|
'affine:paragraph',
|
|
{
|
|
text: new doc.Text(text),
|
|
},
|
|
noteId
|
|
);
|
|
});
|
|
|
|
doc.resetHistory();
|
|
});
|
|
|
|
await assertBlockCount(page, 'note', 2);
|
|
|
|
await assertRichTexts(page, ['123', '456']);
|
|
await focusRichText(page, 1);
|
|
await pressArrowLeft(page, 3);
|
|
await pressBackspace(page);
|
|
await assertRichTexts(page, ['123456']);
|
|
});
|
|
|
|
test('arrow up/down navigation within and across paragraphs containing different types of text', async ({
|
|
page,
|
|
}) => {
|
|
test.info().annotations.push({
|
|
type: 'issue',
|
|
description: 'https://github.com/toeverything/blocksuite/issues/5155',
|
|
});
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
|
|
await type(page, 'a'.repeat(20));
|
|
await assertRichTextInlineRange(page, 0, 20, 0);
|
|
await type(page, '*');
|
|
await type(page, 'i'.repeat(5));
|
|
await type(page, '*');
|
|
await pressSpace(page);
|
|
await assertRichTextInlineRange(page, 0, 25, 0);
|
|
await type(page, 'a'.repeat(100));
|
|
await assertRichTextInlineRange(page, 0, 125, 0);
|
|
await pressEnter(page);
|
|
|
|
await type(page, 'a'.repeat(100));
|
|
await assertRichTextInlineRange(page, 1, 100, 0);
|
|
await type(page, '*');
|
|
await type(page, 'i'.repeat(5));
|
|
await type(page, '*');
|
|
await pressSpace(page);
|
|
await assertRichTextInlineRange(page, 1, 105, 0);
|
|
await type(page, 'a'.repeat(20));
|
|
await assertRichTextInlineRange(page, 1, 125, 0);
|
|
|
|
await pressArrowUp(page);
|
|
await assertRichTextInlineRange(page, 1, 32, 0);
|
|
await pressArrowUp(page);
|
|
await assertRichTextInlineRange(page, 0, 125, 0);
|
|
await pressArrowUp(page);
|
|
await assertRichTextInlineRange(page, 0, 35, 0);
|
|
await pressArrowUp(page);
|
|
await assertRichTextInlineRange(page, 0, 0, 0);
|
|
await pressArrowDown(page);
|
|
await assertRichTextInlineRange(page, 0, 125, 0);
|
|
await pressArrowDown(page);
|
|
await assertRichTextInlineRange(page, 1, 32, 0);
|
|
await pressArrowDown(page);
|
|
await assertRichTextInlineRange(page, 1, 125, 0);
|
|
});
|
|
|
|
test('select divider using delete keyboard from prev/next paragraph', async ({
|
|
page,
|
|
}) => {
|
|
test.info().annotations.push({
|
|
type: 'issue',
|
|
description: 'https://github.com/toeverything/blocksuite/issues/4547',
|
|
});
|
|
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
|
|
await initThreeDividers(page);
|
|
await assertDivider(page, 3);
|
|
await assertRichTexts(page, ['123', '123']);
|
|
|
|
await focusRichText(page, 0);
|
|
await pressForwardDelete(page);
|
|
await assertBlockSelections(page, ['4']);
|
|
await assertDivider(page, 3);
|
|
|
|
await focusRichText(page, 1);
|
|
await pressArrowLeft(page, 3);
|
|
await pressBackspace(page);
|
|
await assertBlockSelections(page, ['6']);
|
|
await assertDivider(page, 3);
|
|
|
|
await assertRichTexts(page, ['123', '123']);
|
|
});
|
|
|
|
test.describe('readonly mode', () => {
|
|
test('should placeholder not show at readonly mode', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
await initEmptyParagraphState(page);
|
|
await focusRichText(page);
|
|
|
|
await pressEnter(page);
|
|
await updateBlockType(page, 'affine:paragraph', 'h1');
|
|
|
|
const placeholder = page.locator('.affine-paragraph-placeholder.visible');
|
|
|
|
await switchReadonly(page);
|
|
await focusRichText(page, 0);
|
|
await expect(placeholder).toBeHidden();
|
|
|
|
await focusRichText(page, 1);
|
|
await expect(placeholder).toBeHidden();
|
|
});
|
|
|
|
test('should readonly mode not be able to modify text', async ({ page }) => {
|
|
await enterPlaygroundRoom(page);
|
|
const { paragraphId } = await initEmptyParagraphState(page);
|
|
|
|
await focusRichText(page);
|
|
await type(page, 'hello');
|
|
await switchReadonly(page);
|
|
|
|
await pressBackspace(page, 5);
|
|
await type(page, 'world');
|
|
await dragBetweenIndices(page, [0, 1], [0, 3]);
|
|
await page.keyboard.press(`${SHORT_KEY}+b`);
|
|
await assertStoreMatchJSX(
|
|
page,
|
|
`
|
|
<affine:paragraph
|
|
prop:collapsed={false}
|
|
prop:text="hello"
|
|
prop:type="text"
|
|
/>`,
|
|
paragraphId
|
|
);
|
|
|
|
await undoByKeyboard(page);
|
|
await assertStoreMatchJSX(
|
|
page,
|
|
`
|
|
<affine:paragraph
|
|
prop:collapsed={false}
|
|
prop:text="hello"
|
|
prop:type="text"
|
|
/>`,
|
|
paragraphId
|
|
);
|
|
});
|
|
});
|