mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2026-03-13 10:00:26 +08:00
@@ -1,5 +1,6 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { AuthTestUtils } from '../../support/auth-utils';
|
||||
import { EditorSelectors } from '../../support/selectors';
|
||||
|
||||
describe('Document Editing with Formatting', () => {
|
||||
const generateRandomEmail = () => `${uuidv4()}@appflowy.io`;
|
||||
@@ -26,9 +27,8 @@ describe('Document Editing with Formatting', () => {
|
||||
cy.wait(5000);
|
||||
|
||||
// Find and focus the editor
|
||||
cy.get('[data-slate-editor="true"]', { timeout: 10000 })
|
||||
EditorSelectors.firstEditor()
|
||||
.should('exist')
|
||||
.first()
|
||||
.click({ force: true });
|
||||
cy.wait(1000);
|
||||
|
||||
@@ -65,9 +65,9 @@ describe('Document Editing with Formatting', () => {
|
||||
cy.wait(1000);
|
||||
|
||||
// Verify content
|
||||
cy.get('[data-slate-editor="true"]').should('contain.text', 'Document Title');
|
||||
cy.get('[data-slate-editor="true"]').should('contain.text', 'Main Heading');
|
||||
cy.get('[data-slate-editor="true"]').should('contain.text', 'Some content text');
|
||||
EditorSelectors.slateEditor().should('contain.text', 'Document Title');
|
||||
EditorSelectors.slateEditor().should('contain.text', 'Main Heading');
|
||||
EditorSelectors.slateEditor().should('contain.text', 'Some content text');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,9 +88,8 @@ describe('Document Editing with Formatting', () => {
|
||||
cy.wait(5000);
|
||||
|
||||
// Find and focus the editor
|
||||
cy.get('[data-slate-editor="true"]', { timeout: 10000 })
|
||||
EditorSelectors.firstEditor()
|
||||
.should('exist')
|
||||
.first()
|
||||
.click({ force: true });
|
||||
cy.wait(1000);
|
||||
|
||||
@@ -136,10 +135,10 @@ describe('Document Editing with Formatting', () => {
|
||||
cy.wait(1000);
|
||||
|
||||
// Verify content
|
||||
cy.get('[data-slate-editor="true"]').should('contain.text', 'Shopping List');
|
||||
cy.get('[data-slate-editor="true"]').should('contain.text', 'Apples');
|
||||
cy.get('[data-slate-editor="true"]').should('contain.text', 'Bananas');
|
||||
cy.get('[data-slate-editor="true"]').should('contain.text', 'Oranges');
|
||||
EditorSelectors.slateEditor().should('contain.text', 'Shopping List');
|
||||
EditorSelectors.slateEditor().should('contain.text', 'Apples');
|
||||
EditorSelectors.slateEditor().should('contain.text', 'Bananas');
|
||||
EditorSelectors.slateEditor().should('contain.text', 'Oranges');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -160,9 +159,8 @@ describe('Document Editing with Formatting', () => {
|
||||
cy.wait(5000);
|
||||
|
||||
// Find and focus the editor
|
||||
cy.get('[data-slate-editor="true"]', { timeout: 10000 })
|
||||
EditorSelectors.firstEditor()
|
||||
.should('exist')
|
||||
.first()
|
||||
.click({ force: true });
|
||||
cy.wait(1000);
|
||||
|
||||
@@ -235,12 +233,12 @@ describe('Document Editing with Formatting', () => {
|
||||
cy.wait(1000);
|
||||
|
||||
// Verify content
|
||||
cy.get('[data-slate-editor="true"]').should('contain.text', 'Steps to Follow');
|
||||
cy.get('[data-slate-editor="true"]').should('contain.text', 'First step');
|
||||
cy.get('[data-slate-editor="true"]').should('contain.text', 'Second step');
|
||||
cy.get('[data-slate-editor="true"]').should('contain.text', 'Tasks');
|
||||
cy.get('[data-slate-editor="true"]').should('contain.text', 'Complete report');
|
||||
cy.get('[data-slate-editor="true"]').should('contain.text', 'Review code');
|
||||
EditorSelectors.slateEditor().should('contain.text', 'Steps to Follow');
|
||||
EditorSelectors.slateEditor().should('contain.text', 'First step');
|
||||
EditorSelectors.slateEditor().should('contain.text', 'Second step');
|
||||
EditorSelectors.slateEditor().should('contain.text', 'Tasks');
|
||||
EditorSelectors.slateEditor().should('contain.text', 'Complete report');
|
||||
EditorSelectors.slateEditor().should('contain.text', 'Review code');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { AuthTestUtils } from '../../support/auth-utils';
|
||||
import { PageSelectors, waitForReactUpdate } from '../../support/selectors';
|
||||
import { EditorSelectors, PageSelectors, waitForReactUpdate } from '../../support/selectors';
|
||||
|
||||
describe('Editor Slash Menu', () => {
|
||||
const generateRandomEmail = () => `${uuidv4()}@appflowy.io`;
|
||||
@@ -37,7 +37,7 @@ describe('Editor Slash Menu', () => {
|
||||
cy.wait(5000); // Give page time to fully load
|
||||
|
||||
// Focus on editor
|
||||
cy.get('[data-slate-editor="true"]').should('exist').click();
|
||||
EditorSelectors.slateEditor().should('exist').click();
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Type slash to open menu
|
||||
|
||||
155
cypress/e2e/editor/text-formatting.cy.ts
Normal file
155
cypress/e2e/editor/text-formatting.cy.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { AuthTestUtils } from '../../support/auth-utils';
|
||||
import { EditorSelectors, waitForReactUpdate } from '../../support/selectors';
|
||||
|
||||
describe('Text Formatting - Selection and Formatting', () => {
|
||||
const generateRandomEmail = () => `${uuidv4()}@appflowy.io`;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.on('uncaught:exception', () => false);
|
||||
cy.viewport(1280, 720);
|
||||
});
|
||||
|
||||
it('should apply all formatting styles to text', () => {
|
||||
const testEmail = generateRandomEmail();
|
||||
|
||||
// Login
|
||||
cy.visit('/login', { failOnStatusCode: false });
|
||||
cy.wait(2000);
|
||||
|
||||
const authUtils = new AuthTestUtils();
|
||||
authUtils.signInWithTestUrl(testEmail).then(() => {
|
||||
cy.url({ timeout: 30000 }).should('include', '/app');
|
||||
cy.wait(3000);
|
||||
|
||||
// Navigate to Getting started page
|
||||
cy.contains('Getting started').click();
|
||||
cy.wait(5000); // Give page time to fully load
|
||||
|
||||
// Focus on editor and ensure it's ready
|
||||
EditorSelectors.slateEditor().should('exist').and('be.visible');
|
||||
cy.wait(2000); // Extra wait for CI environment
|
||||
EditorSelectors.slateEditor().click();
|
||||
waitForReactUpdate(2000);
|
||||
|
||||
// Clear existing content
|
||||
cy.focused().type('{selectall}{backspace}');
|
||||
waitForReactUpdate(1000); // Give more time for content to clear
|
||||
|
||||
// Test 1: Bold text using toolbar button
|
||||
cy.log('Testing BOLD formatting');
|
||||
cy.focused().type('This line is bold');
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Select all text
|
||||
cy.focused().type('{selectall}');
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Wait for selection toolbar to appear and click bold button
|
||||
EditorSelectors.selectionToolbar()
|
||||
.should('exist', { timeout: 10000 })
|
||||
.should('have.css', 'opacity', '1');
|
||||
|
||||
EditorSelectors.boldButton()
|
||||
.should('exist', { timeout: 10000 })
|
||||
.should('be.visible')
|
||||
.click({ force: true });
|
||||
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Wait for the strong element to appear with retry and longer timeout
|
||||
cy.get('strong', { timeout: 15000 }).should('exist').and('be.visible');
|
||||
cy.focused().type('{end}{enter}');
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Test 2: Italic text using toolbar button
|
||||
cy.log('Testing ITALIC formatting');
|
||||
cy.focused().type('This line is italic');
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Select all text
|
||||
cy.focused().type('{selectall}');
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Wait for selection toolbar and click italic button
|
||||
EditorSelectors.selectionToolbar()
|
||||
.should('exist', { timeout: 10000 })
|
||||
.should('have.css', 'opacity', '1');
|
||||
|
||||
EditorSelectors.italicButton()
|
||||
.should('exist', { timeout: 10000 })
|
||||
.should('be.visible')
|
||||
.click({ force: true });
|
||||
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Wait for the em element to appear with retry and longer timeout
|
||||
cy.get('em', { timeout: 15000 }).should('exist').and('be.visible');
|
||||
cy.focused().type('{end}{enter}');
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Test 3: Underline text using toolbar button
|
||||
cy.log('Testing UNDERLINE formatting');
|
||||
cy.focused().type('This line is underlined');
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Select all text
|
||||
cy.focused().type('{selectall}');
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Wait for selection toolbar and click underline button
|
||||
EditorSelectors.selectionToolbar()
|
||||
.should('exist', { timeout: 10000 })
|
||||
.should('have.css', 'opacity', '1');
|
||||
|
||||
EditorSelectors.underlineButton()
|
||||
.should('exist', { timeout: 10000 })
|
||||
.should('be.visible')
|
||||
.click({ force: true });
|
||||
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Wait for the u element to appear with retry and longer timeout
|
||||
cy.get('u', { timeout: 15000 }).should('exist').and('be.visible');
|
||||
cy.focused().type('{end}{enter}');
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Test 4: Strikethrough text using toolbar button
|
||||
cy.log('Testing STRIKETHROUGH formatting');
|
||||
cy.focused().type('This line has strikethrough');
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Select all text
|
||||
cy.focused().type('{selectall}');
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Wait for selection toolbar and click strikethrough button
|
||||
EditorSelectors.selectionToolbar()
|
||||
.should('exist', { timeout: 10000 })
|
||||
.should('have.css', 'opacity', '1');
|
||||
|
||||
EditorSelectors.strikethroughButton()
|
||||
.should('exist', { timeout: 10000 })
|
||||
.should('be.visible')
|
||||
.click({ force: true });
|
||||
|
||||
waitForReactUpdate(1000);
|
||||
|
||||
// Wait for the strikethrough element to appear with retry and longer timeout
|
||||
cy.get('s, del, strike, [style*="text-decoration: line-through"]', { timeout: 15000 }).should('exist').and('be.visible');
|
||||
|
||||
// Final verification - all formatted elements should be visible
|
||||
cy.log('Verifying all formatted text is visible');
|
||||
waitForReactUpdate(2000);
|
||||
cy.get('strong', { timeout: 15000 }).should('exist').and('be.visible').and('contain.text', 'bold');
|
||||
cy.get('em', { timeout: 15000 }).should('exist').and('be.visible').and('contain.text', 'italic');
|
||||
cy.get('u', { timeout: 15000 }).should('exist').and('be.visible').and('contain.text', 'underlined');
|
||||
cy.get('s, del, strike, [style*="text-decoration: line-through"]', { timeout: 15000 }).should('exist').and('be.visible');
|
||||
|
||||
// Take screenshot to verify all lines are visible
|
||||
cy.screenshot('all-formatted-lines-visible');
|
||||
|
||||
cy.log('All text formatting styles tested successfully - each line shows a different style');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -303,6 +303,30 @@ export const CheckboxSelectors = {
|
||||
uncheckedCells: () => cy.get('[data-checked="false"]'),
|
||||
};
|
||||
|
||||
/**
|
||||
* Editor-related selectors
|
||||
*/
|
||||
export const EditorSelectors = {
|
||||
// Main Slate editor
|
||||
slateEditor: () => cy.get('[data-slate-editor="true"]'),
|
||||
|
||||
// Get first editor
|
||||
firstEditor: () => cy.get('[data-slate-editor="true"]').first(),
|
||||
|
||||
// Get editor with specific content
|
||||
editorWithText: (text: string) => cy.get('[data-slate-editor="true"]').contains(text),
|
||||
|
||||
// Selection toolbar
|
||||
selectionToolbar: () => cy.get('[data-testid="selection-toolbar"]'),
|
||||
|
||||
// Formatting buttons in toolbar
|
||||
boldButton: () => cy.get(byTestId('toolbar-bold-button')),
|
||||
italicButton: () => cy.get(byTestId('toolbar-italic-button')),
|
||||
underlineButton: () => cy.get(byTestId('toolbar-underline-button')),
|
||||
strikethroughButton: () => cy.get(byTestId('toolbar-strikethrough-button')),
|
||||
codeButton: () => cy.get(byTestId('toolbar-code-button')),
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to wait for React to re-render after state changes
|
||||
*/
|
||||
|
||||
@@ -43,6 +43,7 @@ export function SelectionToolbar() {
|
||||
<SelectionToolbarContext.Provider value={{ visible, forceShow, rePosition, getDecorateState }}>
|
||||
<div
|
||||
ref={ref}
|
||||
data-testid="selection-toolbar"
|
||||
className={
|
||||
'selection-toolbar pointer-events-none transform transition-opacity duration-200 absolute z-[100] flex min-h-[32px] w-fit flex-grow items-center rounded-lg bg-[var(--fill-toolbar)] px-2 opacity-0 shadow-lg'
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ export function Bold() {
|
||||
<ActionButton
|
||||
onClick={onClick}
|
||||
active={isActivated}
|
||||
data-testid="toolbar-bold-button"
|
||||
tooltip={
|
||||
<>
|
||||
<div>{t('toolbar.bold')}</div>
|
||||
|
||||
@@ -24,6 +24,7 @@ export function InlineCode() {
|
||||
<ActionButton
|
||||
onClick={onClick}
|
||||
active={isActivated}
|
||||
data-testid="toolbar-code-button"
|
||||
tooltip={
|
||||
<>
|
||||
<div>{t('editor.embedCode')}</div>
|
||||
|
||||
@@ -24,6 +24,7 @@ export function Italic() {
|
||||
<ActionButton
|
||||
onClick={onClick}
|
||||
active={isActivated}
|
||||
data-testid="toolbar-italic-button"
|
||||
tooltip={
|
||||
<>
|
||||
<div>{t('toolbar.italic')}</div>
|
||||
|
||||
@@ -24,6 +24,7 @@ export function StrikeThrough() {
|
||||
<ActionButton
|
||||
onClick={onClick}
|
||||
active={isActivated}
|
||||
data-testid="toolbar-strikethrough-button"
|
||||
tooltip={
|
||||
<>
|
||||
<div>{t('editor.strikethrough')}</div>
|
||||
|
||||
@@ -24,6 +24,7 @@ export function Underline() {
|
||||
<ActionButton
|
||||
onClick={onClick}
|
||||
active={isActivated}
|
||||
data-testid="toolbar-underline-button"
|
||||
tooltip={
|
||||
<>
|
||||
<div>{t('editor.underline')}</div>
|
||||
|
||||
Reference in New Issue
Block a user