mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-26 13:19:34 +07:00
345 lines
10 KiB
TypeScript
345 lines
10 KiB
TypeScript
/**
|
|
* Page Editor Content Steps
|
|
*
|
|
* Step definitions for Page editor rich text editing E2E tests
|
|
*/
|
|
import { Then, When } from '@cucumber/cucumber';
|
|
import { expect } from '@playwright/test';
|
|
|
|
import { CustomWorld } from '../../support/world';
|
|
|
|
// ============================================
|
|
// Helper Functions
|
|
// ============================================
|
|
|
|
/**
|
|
* Get the contenteditable editor element
|
|
*/
|
|
async function getEditor(world: CustomWorld) {
|
|
const editor = world.page.locator('[contenteditable="true"]').first();
|
|
await expect(editor).toBeVisible({ timeout: 5000 });
|
|
return editor;
|
|
}
|
|
|
|
// ============================================
|
|
// When Steps - Basic Text
|
|
// ============================================
|
|
|
|
When('用户点击编辑器内容区域', async function (this: CustomWorld) {
|
|
console.log(' 📍 Step: 点击编辑器内容区域...');
|
|
|
|
const editorContent = this.page.locator('[contenteditable="true"]').first();
|
|
if ((await editorContent.count()) > 0) {
|
|
await editorContent.click();
|
|
} else {
|
|
// Fallback: click somewhere else
|
|
await this.page.click('body', { position: { x: 400, y: 400 } });
|
|
}
|
|
await this.page.waitForTimeout(500);
|
|
|
|
console.log(' ✅ 已点击编辑器内容区域');
|
|
});
|
|
|
|
When('用户按下 Enter 键', async function (this: CustomWorld) {
|
|
console.log(' 📍 Step: 按下 Enter 键...');
|
|
|
|
await this.page.keyboard.press('Enter');
|
|
// Wait for debounce save (1000ms) + buffer
|
|
await this.page.waitForTimeout(1500);
|
|
|
|
console.log(' ✅ 已按下 Enter 键');
|
|
});
|
|
|
|
When('用户输入文本 {string}', async function (this: CustomWorld, text: string) {
|
|
console.log(` 📍 Step: 输入文本 "${text}"...`);
|
|
|
|
await this.page.keyboard.type(text, { delay: 30 });
|
|
await this.page.waitForTimeout(300);
|
|
|
|
// Store for later verification
|
|
this.testContext.inputText = text;
|
|
|
|
console.log(` ✅ 已输入文本 "${text}"`);
|
|
});
|
|
|
|
When('用户在编辑器中输入内容 {string}', async function (this: CustomWorld, content: string) {
|
|
console.log(` 📍 Step: 在编辑器中输入内容 "${content}"...`);
|
|
|
|
const editor = await getEditor(this);
|
|
await editor.click();
|
|
await this.page.waitForTimeout(300);
|
|
await this.page.keyboard.type(content, { delay: 30 });
|
|
await this.page.waitForTimeout(300);
|
|
|
|
this.testContext.inputText = content;
|
|
|
|
console.log(` ✅ 已输入内容 "${content}"`);
|
|
});
|
|
|
|
When('用户选中所有内容', async function (this: CustomWorld) {
|
|
console.log(' 📍 Step: 选中所有内容...');
|
|
|
|
await this.page.keyboard.press(`${this.modKey}+A`);
|
|
await this.page.waitForTimeout(300);
|
|
|
|
console.log(' ✅ 已选中所有内容');
|
|
});
|
|
|
|
// ============================================
|
|
// When Steps - Slash Commands
|
|
// ============================================
|
|
|
|
When('用户输入斜杠 {string}', async function (this: CustomWorld, slash: string) {
|
|
console.log(` 📍 Step: 输入斜杠 "${slash}"...`);
|
|
|
|
await this.page.keyboard.type(slash, { delay: 50 });
|
|
// Wait for slash menu to appear
|
|
await this.page.waitForTimeout(500);
|
|
|
|
console.log(` ✅ 已输入斜杠 "${slash}"`);
|
|
});
|
|
|
|
When('用户输入斜杠命令 {string}', async function (this: CustomWorld, command: string) {
|
|
console.log(` 📍 Step: 输入斜杠命令 "${command}"...`);
|
|
|
|
// The command format is "/shortcut" (e.g., "/h1", "/codeblock")
|
|
// First type the slash and wait for menu
|
|
await this.page.keyboard.type('/', { delay: 100 });
|
|
await this.page.waitForTimeout(800); // Wait for slash menu to appear
|
|
|
|
// Then type the rest of the command (without the leading /)
|
|
const shortcut = command.startsWith('/') ? command.slice(1) : command;
|
|
await this.page.keyboard.type(shortcut, { delay: 80 });
|
|
await this.page.waitForTimeout(500); // Wait for menu to filter
|
|
|
|
console.log(` ✅ 已输入斜杠命令 "${command}"`);
|
|
});
|
|
|
|
// ============================================
|
|
// When Steps - Formatting
|
|
// ============================================
|
|
|
|
When('用户按下快捷键 {string}', async function (this: CustomWorld, shortcut: string) {
|
|
console.log(` 📍 Step: 按下快捷键 "${shortcut}"...`);
|
|
|
|
// Convert Meta to platform-specific modifier key for cross-platform support
|
|
const platformShortcut = shortcut.replaceAll('Meta', this.modKey);
|
|
await this.page.keyboard.press(platformShortcut);
|
|
await this.page.waitForTimeout(300);
|
|
|
|
console.log(` ✅ 已按下快捷键 "${platformShortcut}"`);
|
|
});
|
|
|
|
// ============================================
|
|
// Then Steps - Basic Text
|
|
// ============================================
|
|
|
|
Then('编辑器应该显示输入的文本', async function (this: CustomWorld) {
|
|
console.log(' 📍 Step: 验证编辑器显示输入的文本...');
|
|
|
|
const editor = await getEditor(this);
|
|
const text = this.testContext.inputText;
|
|
|
|
// Check if the text is visible in the editor
|
|
const editorText = await editor.textContent();
|
|
expect(editorText).toContain(text);
|
|
|
|
console.log(` ✅ 编辑器显示文本: "${text}"`);
|
|
});
|
|
|
|
Then('编辑器应该显示 {string}', async function (this: CustomWorld, expectedText: string) {
|
|
console.log(` 📍 Step: 验证编辑器显示 "${expectedText}"...`);
|
|
|
|
const editor = await getEditor(this);
|
|
const editorText = await editor.textContent();
|
|
expect(editorText).toContain(expectedText);
|
|
|
|
console.log(` ✅ 编辑器显示 "${expectedText}"`);
|
|
});
|
|
|
|
// ============================================
|
|
// Then Steps - Slash Commands
|
|
// ============================================
|
|
|
|
Then('应该显示斜杠命令菜单', async function (this: CustomWorld) {
|
|
console.log(' 📍 Step: 验证显示斜杠命令菜单...');
|
|
|
|
// The slash menu should be visible
|
|
// Look for menu with heading options, list options, etc.
|
|
const menuSelectors = ['[role="menu"]', '[role="listbox"]', '.slash-menu', '[data-slash-menu]'];
|
|
|
|
let menuFound = false;
|
|
for (const selector of menuSelectors) {
|
|
const menu = this.page.locator(selector);
|
|
if ((await menu.count()) > 0 && (await menu.isVisible())) {
|
|
menuFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Alternative: look for menu items by text
|
|
if (!menuFound) {
|
|
const headingOption = this.page.getByText(/heading|标题/i).first();
|
|
const listOption = this.page.getByText(/list|列表/i).first();
|
|
|
|
menuFound =
|
|
((await headingOption.count()) > 0 && (await headingOption.isVisible())) ||
|
|
((await listOption.count()) > 0 && (await listOption.isVisible()));
|
|
}
|
|
|
|
expect(menuFound).toBe(true);
|
|
|
|
console.log(' ✅ 斜杠命令菜单已显示');
|
|
});
|
|
|
|
Then('编辑器应该包含一级标题', async function (this: CustomWorld) {
|
|
console.log(' 📍 Step: 验证编辑器包含一级标题...');
|
|
|
|
// Check for h1 element in the editor
|
|
const editor = await getEditor(this);
|
|
const h1 = editor.locator('h1');
|
|
|
|
await expect(h1).toBeVisible({ timeout: 5000 });
|
|
|
|
console.log(' ✅ 编辑器包含一级标题');
|
|
});
|
|
|
|
Then('编辑器应该包含无序列表', async function (this: CustomWorld) {
|
|
console.log(' 📍 Step: 验证编辑器包含无序列表...');
|
|
|
|
const editor = await getEditor(this);
|
|
const ul = editor.locator('ul');
|
|
|
|
await expect(ul).toBeVisible({ timeout: 5000 });
|
|
|
|
console.log(' ✅ 编辑器包含无序列表');
|
|
});
|
|
|
|
Then('编辑器应该包含任务列表', async function (this: CustomWorld) {
|
|
console.log(' 📍 Step: 验证编辑器包含任务列表...');
|
|
|
|
const editor = await getEditor(this);
|
|
|
|
// Task list usually has checkbox elements
|
|
const checkboxSelectors = [
|
|
'input[type="checkbox"]',
|
|
'[role="checkbox"]',
|
|
'[data-lexical-check-list]',
|
|
'li[role="listitem"] input',
|
|
];
|
|
|
|
let found = false;
|
|
for (const selector of checkboxSelectors) {
|
|
const checkbox = editor.locator(selector);
|
|
if ((await checkbox.count()) > 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Alternative: check for specific class or structure
|
|
if (!found) {
|
|
const listItem = editor.locator('li');
|
|
found = (await listItem.count()) > 0;
|
|
}
|
|
|
|
expect(found).toBe(true);
|
|
|
|
console.log(' ✅ 编辑器包含任务列表');
|
|
});
|
|
|
|
Then('编辑器应该包含代码块', async function (this: CustomWorld) {
|
|
console.log(' 📍 Step: 验证编辑器包含代码块...');
|
|
|
|
// Code block might be rendered inside the editor OR as a sibling element
|
|
// CodeMirror renders its own container
|
|
|
|
// First check inside the editor
|
|
const editor = await getEditor(this);
|
|
const codeBlockSelectors = [
|
|
'pre',
|
|
'code',
|
|
'.cm-editor', // CodeMirror
|
|
'[data-language]',
|
|
'.code-block',
|
|
];
|
|
|
|
let found = false;
|
|
for (const selector of codeBlockSelectors) {
|
|
const codeBlock = editor.locator(selector);
|
|
if ((await codeBlock.count()) > 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If not found inside editor, check the whole page
|
|
// CodeMirror might render outside the contenteditable
|
|
if (!found) {
|
|
for (const selector of codeBlockSelectors) {
|
|
const codeBlock = this.page.locator(selector);
|
|
if ((await codeBlock.count()) > 0 && (await codeBlock.isVisible())) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
expect(found).toBe(true);
|
|
|
|
console.log(' ✅ 编辑器包含代码块');
|
|
});
|
|
|
|
// ============================================
|
|
// Then Steps - Formatting
|
|
// ============================================
|
|
|
|
Then('选中的文本应该被加粗', async function (this: CustomWorld) {
|
|
console.log(' 📍 Step: 验证文本已加粗...');
|
|
|
|
const editor = await getEditor(this);
|
|
|
|
// Check for bold element (strong or b tag, or font-weight style)
|
|
const boldSelectors = [
|
|
'strong',
|
|
'b',
|
|
'[style*="font-weight: bold"]',
|
|
'[style*="font-weight: 700"]',
|
|
];
|
|
|
|
let found = false;
|
|
for (const selector of boldSelectors) {
|
|
const boldElement = editor.locator(selector);
|
|
if ((await boldElement.count()) > 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
expect(found).toBe(true);
|
|
|
|
console.log(' ✅ 文本已加粗');
|
|
});
|
|
|
|
Then('选中的文本应该变为斜体', async function (this: CustomWorld) {
|
|
console.log(' 📍 Step: 验证文本已斜体...');
|
|
|
|
const editor = await getEditor(this);
|
|
|
|
// Check for italic element (em or i tag, or font-style style)
|
|
const italicSelectors = ['em', 'i', '[style*="font-style: italic"]'];
|
|
|
|
let found = false;
|
|
for (const selector of italicSelectors) {
|
|
const italicElement = editor.locator(selector);
|
|
if ((await italicElement.count()) > 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
expect(found).toBe(true);
|
|
|
|
console.log(' ✅ 文本已斜体');
|
|
});
|