import { expect } from '@playwright/test'; import { addNote, switchEditorMode } from './utils/actions/edgeless.js'; import { pressArrowDown, pressArrowLeft, pressArrowRight, pressArrowUp, pressBackspace, pressEnter, pressEscape, pressShiftEnter, pressShiftTab, pressTab, redoByKeyboard, SHORT_KEY, type, undoByKeyboard, } from './utils/actions/keyboard.js'; import { captureHistory, enterPlaygroundRoom, focusRichText, getInlineSelectionText, getPageSnapshot, getSelectionRect, initEmptyEdgelessState, initEmptyParagraphState, insertThreeLevelLists, waitNextFrame, } from './utils/actions/misc.js'; import { assertAlmostEqual, assertBlockCount, assertRichTexts, } from './utils/asserts.js'; import { test } from './utils/playwright.js'; function formatWithTimezone(date: Date, timeZone: string = 'Asia/Tokyo') { const tokyoDate = date.toLocaleString('en-US', { timeZone }); const year = tokyoDate.split(',')[0].split('/')[2]; const month = tokyoDate.split(',')[0].split('/')[0].padStart(2, '0'); const day = tokyoDate.split(',')[0].split('/')[1].padStart(2, '0'); return `${year}-${month}-${day}`; } test.describe('slash menu should show and hide correctly', () => { test.beforeEach(async ({ page }) => { await enterPlaygroundRoom(page); }); test("slash menu should show when user input '/'", async ({ page }) => { await initEmptyParagraphState(page); const slashMenu = page.locator(`.slash-menu`); await focusRichText(page); await type(page, '/'); await expect(slashMenu).toBeVisible(); }); // Playwright dose not support IME // https://github.com/microsoft/playwright/issues/5777 test.skip("slash menu should show when user input '、'", async ({ page }) => { await initEmptyParagraphState(page); const slashMenu = page.locator(`.slash-menu`); await focusRichText(page); await type(page, '、'); await expect(slashMenu).toBeVisible(); }); test('slash menu should hide after click away', async ({ page, }, testInfo) => { await initEmptyParagraphState(page); const slashMenu = page.locator(`.slash-menu`); await focusRichText(page); await type(page, '/'); await expect(slashMenu).toBeVisible(); // Click outside should close slash menu await page.mouse.click(0, 50); await expect(slashMenu).toBeHidden(); expect(await getPageSnapshot(page, true)).toMatchSnapshot( `${testInfo.title}.json` ); }); test('slash menu should hide after input whitespace', async ({ page }) => { await initEmptyParagraphState(page); const slashMenu = page.locator(`.slash-menu`); await focusRichText(page); await type(page, '/'); await expect(slashMenu).toBeVisible(); await type(page, ' '); await expect(slashMenu).toBeHidden(); await assertRichTexts(page, ['/ ']); await pressBackspace(page); await expect(slashMenu).toBeVisible(); await type(page, 'head'); await expect(slashMenu).toBeVisible(); await type(page, ' '); await expect(slashMenu).toBeHidden(); await pressBackspace(page); await expect(slashMenu).toBeVisible(); }); test('delete the slash symbol should close the slash menu', async ({ page, }, testInfo) => { await initEmptyParagraphState(page); const slashMenu = page.locator(`.slash-menu`); await focusRichText(page); await type(page, '/'); await expect(slashMenu).toBeVisible(); await pressBackspace(page); await expect(slashMenu).toBeHidden(); expect(await getPageSnapshot(page, true)).toMatchSnapshot( `${testInfo.title}.json` ); }); test('typing something that does not match should close the slash menu', async ({ page, }) => { await initEmptyParagraphState(page); const slashMenu = page.locator(`.slash-menu`); await focusRichText(page); await type(page, '/'); await expect(slashMenu).toBeVisible(); await type(page, '_'); await expect(slashMenu).toBeHidden(); await assertRichTexts(page, ['/_']); // And pressing backspace immediately should reappear the slash menu await pressBackspace(page); await expect(slashMenu).toBeVisible(); await type(page, '__'); await pressBackspace(page); await expect(slashMenu).toBeHidden(); }); test('pressing the slash key again should close the old slash menu and open new one', async ({ page, }) => { await initEmptyParagraphState(page); const slashMenu = page.locator(`.slash-menu`); await focusRichText(page); await type(page, '/'); await expect(slashMenu).toBeVisible(); await type(page, '/'); await expect(slashMenu).toBeVisible(); await expect(slashMenu).toHaveCount(1); await assertRichTexts(page, ['//']); }); test('should position slash menu correctly', async ({ page }) => { await initEmptyParagraphState(page); const slashMenu = page.locator(`.slash-menu`); await focusRichText(page); await type(page, '/'); await expect(slashMenu).toBeVisible(); const box = await slashMenu.boundingBox(); if (!box) { throw new Error("slashMenu doesn't exist"); } const rect = await getSelectionRect(page); const { x, y } = box; assertAlmostEqual(x - rect.x, 0, 10); assertAlmostEqual(y - rect.bottom, 5, 10); }); test('should move up down with arrow key', async ({ page }) => { await initEmptyParagraphState(page); const slashMenu = page.locator(`.slash-menu`); await focusRichText(page); await type(page, '/'); await expect(slashMenu).toBeVisible(); const slashItems = slashMenu.locator('icon-button'); await pressArrowDown(page); await expect(slashMenu).toBeVisible(); await expect(slashItems.nth(1)).toHaveAttribute('hover', 'true'); await expect(slashItems.nth(1).locator('.text')).toHaveText(['Heading 1']); await assertRichTexts(page, ['/']); await pressArrowUp(page); await expect(slashMenu).toBeVisible(); await expect(slashItems.first()).toHaveAttribute('hover', 'true'); await expect(slashItems.first().locator('.text')).toHaveText(['Text']); await assertRichTexts(page, ['/']); await pressArrowUp(page); await expect(slashMenu).toBeVisible(); await expect(slashItems.last()).toHaveAttribute('hover', 'true'); await expect(slashItems.last().locator('.text')).toHaveText(['Delete']); await assertRichTexts(page, ['/']); await pressArrowDown(page); await expect(slashMenu).toBeVisible(); await expect(slashItems.first()).toHaveAttribute('hover', 'true'); await expect(slashItems.first().locator('.text')).toHaveText(['Text']); await assertRichTexts(page, ['/']); }); test('slash menu hover state', async ({ page }) => { await initEmptyParagraphState(page); const slashMenu = page.locator(`.slash-menu`); await focusRichText(page); await type(page, '/'); await expect(slashMenu).toBeVisible(); const slashItems = slashMenu.locator('icon-button'); await pressArrowDown(page); await expect(slashItems.nth(1)).toHaveAttribute('hover', 'true'); await pressArrowUp(page); await expect(slashItems.nth(1)).toHaveAttribute('hover', 'false'); await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true'); await pressArrowDown(page); await pressArrowDown(page); await expect(slashItems.nth(2)).toHaveAttribute('hover', 'true'); await expect(slashItems.nth(1)).toHaveAttribute('hover', 'false'); await expect(slashItems.nth(0)).toHaveAttribute('hover', 'false'); await slashItems.nth(0).hover(); await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true'); await expect(slashItems.nth(2)).toHaveAttribute('hover', 'false'); await expect(slashItems.nth(1)).toHaveAttribute('hover', 'false'); }); test('should open tooltip when hover on item', async ({ page }) => { await initEmptyParagraphState(page); await focusRichText(page); await type(page, '/'); const slashMenu = page.locator(`.slash-menu`); await expect(slashMenu).toBeVisible(); const slashItems = slashMenu.locator('icon-button'); const tooltip = page.locator('.affine-tooltip'); await slashItems.nth(0).hover(); await expect(tooltip).toBeVisible(); await expect(tooltip.locator('.tooltip-caption')).toHaveText(['Text']); await page.mouse.move(0, 0); await expect(tooltip).toBeHidden(); await slashItems.nth(1).hover(); await expect(tooltip).toBeVisible(); await expect(tooltip.locator('.tooltip-caption')).toHaveText([ 'Heading #1', ]); await page.mouse.move(0, 0); await expect(tooltip).toBeHidden(); await expect(slashItems.nth(4).locator('.text')).toHaveText([ 'Other Headings', ]); await slashItems.nth(4).hover(); await expect(tooltip).toBeHidden(); }); test('press tab should move up and down', async ({ page }) => { await initEmptyParagraphState(page); const slashMenu = page.locator(`.slash-menu`); await focusRichText(page); await type(page, '/'); await expect(slashMenu).toBeVisible(); const slashItems = slashMenu.locator('icon-button'); await pressTab(page); await expect(slashMenu).toBeVisible(); await expect(slashItems.nth(1)).toHaveAttribute('hover', 'true'); await expect(slashItems.nth(1).locator('.text')).toHaveText(['Heading 1']); await assertRichTexts(page, ['/']); await pressShiftTab(page); await expect(slashMenu).toBeVisible(); await expect(slashItems.first()).toHaveAttribute('hover', 'true'); await expect(slashItems.first().locator('.text')).toHaveText(['Text']); await assertRichTexts(page, ['/']); await pressShiftTab(page); await expect(slashMenu).toBeVisible(); await expect(slashItems.last()).toHaveAttribute('hover', 'true'); await expect(slashItems.last().locator('.text')).toHaveText(['Delete']); await assertRichTexts(page, ['/']); await pressTab(page); await expect(slashMenu).toBeVisible(); await expect(slashItems.first()).toHaveAttribute('hover', 'true'); await expect(slashItems.first().locator('.text')).toHaveText(['Text']); await assertRichTexts(page, ['/']); }); test('should move up down with ctrl/cmd+n and ctrl/cmd+p', async ({ page, }) => { await initEmptyParagraphState(page); const slashMenu = page.locator(`.slash-menu`); await focusRichText(page); await type(page, '/'); await expect(slashMenu).toBeVisible(); const slashItems = slashMenu.locator('icon-button'); await page.keyboard.press(`${SHORT_KEY}+n`); await expect(slashMenu).toBeVisible(); await expect(slashItems.nth(1)).toHaveAttribute('hover', 'true'); await expect(slashItems.nth(1).locator('.text')).toHaveText(['Heading 1']); await assertRichTexts(page, ['/']); await page.keyboard.press(`${SHORT_KEY}+p`); await expect(slashMenu).toBeVisible(); await expect(slashItems.first()).toHaveAttribute('hover', 'true'); await expect(slashItems.first().locator('.text')).toHaveText(['Text']); await assertRichTexts(page, ['/']); await page.keyboard.press(`${SHORT_KEY}+p`); await expect(slashMenu).toBeVisible(); await expect(slashItems.last()).toHaveAttribute('hover', 'true'); await expect(slashItems.last().locator('.text')).toHaveText(['Delete']); await assertRichTexts(page, ['/']); await page.keyboard.press(`${SHORT_KEY}+n`); await expect(slashMenu).toBeVisible(); await expect(slashItems.first()).toHaveAttribute('hover', 'true'); await expect(slashItems.first().locator('.text')).toHaveText(['Text']); await assertRichTexts(page, ['/']); }); test('should open sub menu when hover on SubMenuItem', async ({ page }) => { await initEmptyParagraphState(page); await focusRichText(page); await type(page, '/'); const slashMenu = page.locator('.slash-menu[data-testid=sub-menu-0]'); await expect(slashMenu).toBeVisible(); const slashItems = slashMenu.locator('icon-button'); const subMenu = page.locator('.slash-menu[data-testid=sub-menu-1]'); let rect = await slashItems.nth(4).boundingBox(); if (!rect) { throw new Error('rect is not found'); } await page.mouse.move(rect.x + 10, rect.y + 10); await expect(slashMenu).toBeVisible(); await expect(slashItems.nth(4)).toHaveAttribute('hover', 'true'); await expect(slashItems.nth(4).locator('.text')).toHaveText([ 'Other Headings', ]); await expect(subMenu).toBeVisible(); rect = await slashItems.nth(3).boundingBox(); if (!rect) { throw new Error('rect is not found'); } await page.mouse.move(rect.x + 10, rect.y + 10); await expect(slashMenu).toBeVisible(); await expect(slashItems.nth(3)).toHaveAttribute('hover', 'true'); await expect(slashItems.nth(3).locator('.text')).toHaveText(['Heading 3']); await expect(subMenu).toBeHidden(); }); test('navigate menu with left right arrow and Enter', async ({ page }) => { await initEmptyParagraphState(page); await focusRichText(page); const slashMenu = page.locator('.slash-menu[data-testid=sub-menu-0]'); await type(page, '/'); await expect(slashMenu).toBeVisible(); await pressEscape(page); await expect(slashMenu).toBeHidden(); await type(page, '/'); await expect(slashMenu).toBeVisible(); await pressArrowLeft(page); await expect( slashMenu, 'root menu should be visible when press arrow left' ).toBeVisible(); await pressEscape(page); // Test sub menu case const slashItems = slashMenu.locator('icon-button'); await type(page, '/'); await slashMenu.waitFor({ state: 'visible' }); await pressArrowDown(page, 4); await expect(slashItems.nth(4)).toHaveAttribute('hover', 'true'); await expect(slashItems.nth(4).locator('.text')).toHaveText([ 'Other Headings', ]); const subMenu = page.locator('.slash-menu[data-testid=sub-menu-1]'); await pressArrowRight(page); await expect(slashMenu).toBeVisible(); await expect(subMenu).toBeVisible(); await pressArrowLeft(page); await expect(slashMenu).toBeVisible(); await expect(subMenu).toBeHidden(); await pressEnter(page); await expect(slashMenu).toBeVisible(); await expect(subMenu).toBeVisible(); }); test('menu should be hidden when press escape', async ({ page }) => { await initEmptyParagraphState(page); await focusRichText(page); const slashMenu = page.locator('.slash-menu[data-testid=sub-menu-0]'); await type(page, '/'); await expect(slashMenu).toBeVisible(); await pressEscape(page); await expect(slashMenu).toBeHidden(); // go to sub menu await type(page, '/'); await expect(slashMenu).toBeVisible(); await pressArrowDown(page, 4); await pressArrowRight(page); await expect(slashMenu).toBeVisible(); const subMenu = page.locator('.slash-menu[data-testid=sub-menu-1]'); await expect(subMenu).toBeVisible(); await pressEscape(page); await expect(slashMenu).toBeHidden(); await expect(subMenu).toBeHidden(); }); test('show close current all submenu when typing', async ({ page }) => { await initEmptyParagraphState(page); await focusRichText(page); const slashMenu = page.locator('.slash-menu[data-testid=sub-menu-0]'); const subMenu = page.locator('.slash-menu[data-testid=sub-menu-1]'); const slashItems = slashMenu.locator('icon-button'); await type(page, '/'); await expect(slashMenu).toBeVisible(); await pressArrowDown(page, 4); await expect(slashItems.nth(4)).toHaveAttribute('hover', 'true'); await expect(slashItems.nth(4).locator('.text')).toHaveText([ 'Other Headings', ]); await pressEnter(page); await expect(subMenu).toBeVisible(); await type(page, 'h'); await expect(subMenu).toBeHidden(); }); test('should allow only pressing modifier key', async ({ page }) => { await initEmptyParagraphState(page); await focusRichText(page); const slashMenu = page.locator(`.slash-menu`); await type(page, '/'); await expect(slashMenu).toBeVisible(); await page.keyboard.press(SHORT_KEY); await expect(slashMenu).toBeVisible(); await page.keyboard.press('Shift'); await expect(slashMenu).toBeVisible(); }); test('should allow other hotkey to passthrough', async ({ page }) => { await initEmptyParagraphState(page); await focusRichText(page); await type(page, 'hello'); await pressEnter(page); await type(page, 'world'); const slashMenu = page.locator(`.slash-menu`); await type(page, '/'); await expect(slashMenu).toBeVisible(); await page.keyboard.press(`${SHORT_KEY}+a`); await expect(slashMenu).toBeHidden(); await assertRichTexts(page, ['hello', 'world/']); const selected = await getInlineSelectionText(page); expect(selected).toBe('world/'); }); test('can input search input after click menu', async ({ page }) => { await initEmptyParagraphState(page); const slashMenu = page.locator(`.slash-menu`); await focusRichText(page); await type(page, '/'); await expect(slashMenu).toBeVisible(); const box = await slashMenu.boundingBox(); if (!box) { throw new Error("slashMenu doesn't exist"); } const { x, y } = box; await page.mouse.click(x + 10, y + 10); await expect(slashMenu).toBeVisible(); await type(page, 'a'); await assertRichTexts(page, ['/a']); }); }); test.describe('slash menu should not be shown in ignored blocks', () => { test('code block', async ({ page }) => { await enterPlaygroundRoom(page); await initEmptyParagraphState(page); await focusRichText(page); await type(page, '```'); await pressEnter(page); await type(page, '/'); await expect(page.locator('.slash-menu')).toBeHidden(); }); }); test('should slash menu works with fast type', async ({ page }) => { await enterPlaygroundRoom(page); await initEmptyParagraphState(page); await focusRichText(page); await type(page, 'a/text', 0); const slashMenu = page.locator(`.slash-menu`); await expect(slashMenu).toBeVisible(); }); test('should clean slash string after soft enter', async ({ page, }, testInfo) => { test.info().annotations.push({ type: 'issue', description: 'https://github.com/toeverything/blocksuite/issues/1126', }); await enterPlaygroundRoom(page); await initEmptyParagraphState(page); await focusRichText(page); await type(page, 'hello'); await pressShiftEnter(page); await waitNextFrame(page); await type(page, '/copy'); await pressEnter(page); expect(await getPageSnapshot(page, true)).toMatchSnapshot( `${testInfo.title}.json` ); }); test.describe('slash search', () => { test('should slash menu search and keyboard works', async ({ page }) => { await enterPlaygroundRoom(page); await initEmptyParagraphState(page); await focusRichText(page); const slashMenu = page.locator(`.slash-menu`); const slashItems = slashMenu.locator('icon-button'); await type(page, '/'); await expect(slashMenu).toBeVisible(); // search should active the first item await type(page, 'co'); await expect(slashItems).toHaveCount(3); await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']); await expect(slashItems.nth(1).locator('.text')).toHaveText(['Code Block']); await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true'); await type(page, 'p'); await expect(slashItems).toHaveCount(1); await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']); // assert backspace works await pressBackspace(page); await expect(slashItems).toHaveCount(3); await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']); await expect(slashItems.nth(1).locator('.text')).toHaveText(['Code Block']); await expect(slashItems.nth(0)).toHaveAttribute('hover', 'true'); }); test('slash menu supports fuzzy search', async ({ page }) => { await enterPlaygroundRoom(page); await initEmptyParagraphState(page); await focusRichText(page); const slashMenu = page.locator(`.slash-menu`); const slashItems = slashMenu.locator('icon-button'); await type(page, '/'); await expect(slashMenu).toBeVisible(); await type(page, 'c'); await expect(slashItems).toHaveCount(8); await expect(slashItems.nth(0).locator('.text')).toHaveText(['Copy']); await expect(slashItems.nth(1).locator('.text')).toHaveText(['Italic']); await expect(slashItems.nth(2).locator('.text')).toHaveText(['New Doc']); await expect(slashItems.nth(3).locator('.text')).toHaveText(['Duplicate']); await expect(slashItems.nth(4).locator('.text')).toHaveText(['Code Block']); await expect(slashItems.nth(5).locator('.text')).toHaveText(['Linked Doc']); await expect(slashItems.nth(6).locator('.text')).toHaveText(['Attachment']); await type(page, 'b'); await expect(slashItems.nth(0).locator('.text')).toHaveText(['Code Block']); }); test('slash menu supports alias search', async ({ page }) => { await enterPlaygroundRoom(page); await initEmptyParagraphState(page); await focusRichText(page); await type(page, '/'); const slashMenu = page.locator(`.slash-menu`); await expect(slashMenu).toBeVisible(); const slashItems = slashMenu.locator('icon-button'); await type(page, 'database'); await expect(slashItems).toHaveCount(2); await expect(slashItems.nth(0).locator('.text')).toHaveText(['Table View']); await expect(slashItems.nth(1).locator('.text')).toHaveText([ 'Kanban View', ]); await type(page, 'v'); await expect(slashItems).toHaveCount(0); }); }); test('should focus on code blocks created by the slash menu', async ({ page, }) => { await enterPlaygroundRoom(page); await initEmptyParagraphState(page); await focusRichText(page); await type(page, '000'); await type(page, '/code'); const slashMenu = page.locator(`.slash-menu`); await expect(slashMenu).toBeVisible(); const codeBlock = page.getByTestId('Code Block'); await codeBlock.click(); await expect(slashMenu).toBeHidden(); await focusRichText(page); // FIXME: flaky selection asserter await type(page, '111'); await assertRichTexts(page, ['000111']); }); // Selection is not yet available in edgeless test('slash menu should work in edgeless mode', async ({ page }) => { await enterPlaygroundRoom(page); await initEmptyEdgelessState(page); await switchEditorMode(page); await addNote(page, '/', 30, 40); await assertRichTexts(page, ['', '/']); const slashMenu = page.locator(`.slash-menu`); await expect(slashMenu).toBeVisible(); }); test.describe('slash menu with date & time', () => { test("should insert Today's time string", async ({ page }) => { await enterPlaygroundRoom(page); await initEmptyParagraphState(page); await focusRichText(page); await type(page, '/'); const slashMenu = page.locator(`.slash-menu`); await expect(slashMenu).toBeVisible(); const todayBlock = page.getByTestId('Today'); await todayBlock.click(); await expect(slashMenu).toBeHidden(); const date = new Date(); const formattedDate = formatWithTimezone(date); await assertRichTexts(page, [formattedDate]); }); test("should create Tomorrow's time string", async ({ page }) => { await enterPlaygroundRoom(page); await initEmptyParagraphState(page); await focusRichText(page); await type(page, '/'); const slashMenu = page.locator(`.slash-menu`); await expect(slashMenu).toBeVisible(); const todayBlock = page.getByTestId('Tomorrow'); await todayBlock.click(); await expect(slashMenu).toBeHidden(); const date = new Date(); date.setDate(date.getDate() + 1); const formattedDate = formatWithTimezone(date); await assertRichTexts(page, [formattedDate]); }); test("should insert Yesterday's time string", async ({ page }) => { await enterPlaygroundRoom(page); await initEmptyParagraphState(page); await focusRichText(page); await type(page, '/'); const slashMenu = page.locator(`.slash-menu`); await expect(slashMenu).toBeVisible(); const todayBlock = page.getByTestId('Yesterday'); await todayBlock.click(); await expect(slashMenu).toBeHidden(); const date = new Date(); date.setDate(date.getDate() - 1); const formattedDate = formatWithTimezone(date); await assertRichTexts(page, [formattedDate]); }); }); test.describe('slash menu with style', () => { test('should style text line works', async ({ page }, testInfo) => { await enterPlaygroundRoom(page); await initEmptyParagraphState(page); await focusRichText(page); await type(page, 'hello/'); const slashMenu = page.locator(`.slash-menu`); await expect(slashMenu).toBeVisible(); const bold = page.getByTestId('Bold'); await bold.click(); expect(await getPageSnapshot(page, true)).toMatchSnapshot( `${testInfo.title}.json` ); }); test('should style empty line works', async ({ page }, testInfo) => { await enterPlaygroundRoom(page); await initEmptyParagraphState(page); await focusRichText(page); await type(page, '/'); const slashMenu = page.locator(`.slash-menu`); await expect(slashMenu).toBeVisible(); const bold = page.getByTestId('Bold'); await bold.click(); await page.waitForTimeout(50); await type(page, 'hello'); expect(await getPageSnapshot(page, true)).toMatchSnapshot( `${testInfo.title}.json` ); }); }); test('should insert database', async ({ page }) => { await enterPlaygroundRoom(page); await initEmptyParagraphState(page); await focusRichText(page); await assertBlockCount(page, 'paragraph', 1); await type(page, '/'); const tableBlock = page.getByTestId('Table View'); await tableBlock.click(); await assertBlockCount(page, 'paragraph', 0); await assertBlockCount(page, 'database', 1); const database = page.locator('affine-database'); await expect(database).toBeVisible(); const titleColumn = page.locator('.affine-database-column').nth(0); expect(await titleColumn.innerText()).toBe('Title'); const defaultRows = page.locator('.affine-database-block-row'); expect(await defaultRows.count()).toBe(3); }); test('move block up and down by slash menu', async ({ page }) => { await enterPlaygroundRoom(page); await initEmptyParagraphState(page); const slashMenu = page.locator(`.slash-menu`); await focusRichText(page); await type(page, 'hello'); await pressEnter(page); await type(page, 'world'); await assertRichTexts(page, ['hello', 'world']); await type(page, '/'); await expect(slashMenu).toBeVisible(); const moveUp = page.getByTestId('Move Up'); await moveUp.click(); await assertRichTexts(page, ['world', 'hello']); await type(page, '/'); await expect(slashMenu).toBeVisible(); const moveDown = page.getByTestId('Move Down'); await moveDown.click(); await assertRichTexts(page, ['hello', 'world']); }); test('delete block by slash menu should remove children', async ({ page, }, testInfo) => { await enterPlaygroundRoom(page); await initEmptyParagraphState(page); await insertThreeLevelLists(page); const slashMenu = page.locator(`.slash-menu`); const slashItems = slashMenu.locator('icon-button'); await captureHistory(page); await focusRichText(page, 1); await waitNextFrame(page); await type(page, '/'); await expect(slashMenu).toBeVisible(); await type(page, 'remove'); await expect(slashItems).toHaveCount(1); await pressEnter(page); expect(await getPageSnapshot(page, true)).toMatchSnapshot( `${testInfo.title}.json` ); await undoByKeyboard(page); await assertRichTexts(page, ['123', '456', '789']); await redoByKeyboard(page); await assertRichTexts(page, ['123']); });