mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-24 04:27:27 +08:00
test(editor): move blocksuite test to tests folder (#10917)
This commit is contained in:
@@ -0,0 +1,843 @@
|
||||
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];
|
||||
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']);
|
||||
});
|
||||
Reference in New Issue
Block a user