mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-03 02:30:36 +08:00
b9f2650369
### What's Changed! * adds theme type: `ThemeSchema` * adds default theme: `DefaultTheme` * stores real color values
1014 lines
25 KiB
TypeScript
1014 lines
25 KiB
TypeScript
import {
|
|
assertSelection,
|
|
enterInlineEditorPlayground,
|
|
focusInlineRichText,
|
|
getDeltaFromInlineRichText,
|
|
getInlineRangeIndexRect,
|
|
getInlineRichTextLine,
|
|
press,
|
|
setInlineRichTextRange,
|
|
type,
|
|
} from '@inline/__tests__/utils.js';
|
|
import { ZERO_WIDTH_SPACE } from '@inline/consts.js';
|
|
import type { InlineEditor } from '@inline/index.js';
|
|
import { expect, test } from '@playwright/test';
|
|
|
|
test('basic input', async ({ page }) => {
|
|
await enterInlineEditorPlayground(page);
|
|
await focusInlineRichText(page);
|
|
|
|
const editorA = page.locator('[data-v-root="true"]').nth(0);
|
|
const editorB = page.locator('[data-v-root="true"]').nth(1);
|
|
|
|
const editorAUndo = page.getByText('undo').nth(0);
|
|
const editorARedo = page.getByText('redo').nth(0);
|
|
|
|
expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
|
|
await page.waitForTimeout(100);
|
|
|
|
await type(page, 'abcd😃efg👨👨👧👦hj');
|
|
|
|
expect(await editorA.innerText()).toBe('abcd😃efg👨👨👧👦hj');
|
|
expect(await editorB.innerText()).toBe('abcd😃efg👨👨👧👦hj');
|
|
|
|
await editorAUndo.click();
|
|
|
|
expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
|
|
await editorARedo.click();
|
|
|
|
expect(await editorA.innerText()).toBe('abcd😃efg👨👨👧👦hj');
|
|
expect(await editorB.innerText()).toBe('abcd😃efg👨👨👧👦hj');
|
|
|
|
await focusInlineRichText(page);
|
|
await press(page, 'Backspace');
|
|
await press(page, 'Backspace');
|
|
await press(page, 'Backspace');
|
|
|
|
expect(await editorA.innerText()).toBe('abcd😃efg');
|
|
expect(await editorB.innerText()).toBe('abcd😃efg');
|
|
|
|
await editorAUndo.click();
|
|
|
|
expect(await editorA.innerText()).toBe('abcd😃efg👨👨👧👦hj');
|
|
expect(await editorB.innerText()).toBe('abcd😃efg👨👨👧👦hj');
|
|
|
|
await editorARedo.click();
|
|
|
|
expect(await editorA.innerText()).toBe('abcd😃efg');
|
|
expect(await editorB.innerText()).toBe('abcd😃efg');
|
|
|
|
await focusInlineRichText(page);
|
|
await press(page, 'ArrowLeft');
|
|
await press(page, 'ArrowLeft');
|
|
await press(page, 'ArrowLeft');
|
|
await press(page, 'ArrowLeft');
|
|
await press(page, 'ArrowLeft');
|
|
await press(page, 'Delete');
|
|
await press(page, 'Delete');
|
|
|
|
await type(page, '🥰👨👨👧👦');
|
|
expect(await editorA.innerText()).toBe('abc🥰👨👨👧👦efg');
|
|
expect(await editorB.innerText()).toBe('abc🥰👨👨👧👦efg');
|
|
|
|
await setInlineRichTextRange(page, {
|
|
index: 3,
|
|
length: 16,
|
|
});
|
|
await page.waitForTimeout(100);
|
|
await press(page, 'Delete');
|
|
|
|
expect(await editorA.innerText()).toBe('abc');
|
|
expect(await editorA.innerText()).toBe('abc');
|
|
|
|
await editorAUndo.click();
|
|
|
|
expect(await editorA.innerText()).toBe('abcd😃efg');
|
|
expect(await editorB.innerText()).toBe('abcd😃efg');
|
|
|
|
await editorARedo.click();
|
|
|
|
expect(await editorA.innerText()).toBe('abc');
|
|
expect(await editorB.innerText()).toBe('abc');
|
|
|
|
await focusInlineRichText(page);
|
|
await page.waitForTimeout(100);
|
|
await press(page, 'Enter');
|
|
await press(page, 'Enter');
|
|
await type(page, 'bbb');
|
|
|
|
await page.waitForTimeout(100);
|
|
|
|
expect(await editorA.innerText()).toBe('abc\n' + ZERO_WIDTH_SPACE + '\nbbb');
|
|
expect(await editorB.innerText()).toBe('abc\n' + ZERO_WIDTH_SPACE + '\nbbb');
|
|
|
|
await editorAUndo.click();
|
|
|
|
expect(await editorA.innerText()).toBe('abc');
|
|
expect(await editorB.innerText()).toBe('abc');
|
|
|
|
await editorARedo.click();
|
|
|
|
expect(await editorA.innerText()).toBe('abc\n' + ZERO_WIDTH_SPACE + '\nbbb');
|
|
expect(await editorB.innerText()).toBe('abc\n' + ZERO_WIDTH_SPACE + '\nbbb');
|
|
|
|
await focusInlineRichText(page);
|
|
await page.waitForTimeout(100);
|
|
await press(page, 'Backspace');
|
|
await press(page, 'Backspace');
|
|
await press(page, 'Backspace');
|
|
await press(page, 'Backspace');
|
|
await press(page, 'Backspace');
|
|
|
|
expect(await editorA.innerText()).toBe('abc');
|
|
expect(await editorB.innerText()).toBe('abc');
|
|
|
|
await editorAUndo.click();
|
|
|
|
expect(await editorA.innerText()).toBe('abc\n' + ZERO_WIDTH_SPACE + '\nbbb');
|
|
expect(await editorB.innerText()).toBe('abc\n' + ZERO_WIDTH_SPACE + '\nbbb');
|
|
|
|
await editorARedo.click();
|
|
|
|
expect(await editorA.innerText()).toBe('abc');
|
|
|
|
await focusInlineRichText(page);
|
|
await page.waitForTimeout(100);
|
|
await press(page, 'ArrowLeft');
|
|
await press(page, 'ArrowLeft');
|
|
await type(page, 'bb');
|
|
await press(page, 'ArrowRight');
|
|
await press(page, 'ArrowRight');
|
|
await type(page, 'dd');
|
|
|
|
expect(await editorA.innerText()).toBe('abbbcdd');
|
|
expect(await editorB.innerText()).toBe('abbbcdd');
|
|
|
|
await editorAUndo.click();
|
|
|
|
expect(await editorA.innerText()).toBe('abc');
|
|
|
|
await editorARedo.click();
|
|
|
|
expect(await editorA.innerText()).toBe('abbbcdd');
|
|
expect(await editorB.innerText()).toBe('abbbcdd');
|
|
|
|
await focusInlineRichText(page);
|
|
await page.waitForTimeout(100);
|
|
await press(page, 'ArrowLeft');
|
|
await press(page, 'ArrowLeft');
|
|
await press(page, 'Enter');
|
|
await press(page, 'Enter');
|
|
|
|
expect(await editorA.innerText()).toBe('abbbc\n' + ZERO_WIDTH_SPACE + '\ndd');
|
|
expect(await editorB.innerText()).toBe('abbbc\n' + ZERO_WIDTH_SPACE + '\ndd');
|
|
|
|
await editorAUndo.click();
|
|
|
|
expect(await editorA.innerText()).toBe('abbbcdd');
|
|
expect(await editorB.innerText()).toBe('abbbcdd');
|
|
|
|
await editorARedo.click();
|
|
|
|
expect(await editorA.innerText()).toBe('abbbc\n' + ZERO_WIDTH_SPACE + '\ndd');
|
|
expect(await editorB.innerText()).toBe('abbbc\n' + ZERO_WIDTH_SPACE + '\ndd');
|
|
});
|
|
|
|
test('chinese input', async ({ page }) => {
|
|
await enterInlineEditorPlayground(page);
|
|
await focusInlineRichText(page);
|
|
|
|
const editorA = page.locator('[data-v-root="true"]').nth(0);
|
|
const editorB = page.locator('[data-v-root="true"]').nth(1);
|
|
|
|
expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
|
|
await page.waitForTimeout(100);
|
|
const client = await page.context().newCDPSession(page);
|
|
await client.send('Input.imeSetComposition', {
|
|
selectionStart: 0,
|
|
selectionEnd: 0,
|
|
text: 'n',
|
|
});
|
|
await client.send('Input.imeSetComposition', {
|
|
selectionStart: 0,
|
|
selectionEnd: 1,
|
|
text: 'ni',
|
|
});
|
|
await client.send('Input.insertText', {
|
|
text: '你',
|
|
});
|
|
expect(await editorA.innerText()).toBe('你');
|
|
expect(await editorB.innerText()).toBe('你');
|
|
});
|
|
|
|
test('type many times in one moment', async ({ page }) => {
|
|
await enterInlineEditorPlayground(page);
|
|
await focusInlineRichText(page);
|
|
await page.waitForTimeout(100);
|
|
await Promise.all(
|
|
'aaaaaaaaaaaaaaaaaaaa'.split('').map(s => page.keyboard.type(s))
|
|
);
|
|
const preOffset = await page.evaluate(() => {
|
|
return getSelection()?.getRangeAt(0).endOffset;
|
|
});
|
|
await page.keyboard.press('ArrowRight');
|
|
await page.keyboard.press('ArrowRight');
|
|
await page.keyboard.press('ArrowRight');
|
|
const offset = await page.evaluate(() => {
|
|
return getSelection()?.getRangeAt(0).endOffset;
|
|
});
|
|
expect(preOffset).toBe(offset);
|
|
});
|
|
|
|
test('readonly mode', async ({ page }) => {
|
|
await enterInlineEditorPlayground(page);
|
|
await focusInlineRichText(page);
|
|
|
|
const editorA = page.locator('[data-v-root="true"]').nth(0);
|
|
const editorB = page.locator('[data-v-root="true"]').nth(1);
|
|
|
|
expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
|
|
await page.waitForTimeout(100);
|
|
|
|
await type(page, 'abcdefg');
|
|
|
|
expect(await editorA.innerText()).toBe('abcdefg');
|
|
expect(await editorB.innerText()).toBe('abcdefg');
|
|
|
|
await page.evaluate(() => {
|
|
const richTextA = document
|
|
.querySelector('test-page')
|
|
?.querySelector('test-rich-text');
|
|
|
|
if (!richTextA) {
|
|
throw new Error('Cannot find editor');
|
|
}
|
|
|
|
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
|
|
(richTextA as any).inlineEditor.setReadonly(true);
|
|
});
|
|
|
|
await type(page, 'aaaa');
|
|
|
|
expect(await editorA.innerText()).toBe('abcdefg');
|
|
expect(await editorB.innerText()).toBe('abcdefg');
|
|
});
|
|
|
|
test('basic styles', async ({ page }) => {
|
|
await enterInlineEditorPlayground(page);
|
|
await focusInlineRichText(page);
|
|
|
|
const editorA = page.locator('[data-v-root="true"]').nth(0);
|
|
const editorB = page.locator('[data-v-root="true"]').nth(1);
|
|
|
|
const editorABold = page.getByText('bold').nth(0);
|
|
const editorAItalic = page.getByText('italic').nth(0);
|
|
const editorAUnderline = page.getByText('underline').nth(0);
|
|
const editorAStrike = page.getByText('strike').nth(0);
|
|
const editorACode = page.getByText('code').nth(0);
|
|
|
|
const editorAUndo = page.getByText('undo').nth(0);
|
|
const editorARedo = page.getByText('redo').nth(0);
|
|
|
|
expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
|
|
await page.waitForTimeout(100);
|
|
|
|
await type(page, 'abcdefg');
|
|
|
|
expect(await editorA.innerText()).toBe('abcdefg');
|
|
expect(await editorB.innerText()).toBe('abcdefg');
|
|
|
|
let delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'abcdefg',
|
|
},
|
|
]);
|
|
|
|
await setInlineRichTextRange(page, { index: 2, length: 3 });
|
|
|
|
await editorABold.click();
|
|
await page.waitForTimeout(100);
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'ab',
|
|
},
|
|
{
|
|
insert: 'cde',
|
|
attributes: {
|
|
bold: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'fg',
|
|
},
|
|
]);
|
|
|
|
await editorAItalic.click();
|
|
await page.waitForTimeout(100);
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'ab',
|
|
},
|
|
{
|
|
insert: 'cde',
|
|
attributes: {
|
|
bold: true,
|
|
italic: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'fg',
|
|
},
|
|
]);
|
|
|
|
await editorAUnderline.click();
|
|
await page.waitForTimeout(100);
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'ab',
|
|
},
|
|
{
|
|
insert: 'cde',
|
|
attributes: {
|
|
bold: true,
|
|
italic: true,
|
|
underline: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'fg',
|
|
},
|
|
]);
|
|
|
|
await editorAStrike.click();
|
|
await page.waitForTimeout(100);
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'ab',
|
|
},
|
|
{
|
|
insert: 'cde',
|
|
attributes: {
|
|
bold: true,
|
|
italic: true,
|
|
underline: true,
|
|
strike: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'fg',
|
|
},
|
|
]);
|
|
|
|
await editorACode.click();
|
|
await page.waitForTimeout(100);
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'ab',
|
|
},
|
|
{
|
|
insert: 'cde',
|
|
attributes: {
|
|
bold: true,
|
|
italic: true,
|
|
underline: true,
|
|
strike: true,
|
|
code: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'fg',
|
|
},
|
|
]);
|
|
|
|
await editorAUndo.click({
|
|
clickCount: 5,
|
|
});
|
|
await page.waitForTimeout(100);
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'abcdefg',
|
|
},
|
|
]);
|
|
|
|
await editorARedo.click({
|
|
clickCount: 5,
|
|
});
|
|
await page.waitForTimeout(100);
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'ab',
|
|
},
|
|
{
|
|
insert: 'cde',
|
|
attributes: {
|
|
bold: true,
|
|
italic: true,
|
|
underline: true,
|
|
strike: true,
|
|
code: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'fg',
|
|
},
|
|
]);
|
|
|
|
await editorABold.click();
|
|
await page.waitForTimeout(100);
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'ab',
|
|
},
|
|
{
|
|
insert: 'cde',
|
|
attributes: {
|
|
italic: true,
|
|
underline: true,
|
|
strike: true,
|
|
code: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'fg',
|
|
},
|
|
]);
|
|
|
|
await editorAItalic.click();
|
|
await page.waitForTimeout(100);
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'ab',
|
|
},
|
|
{
|
|
insert: 'cde',
|
|
attributes: {
|
|
underline: true,
|
|
strike: true,
|
|
code: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'fg',
|
|
},
|
|
]);
|
|
|
|
await editorAUnderline.click();
|
|
await page.waitForTimeout(100);
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'ab',
|
|
},
|
|
{
|
|
insert: 'cde',
|
|
attributes: {
|
|
strike: true,
|
|
code: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'fg',
|
|
},
|
|
]);
|
|
|
|
await editorAStrike.click();
|
|
await page.waitForTimeout(100);
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'ab',
|
|
},
|
|
{
|
|
insert: 'cde',
|
|
attributes: {
|
|
code: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'fg',
|
|
},
|
|
]);
|
|
|
|
await editorACode.click();
|
|
await page.waitForTimeout(100);
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'abcdefg',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('overlapping styles', async ({ page }) => {
|
|
await enterInlineEditorPlayground(page);
|
|
await focusInlineRichText(page);
|
|
|
|
const editorA = page.locator('[data-v-root="true"]').nth(0);
|
|
const editorB = page.locator('[data-v-root="true"]').nth(1);
|
|
|
|
const editorABold = page.getByText('bold').nth(0);
|
|
const editorAItalic = page.getByText('italic').nth(0);
|
|
|
|
const editorAUndo = page.getByText('undo').nth(0);
|
|
const editorARedo = page.getByText('redo').nth(0);
|
|
|
|
expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
|
|
await page.waitForTimeout(100);
|
|
|
|
await type(page, 'abcdefghijk');
|
|
|
|
expect(await editorA.innerText()).toBe('abcdefghijk');
|
|
expect(await editorB.innerText()).toBe('abcdefghijk');
|
|
|
|
let delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'abcdefghijk',
|
|
},
|
|
]);
|
|
|
|
await setInlineRichTextRange(page, { index: 1, length: 3 });
|
|
await editorABold.click();
|
|
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'a',
|
|
},
|
|
{
|
|
insert: 'bcd',
|
|
attributes: {
|
|
bold: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'efghijk',
|
|
},
|
|
]);
|
|
|
|
await setInlineRichTextRange(page, { index: 7, length: 3 });
|
|
await editorABold.click();
|
|
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'a',
|
|
},
|
|
{
|
|
insert: 'bcd',
|
|
attributes: {
|
|
bold: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'efg',
|
|
},
|
|
{
|
|
insert: 'hij',
|
|
attributes: {
|
|
bold: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'k',
|
|
},
|
|
]);
|
|
|
|
await setInlineRichTextRange(page, { index: 3, length: 5 });
|
|
await editorAItalic.click();
|
|
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'a',
|
|
},
|
|
{
|
|
insert: 'bc',
|
|
attributes: {
|
|
bold: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'd',
|
|
attributes: {
|
|
bold: true,
|
|
italic: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'efg',
|
|
attributes: {
|
|
italic: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'h',
|
|
attributes: {
|
|
bold: true,
|
|
italic: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'ij',
|
|
attributes: {
|
|
bold: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'k',
|
|
},
|
|
]);
|
|
|
|
await editorAUndo.click({
|
|
clickCount: 3,
|
|
});
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'abcdefghijk',
|
|
},
|
|
]);
|
|
|
|
await editorARedo.click({
|
|
clickCount: 3,
|
|
});
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'a',
|
|
},
|
|
{
|
|
insert: 'bc',
|
|
attributes: {
|
|
bold: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'd',
|
|
attributes: {
|
|
bold: true,
|
|
italic: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'efg',
|
|
attributes: {
|
|
italic: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'h',
|
|
attributes: {
|
|
bold: true,
|
|
italic: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'ij',
|
|
attributes: {
|
|
bold: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'k',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('input continuous spaces', async ({ page }) => {
|
|
await enterInlineEditorPlayground(page);
|
|
await focusInlineRichText(page);
|
|
|
|
const editorA = page.locator('[data-v-root="true"]').nth(0);
|
|
const editorB = page.locator('[data-v-root="true"]').nth(1);
|
|
|
|
expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
|
|
await page.waitForTimeout(100);
|
|
|
|
await type(page, 'abc def');
|
|
|
|
expect(await editorA.innerText()).toBe('abc def');
|
|
expect(await editorB.innerText()).toBe('abc def');
|
|
|
|
await focusInlineRichText(page);
|
|
await page.waitForTimeout(100);
|
|
await press(page, 'ArrowLeft');
|
|
await press(page, 'ArrowLeft');
|
|
await press(page, 'ArrowLeft');
|
|
await press(page, 'ArrowLeft');
|
|
|
|
await press(page, 'Enter');
|
|
|
|
expect(await editorA.innerText()).toBe('abc \n' + ' def');
|
|
expect(await editorB.innerText()).toBe('abc \n' + ' def');
|
|
});
|
|
|
|
test('select from the start of line using shift+arrow', async ({ page }) => {
|
|
await enterInlineEditorPlayground(page);
|
|
await focusInlineRichText(page);
|
|
|
|
const editorA = page.locator('[data-v-root="true"]').nth(0);
|
|
const editorB = page.locator('[data-v-root="true"]').nth(1);
|
|
|
|
expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
|
|
await page.waitForTimeout(100);
|
|
|
|
await type(page, 'abc');
|
|
await press(page, 'Enter');
|
|
await type(page, 'def');
|
|
await press(page, 'Enter');
|
|
await type(page, 'ghi');
|
|
|
|
expect(await editorA.innerText()).toBe('abc\ndef\nghi');
|
|
expect(await editorB.innerText()).toBe('abc\ndef\nghi');
|
|
|
|
/**
|
|
* abc
|
|
* def
|
|
* |ghi
|
|
*/
|
|
await press(page, 'ArrowLeft');
|
|
await press(page, 'ArrowLeft');
|
|
await press(page, 'ArrowLeft');
|
|
await assertSelection(page, 0, 8);
|
|
|
|
/**
|
|
* |abc
|
|
* def
|
|
* |ghi
|
|
*/
|
|
await page.keyboard.down('Shift');
|
|
await press(page, 'ArrowUp');
|
|
await press(page, 'ArrowUp');
|
|
await assertSelection(page, 0, 0, 8);
|
|
|
|
/**
|
|
* a|bc
|
|
* def
|
|
* |ghi
|
|
*/
|
|
await press(page, 'ArrowRight');
|
|
await assertSelection(page, 0, 1, 7);
|
|
await press(page, 'Backspace');
|
|
await page.waitForTimeout(100);
|
|
|
|
expect(await editorA.innerText()).toBe('aghi');
|
|
expect(await editorB.innerText()).toBe('aghi');
|
|
});
|
|
|
|
test('getLine', async ({ page }) => {
|
|
await enterInlineEditorPlayground(page);
|
|
await focusInlineRichText(page);
|
|
|
|
const editorA = page.locator('[data-v-root="true"]').nth(0);
|
|
const editorB = page.locator('[data-v-root="true"]').nth(1);
|
|
|
|
expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
expect(await editorB.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
|
|
await page.waitForTimeout(100);
|
|
|
|
await type(page, 'abc\ndef\nghi');
|
|
|
|
expect(await editorA.innerText()).toBe('abc\ndef\nghi');
|
|
expect(await editorB.innerText()).toBe('abc\ndef\nghi');
|
|
|
|
const [line1, offset1] = await getInlineRichTextLine(page, 0);
|
|
const [line2, offset2] = await getInlineRichTextLine(page, 1);
|
|
const [line3, offset3] = await getInlineRichTextLine(page, 4);
|
|
const [line4, offset4] = await getInlineRichTextLine(page, 5);
|
|
const [line5, offset5] = await getInlineRichTextLine(page, 8);
|
|
const [line6, offset6] = await getInlineRichTextLine(page, 11);
|
|
|
|
expect(line1).toEqual('abc');
|
|
expect(offset1).toEqual(0);
|
|
expect(line2).toEqual('abc');
|
|
expect(offset2).toEqual(1);
|
|
expect(line3).toEqual('def');
|
|
expect(offset3).toEqual(0);
|
|
expect(line4).toEqual('def');
|
|
expect(offset4).toEqual(1);
|
|
expect(line5).toEqual('ghi');
|
|
expect(offset5).toEqual(0);
|
|
expect(line6).toEqual('ghi');
|
|
expect(offset6).toEqual(3);
|
|
});
|
|
|
|
test('yText should not contain \r', async ({ page }) => {
|
|
await enterInlineEditorPlayground(page);
|
|
await focusInlineRichText(page);
|
|
|
|
await page.waitForTimeout(100);
|
|
const message = await page.evaluate(() => {
|
|
const richText = document
|
|
.querySelector('test-page')
|
|
?.querySelector('test-rich-text');
|
|
|
|
if (!richText) {
|
|
throw new Error('Cannot find test-rich-text');
|
|
}
|
|
|
|
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const editor = (richText as any).inlineEditor as InlineEditor;
|
|
|
|
try {
|
|
editor.insertText({ index: 0, length: 0 }, 'abc\r');
|
|
} catch (e) {
|
|
// oxlint-disable-next-line @typescript-eslint/no-explicit-any
|
|
return (e as any).message;
|
|
}
|
|
});
|
|
|
|
expect(message).toBe(
|
|
'yText must not contain "\\r" because it will break the range synchronization'
|
|
);
|
|
});
|
|
|
|
test('embed', async ({ page }) => {
|
|
await enterInlineEditorPlayground(page);
|
|
await focusInlineRichText(page);
|
|
|
|
const editorA = page.locator('[data-v-root="true"]').nth(0);
|
|
const editorAEmbed = page.getByText('embed').nth(0);
|
|
|
|
expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
|
|
await page.waitForTimeout(100);
|
|
|
|
await type(page, 'abcde');
|
|
|
|
expect(await editorA.innerText()).toBe('abcde');
|
|
|
|
await press(page, 'ArrowLeft');
|
|
await page.waitForTimeout(100);
|
|
await page.keyboard.down('Shift');
|
|
await press(page, 'ArrowLeft');
|
|
await press(page, 'ArrowLeft');
|
|
await press(page, 'ArrowLeft');
|
|
await page.keyboard.up('Shift');
|
|
await page.waitForTimeout(100);
|
|
await assertSelection(page, 0, 1, 3);
|
|
|
|
await editorAEmbed.click();
|
|
const embedCount = await page.locator('[data-v-embed="true"]').count();
|
|
expect(embedCount).toBe(3);
|
|
|
|
// try to update cursor position using arrow keys
|
|
await assertSelection(page, 0, 1, 3);
|
|
await press(page, 'ArrowLeft');
|
|
await assertSelection(page, 0, 1, 0);
|
|
await press(page, 'ArrowLeft');
|
|
await assertSelection(page, 0, 0, 0);
|
|
await press(page, 'ArrowRight');
|
|
await assertSelection(page, 0, 1, 0);
|
|
await press(page, 'ArrowRight');
|
|
await assertSelection(page, 0, 1, 1);
|
|
await press(page, 'ArrowRight');
|
|
await assertSelection(page, 0, 2, 0);
|
|
await press(page, 'ArrowRight');
|
|
await assertSelection(page, 0, 2, 1);
|
|
await press(page, 'ArrowRight');
|
|
await assertSelection(page, 0, 3, 0);
|
|
await press(page, 'ArrowRight');
|
|
await assertSelection(page, 0, 3, 1);
|
|
await press(page, 'ArrowRight');
|
|
await assertSelection(page, 0, 4, 0);
|
|
await press(page, 'ArrowRight');
|
|
await assertSelection(page, 0, 5, 0);
|
|
await press(page, 'ArrowLeft');
|
|
await assertSelection(page, 0, 4, 0);
|
|
await press(page, 'ArrowLeft');
|
|
await assertSelection(page, 0, 3, 1);
|
|
|
|
// try to update cursor position and select embed element by clicking embed element
|
|
let rect = await getInlineRangeIndexRect(page, [0, 1]);
|
|
await page.mouse.click(rect.x + 3, rect.y);
|
|
await assertSelection(page, 0, 1, 1);
|
|
|
|
rect = await getInlineRangeIndexRect(page, [0, 2]);
|
|
await page.mouse.click(rect.x + 3, rect.y);
|
|
await assertSelection(page, 0, 2, 1);
|
|
|
|
rect = await getInlineRangeIndexRect(page, [0, 3]);
|
|
await page.mouse.click(rect.x + 3, rect.y);
|
|
await assertSelection(page, 0, 3, 1);
|
|
});
|
|
|
|
test('delete embed when pressing backspace after embed', async ({ page }) => {
|
|
await enterInlineEditorPlayground(page);
|
|
await focusInlineRichText(page);
|
|
|
|
const editorA = page.locator('[data-v-root="true"]').nth(0);
|
|
const editorAEmbed = page.getByText('embed').nth(0);
|
|
expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
await page.waitForTimeout(100);
|
|
await type(page, 'ab');
|
|
expect(await editorA.innerText()).toBe('ab');
|
|
|
|
await page.keyboard.down('Shift');
|
|
await press(page, 'ArrowLeft');
|
|
await page.keyboard.up('Shift');
|
|
await page.waitForTimeout(100);
|
|
await assertSelection(page, 0, 1, 1);
|
|
await editorAEmbed.click();
|
|
|
|
let delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'a',
|
|
},
|
|
{
|
|
insert: 'b',
|
|
attributes: {
|
|
embed: true,
|
|
},
|
|
},
|
|
]);
|
|
|
|
const rect = await getInlineRangeIndexRect(page, [0, 2]);
|
|
// use click to select right side of the embed instead of use arrow key
|
|
await page.mouse.click(rect.x + 3, rect.y);
|
|
await assertSelection(page, 0, 2, 0);
|
|
await press(page, 'Backspace');
|
|
|
|
delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'a',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('markdown shortcut using keyboard util', async ({ page }) => {
|
|
await enterInlineEditorPlayground(page);
|
|
await focusInlineRichText(page);
|
|
|
|
await page.waitForTimeout(100);
|
|
|
|
await type(page, 'aaa**bbb** ccc');
|
|
|
|
const delta = await getDeltaFromInlineRichText(page);
|
|
expect(delta).toEqual([
|
|
{
|
|
insert: 'aaa',
|
|
},
|
|
{
|
|
insert: 'bbb',
|
|
attributes: {
|
|
bold: true,
|
|
},
|
|
},
|
|
{
|
|
insert: 'ccc',
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('triple click to select line', async ({ page }) => {
|
|
await enterInlineEditorPlayground(page);
|
|
await focusInlineRichText(page);
|
|
|
|
const editorA = page.locator('[data-v-root="true"]').nth(0);
|
|
|
|
expect(await editorA.innerText()).toBe(ZERO_WIDTH_SPACE);
|
|
await page.waitForTimeout(100);
|
|
await type(page, 'abc\nabc abc abc\nabc');
|
|
|
|
expect(await editorA.innerText()).toBe('abc\nabc abc abc\nabc');
|
|
|
|
const rect = await getInlineRangeIndexRect(page, [0, 10]);
|
|
await page.mouse.click(rect.x, rect.y, {
|
|
clickCount: 3,
|
|
});
|
|
await assertSelection(page, 0, 4, 11);
|
|
|
|
await press(page, 'Backspace');
|
|
expect(await editorA.innerText()).toBe('abc\n' + ZERO_WIDTH_SPACE + '\nabc');
|
|
});
|