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:
google-labs-jules[bot]
2025-06-14 19:41:42 +00:00
parent fb93063b47
commit f80e957036
6 changed files with 790 additions and 57 deletions
@@ -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();
});
});