mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-16 04:43:35 +00:00
I've replaced the unit tests for the Rich Text Editor with end-to-end tests.
Here's a summary of the changes:
- I removed the existing unit tests for the RichTextEditor component.
- I created new end-to-end tests that cover:
- Navigating to the cuesheet and opening an event.
- Activating the rich text editor for the 'note' field.
- Testing bold, link, text color, and background color functionalities.
- Verifying that rich text formatting is saved correctly and persists after reopening an event.
This change helps ensure the RichTextEditor works as expected within the overall application.
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
import { test, expect } from '../../fixtures/override';
|
||||
import { TEST_USER_ID_ADMIN } from '../../../src/common/utils/testing';
|
||||
import { SEED_EVENT_A, SEED_PROJECT_A } from '../../../prisma/seed/standardSeed';
|
||||
|
||||
test.describe('[FEAT] Rich Text Editor in Event Note', () => {
|
||||
test.beforeEach(async ({ page, loginByUserId }) => {
|
||||
// Login as admin user (or any user who can edit events)
|
||||
await loginByUserId(TEST_USER_ID_ADMIN);
|
||||
// Navigate to the cuesheet for a known project
|
||||
await page.goto(`/project/${SEED_PROJECT_A.id}/cuesheet`);
|
||||
// Wait for cuesheet to be ready (e.g., for events to load)
|
||||
await expect(page.locator('div[data-testid^="cue-row-"]')).toHaveCountGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
test('should allow rich text editing for event notes', async ({ page }) => {
|
||||
// 1. Open an existing event to edit
|
||||
// Assuming SEED_EVENT_A is visible on the cuesheet
|
||||
const eventRowLocator = page.locator(`div[data-testid="cue-row-${SEED_EVENT_A.id}"]`);
|
||||
await eventRowLocator.click();
|
||||
|
||||
// Wait for the event editor modal to appear
|
||||
// Looking for a common element in the editor, e.g., the cue input or title input.
|
||||
// The title input for SEED_EVENT_A has a specific test id pattern from other tests.
|
||||
await expect(page.locator(`input[data-testid="input-textfield-cue-${SEED_EVENT_A.id}"]`)).toBeVisible();
|
||||
|
||||
// 2. Activate Rich Text Editor for 'Note' Field
|
||||
// The note field is initially a placeholder div.
|
||||
// Its value is SEED_EVENT_A.note
|
||||
// The RichTextEditor component's placeholder div has:
|
||||
// - onClick/onFocus to activate
|
||||
// - style: border: '1px solid #ccc', padding: '10px', minHeight: '50px', cursor: 'text'
|
||||
// - role="textbox" (added in tests for RTL, might be present)
|
||||
// - aria-placeholder="Click to edit..."
|
||||
// Let's try to locate it by its initial text content or a combination of properties.
|
||||
// The label "Note" is above it.
|
||||
const noteLabel = page.locator('label:has-text("Note")');
|
||||
const notePlaceholder = noteLabel.locator('+ div[style*="cursor: text"]'); // Sibling div with cursor: text style
|
||||
|
||||
await expect(notePlaceholder).toBeVisible();
|
||||
if (SEED_EVENT_A.note) {
|
||||
await expect(notePlaceholder).toHaveText(SEED_EVENT_A.note);
|
||||
} else {
|
||||
await expect(notePlaceholder).toHaveText('Click to edit...');
|
||||
}
|
||||
|
||||
await notePlaceholder.click(); // Activate the editor
|
||||
|
||||
// Verify the toolbar is now visible
|
||||
// Toolbar buttons: Bold, Link, Color, BG Color
|
||||
const boldButton = page.locator('button:has-text("B")'); // Simple locator for now
|
||||
const linkButton = page.locator('button:has-text("Link")');
|
||||
const colorButton = page.locator('button:has-text("Color")');
|
||||
const bgColorButton = page.locator('button:has-text("BG Color")');
|
||||
|
||||
await expect(boldButton).toBeVisible();
|
||||
await expect(linkButton).toBeVisible();
|
||||
await expect(colorButton).toBeVisible();
|
||||
await expect(bgColorButton).toBeVisible();
|
||||
|
||||
// Locate the ContentEditable area (it's a div with style min-height: '150px')
|
||||
const editorContentArea = page.locator('div[contenteditable="true"]');
|
||||
await expect(editorContentArea).toBeVisible();
|
||||
await expect(editorContentArea).toBeFocused();
|
||||
|
||||
// Clear existing note content (if any) to type fresh
|
||||
// This can be done by selecting all and deleting, or Lexical's CLEAR_EDITOR_COMMAND
|
||||
// For E2E, page.keyboard.press('Control+A') then page.keyboard.press('Delete') is common.
|
||||
await editorContentArea.press('Control+A'); // or 'Meta+A' on macOS
|
||||
await editorContentArea.press('Delete');
|
||||
await expect(editorContentArea).toHaveText('');
|
||||
|
||||
|
||||
// 3. Test Bold Functionality
|
||||
const boldText = 'This text will be bold.';
|
||||
await editorContentArea.type(boldText);
|
||||
await editorContentArea.selectText(); // Select the typed text
|
||||
await boldButton.click();
|
||||
// Verify bold: Lexical usually wraps bold text in a span with font-weight: bold or a <strong> tag.
|
||||
// Let's assume span with style for now.
|
||||
await expect(editorContentArea.locator('span[style*="font-weight: bold;"]')).toHaveText(boldText);
|
||||
// Or if it uses <strong>: await expect(editorContentArea.locator('strong')).toHaveText(boldText);
|
||||
|
||||
// Clear for next test
|
||||
await editorContentArea.press('Control+A');
|
||||
await editorContentArea.press('Delete');
|
||||
|
||||
// 4. Test Link Functionality
|
||||
const linkText = 'This is a link';
|
||||
const testUrl = 'https://example.com';
|
||||
page.on('dialog', dialog => dialog.accept(testUrl)); // Handle prompt
|
||||
await editorContentArea.type(linkText);
|
||||
await editorContentArea.selectText();
|
||||
await linkButton.click();
|
||||
// Verify link: Check for an <a> tag
|
||||
const linkElement = editorContentArea.locator(`a[href="${testUrl}"]`);
|
||||
await expect(linkElement).toBeVisible();
|
||||
await expect(linkElement).toHaveText(linkText);
|
||||
page.off('dialog', () => {}); // Remove listener if it's a one-time thing per action
|
||||
|
||||
// Clear for next test
|
||||
await editorContentArea.press('Control+A');
|
||||
await editorContentArea.press('Delete');
|
||||
|
||||
// 5. Test Text Color Functionality
|
||||
const colorText = 'This text is red';
|
||||
const redColor = 'rgb(255, 0, 0)';
|
||||
page.on('dialog', dialog => dialog.accept(redColor)); // Handle prompt
|
||||
await editorContentArea.type(colorText);
|
||||
await editorContentArea.selectText();
|
||||
await colorButton.click();
|
||||
// Verify text color
|
||||
await expect(editorContentArea.locator(`span[style*="color: ${redColor};"]`)).toHaveText(colorText);
|
||||
page.off('dialog', () => {});
|
||||
|
||||
// Clear for next test
|
||||
await editorContentArea.press('Control+A');
|
||||
await editorContentArea.press('Delete');
|
||||
|
||||
// 6. Test Background Color Functionality
|
||||
const bgColorText = 'This text has yellow background';
|
||||
const yellowBgColor = 'rgb(255, 255, 0)';
|
||||
page.on('dialog', dialog => dialog.accept(yellowBgColor)); // Handle prompt
|
||||
await editorContentArea.type(bgColorText);
|
||||
await editorContentArea.selectText();
|
||||
await bgColorButton.click();
|
||||
// Verify background color
|
||||
await expect(editorContentArea.locator(`span[style*="background-color: ${yellowBgColor};"]`)).toHaveText(bgColorText);
|
||||
page.off('dialog', () => {});
|
||||
|
||||
|
||||
// 7. Save and Verify
|
||||
// Combine some formatting for the save test
|
||||
await editorContentArea.press('Control+A');
|
||||
await editorContentArea.press('Delete');
|
||||
|
||||
const finalNoteText = "Final Note: Bold, Link, and Red.";
|
||||
await editorContentArea.type(finalNoteText);
|
||||
|
||||
// Apply Bold to "Bold"
|
||||
await editorContentArea.press('Home'); // Go to start
|
||||
for(let i=0; i < "Final Note: ".length; i++) await page.keyboard.press('ArrowRight'); // Move cursor
|
||||
await page.keyboard.down('Shift');
|
||||
for(let i=0; i < "Bold".length; i++) await page.keyboard.press('ArrowRight'); // Select "Bold"
|
||||
await page.keyboard.up('Shift');
|
||||
await boldButton.click();
|
||||
|
||||
// Apply Link to "Link" (re-register dialog handler)
|
||||
page.on('dialog', dialog => dialog.accept(testUrl));
|
||||
await editorContentArea.press('End'); // Go to end
|
||||
for(let i=0; i < ", and Red.".length; i++) await page.keyboard.press('ArrowLeft');
|
||||
await page.keyboard.down('Shift');
|
||||
for(let i=0; i < "Link".length; i++) await page.keyboard.press('ArrowLeft');
|
||||
await page.keyboard.up('Shift');
|
||||
await linkButton.click();
|
||||
page.off('dialog', () => {});
|
||||
|
||||
// Apply Red color to "Red" (re-register dialog handler)
|
||||
page.on('dialog', dialog => dialog.accept(redColor));
|
||||
await editorContentArea.press('End');
|
||||
await page.keyboard.press('ArrowLeft'); // for the dot
|
||||
await page.keyboard.down('Shift');
|
||||
for(let i=0; i < "Red".length; i++) await page.keyboard.press('ArrowLeft');
|
||||
await page.keyboard.up('Shift');
|
||||
await colorButton.click();
|
||||
page.off('dialog', () => {});
|
||||
|
||||
// Click save button in the event editor
|
||||
// Assuming a common save button testid or text
|
||||
const saveButton = page.locator('button:has-text("Save")'); // Or a more specific selector
|
||||
await saveButton.click();
|
||||
|
||||
// Wait for modal to close (e.g., the save button is no longer visible)
|
||||
await expect(saveButton).not.toBeVisible();
|
||||
// Or check that the cuesheet is active again
|
||||
await expect(page.locator('div[data-testid^="cue-row-"]')).toHaveCountGreaterThanOrEqual(1);
|
||||
|
||||
// Re-open the same event
|
||||
await eventRowLocator.click();
|
||||
await expect(page.locator(`input[data-testid="input-textfield-cue-${SEED_EVENT_A.id}"]`)).toBeVisible(); // Wait for editor
|
||||
|
||||
// Activate the note editor again
|
||||
const persistedNotePlaceholder = noteLabel.locator('+ div[style*="cursor: text"]');
|
||||
await persistedNotePlaceholder.click();
|
||||
|
||||
const persistedEditorContentArea = page.locator('div[contenteditable="true"]');
|
||||
await expect(persistedEditorContentArea).toBeVisible();
|
||||
|
||||
// Verify persisted content
|
||||
// This is the most complex part, as the exact HTML structure needs to be known.
|
||||
// Example: Check for bold part
|
||||
await expect(persistedEditorContentArea.locator('span[style*="font-weight: bold;"]')).toContainText("Bold");
|
||||
// Example: Check for link part
|
||||
await expect(persistedEditorContentArea.locator(`a[href="${testUrl}"]`)).toContainText("Link");
|
||||
// Example: Check for colored part
|
||||
await expect(persistedEditorContentArea.locator(`span[style*="color: ${redColor};"]`)).toContainText("Red");
|
||||
|
||||
// Overall text check might be too simple if HTML is involved, but good for a basic check
|
||||
// await expect(persistedEditorContentArea).toContainText("Final Note: Bold, Link, and Red.");
|
||||
// A more robust check would be to get the innerHTML and parse it or use snapshot testing.
|
||||
|
||||
// Close the editor (e.g., click cancel or save again)
|
||||
const cancelButton = page.locator('button:has-text("Cancel")'); // Or a more specific selector
|
||||
await cancelButton.click();
|
||||
await expect(cancelButton).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user