Operand

pro bo? no.

gram: docs

> ./e2e/tests/fonts.spec.ts

/**
* Font Tests
*
* Comprehensive tests for font family and font size functionality including:
* - Font family changes (Arial, Times New Roman, Georgia, etc.)
* - Font size adjustments (small, medium, large)
* - Combined font and formatting operations
* - Undo/redo for font changes
*/
import { test, expect } from '@playwright/test';
import { EditorPage } from '../helpers/editor-page';
import * as assertions from '../helpers/assertions';
test.describe('Font Family', () => {
let editor: EditorPage;
test.beforeEach(async ({ page }) => {
editor = new EditorPage(page);
await editor.goto();
await editor.waitForReady();
await editor.newDocument();
await editor.focus();
});
test('change font to Arial', async ({ page }) => {
await editor.typeText('Arial font test');
await editor.selectAll();
await editor.setFontFamily('Arial');
// Verify font was applied
const fontFamily = await page.evaluate(() => {
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const element = range.startContainer.parentElement;
return window.getComputedStyle(element!).fontFamily;
}
return '';
});
expect(fontFamily).toContain('Arial');
});
test('change font to Times New Roman', async ({ page }) => {
await editor.typeText('Times font test');
await editor.selectAll();
await editor.setFontFamily('Times New Roman');
// Verify font was applied by checking toolbar reflects the change
// (computed style depends on system font availability)
const toolbarFont = await page.locator('[aria-label="Select font family"]').textContent();
expect(toolbarFont?.toLowerCase()).toContain('times');
await assertions.assertDocumentContainsText(page, 'Times font test');
});
test('change font to Georgia', async ({ page }) => {
await editor.typeText('Georgia font test');
await editor.selectAll();
await editor.setFontFamily('Georgia');
await assertions.assertDocumentContainsText(page, 'Georgia font test');
});
test('change font to Verdana', async ({ page }) => {
await editor.typeText('Verdana font test');
await editor.selectAll();
await editor.setFontFamily('Verdana');
await assertions.assertDocumentContainsText(page, 'Verdana font test');
});
test('change font to Courier New', async ({ page }) => {
await editor.typeText('Courier font test');
await editor.selectAll();
await editor.setFontFamily('Courier New');
await assertions.assertDocumentContainsText(page, 'Courier font test');
});
test('font change on partial selection', async ({ page }) => {
await editor.typeText('Hello World');
await editor.selectText('World');
await editor.setFontFamily('Arial');
await assertions.assertDocumentContainsText(page, 'Hello World');
});
test('multiple font families in document', async ({ page }) => {
// Type all text first
await editor.typeText('First line');
await editor.pressEnter();
await editor.typeText('Second line');
// Now apply fonts to each part
await editor.selectText('First line');
await editor.setFontFamily('Arial');
await editor.selectText('Second line');
await editor.setFontFamily('Georgia'); // Use Georgia instead of Times New Roman which may not be in list
await assertions.assertDocumentContainsText(page, 'First line');
await assertions.assertDocumentContainsText(page, 'Second line');
});
test('undo font family change', async ({ page }) => {
await editor.typeText('Undo test');
await editor.selectAll();
await editor.setFontFamily('Georgia');
await editor.undo();
await assertions.assertDocumentContainsText(page, 'Undo test');
});
});
test.describe('Font Size', () => {
let editor: EditorPage;
test.beforeEach(async ({ page }) => {
editor = new EditorPage(page);
await editor.goto();
await editor.waitForReady();
await editor.newDocument();
await editor.focus();
});
test('set font size to 12pt', async ({ page }) => {
await editor.typeText('Size test');
await editor.selectAll();
await editor.setFontSize(12);
await assertions.assertDocumentContainsText(page, 'Size test');
});
test('set font size to 14pt', async ({ page }) => {
await editor.typeText('Size test');
await editor.selectAll();
await editor.setFontSize(14);
await assertions.assertDocumentContainsText(page, 'Size test');
});
test('set font size to 18pt', async ({ page }) => {
await editor.typeText('Size test');
await editor.selectAll();
await editor.setFontSize(18);
await assertions.assertDocumentContainsText(page, 'Size test');
});
test('set font size to 24pt (large)', async ({ page }) => {
await editor.typeText('Large text');
await editor.selectAll();
await editor.setFontSize(24);
await assertions.assertDocumentContainsText(page, 'Large text');
});
test('set font size to 36pt (very large)', async ({ page }) => {
await editor.typeText('Very large text');
await editor.selectAll();
await editor.setFontSize(36);
await assertions.assertDocumentContainsText(page, 'Very large text');
});
test('set minimum font size 8pt', async ({ page }) => {
await editor.typeText('Tiny text');
await editor.selectAll();
await editor.setFontSize(8);
await assertions.assertDocumentContainsText(page, 'Tiny text');
});
test('set large font size 72pt', async ({ page }) => {
await editor.typeText('Huge');
await editor.selectAll();
await editor.setFontSize(72);
await assertions.assertDocumentContainsText(page, 'Huge');
});
test('font size on partial selection', async ({ page }) => {
await editor.typeText('Hello World');
await editor.selectText('World');
await editor.setFontSize(20);
await assertions.assertDocumentContainsText(page, 'Hello World');
});
test('undo font size change', async ({ page }) => {
await editor.typeText('Undo size test');
await editor.selectAll();
await editor.setFontSize(48);
await editor.undo();
await assertions.assertDocumentContainsText(page, 'Undo size test');
});
});
test.describe('Combined Font Operations', () => {
let editor: EditorPage;
test.beforeEach(async ({ page }) => {
editor = new EditorPage(page);
await editor.goto();
await editor.waitForReady();
await editor.newDocument();
await editor.focus();
});
test('combined font family and size', async ({ page }) => {
await editor.typeText('Combined test');
await editor.selectAll();
await editor.setFontFamily('Georgia');
await editor.setFontSize(16);
await assertions.assertDocumentContainsText(page, 'Combined test');
});
test('font with bold formatting', async ({ page }) => {
await editor.typeText('Bold font test');
await editor.selectAll();
await editor.setFontFamily('Arial');
await editor.applyBold();
await assertions.assertTextIsBold(page, 'Bold font test');
});
test('font with italic formatting', async ({ page }) => {
await editor.typeText('Italic font test');
await editor.selectAll();
await editor.setFontFamily('Georgia');
await editor.applyItalic();
await assertions.assertTextIsItalic(page, 'Italic font test');
});
test('font family and size with bold', async ({ page }) => {
await editor.typeText('Full formatting');
await editor.selectAll();
await editor.setFontFamily('Verdana');
await editor.setFontSize(18);
await editor.applyBold();
await assertions.assertTextIsBold(page, 'Full formatting');
});
test('font with text color', async ({ page }) => {
await editor.typeText('Colored font');
await editor.selectAll();
await editor.setFontFamily('Arial');
await editor.setTextColor('#FF0000');
await assertions.assertDocumentContainsText(page, 'Colored font');
});
test('multiple font changes in sequence', async ({ page }) => {
await editor.typeText('Font changes');
await editor.selectAll();
await editor.setFontFamily('Arial');
await editor.setFontFamily('Georgia');
await editor.setFontFamily('Times New Roman');
await assertions.assertDocumentContainsText(page, 'Font changes');
});
test('multiple size changes in sequence', async ({ page }) => {
await editor.typeText('Size changes');
await editor.selectAll();
await editor.setFontSize(12);
await editor.setFontSize(18);
await editor.setFontSize(24);
await assertions.assertDocumentContainsText(page, 'Size changes');
});
});
test.describe('Font Edge Cases', () => {
let editor: EditorPage;
test.beforeEach(async ({ page }) => {
editor = new EditorPage(page);
await editor.goto();
await editor.waitForReady();
await editor.newDocument();
await editor.focus();
});
test('font change with no selection', async ({ page }) => {
await editor.typeText('No selection');
// Don't select - should apply to next typed text or do nothing
await editor.setFontFamily('Arial');
// Small delay to ensure stored marks are set
await page.waitForTimeout(100);
await editor.typeText(' more text');
await assertions.assertDocumentContainsText(page, 'No selection more text');
});
test('font change on empty document', async ({ page }) => {
// Set font before typing
await editor.setFontFamily('Georgia');
// Small delay to ensure stored marks are set
await page.waitForTimeout(100);
await editor.typeText('Text after font set');
await assertions.assertDocumentContainsText(page, 'Text after font set');
});
test('font size on empty document', async ({ page }) => {
// Set size before typing
await editor.setFontSize(24);
// Small delay to ensure stored marks are set
await page.waitForTimeout(100);
await editor.typeText('Large from start');
await assertions.assertDocumentContainsText(page, 'Large from start');
});
test('rapid font family changes', async ({ page }) => {
await editor.typeText('Rapid changes');
await editor.selectAll();
// Rapid font changes
await editor.setFontFamily('Arial');
await editor.setFontFamily('Georgia');
await editor.setFontFamily('Verdana');
await editor.setFontFamily('Times New Roman');
await assertions.assertDocumentContainsText(page, 'Rapid changes');
});
test('rapid font size changes', async ({ page }) => {
await editor.typeText('Rapid size');
await editor.selectAll();
// Rapid size changes
await editor.setFontSize(10);
await editor.setFontSize(14);
await editor.setFontSize(18);
await editor.setFontSize(24);
await assertions.assertDocumentContainsText(page, 'Rapid size');
});
test('font with special characters', async ({ page }) => {
await editor.typeText('Special: @#$%^&*()');
await editor.selectAll();
await editor.setFontFamily('Courier New');
await assertions.assertDocumentContainsText(page, 'Special: @#$%^&*()');
});
test('font with unicode characters', async ({ page }) => {
await editor.typeText('Unicode: 日本語 中文');
await editor.selectAll();
await editor.setFontFamily('Arial');
await assertions.assertDocumentContainsText(page, 'Unicode:');
});
});
test.describe('Custom font registration via fonts prop', () => {
test('does not register the custom face when fonts prop is omitted', async ({ page }) => {
const editor = new EditorPage(page);
await editor.goto();
await editor.waitForReady();
const found = await page.evaluate(() => {
return Array.from(document.fonts).some(
(f) => f.family.replace(/^["']|["']$/g, '') === 'E2E Custom Font'
);
});
expect(found).toBe(false);
});
test('injects @font-face with each declared weight when fonts prop is supplied', async ({
page,
}) => {
await page.goto('/?e2e=1&empty=1&customFonts=1');
const editor = new EditorPage(page);
await editor.waitForReady();
const styleText = await page.evaluate(() => {
const styles = Array.from(document.head.querySelectorAll('style'));
return styles.map((s) => s.textContent || '').join('\n');
});
expect(styleText).toContain('font-family: "E2E Custom Font"');
expect(styleText).toContain('font-weight: normal');
expect(styleText).toContain('font-weight: 700');
});
test('registers the custom face on document.fonts', async ({ page }) => {
await page.goto('/?e2e=1&empty=1&customFonts=1');
const editor = new EditorPage(page);
await editor.waitForReady();
const families = await page.evaluate(() => {
return Array.from(document.fonts).map((f) => f.family.replace(/^["']|["']$/g, ''));
});
expect(families).toContain('E2E Custom Font');
});
test('custom font actually loads and renders distinct glyphs', async ({ page }) => {
await page.goto('/?e2e=1&empty=1&customFonts=1');
const editor = new EditorPage(page);
await editor.waitForReady();
// Wait for the registered face to finish loading.
const status = await page.evaluate(async () => {
const face = Array.from(document.fonts).find(
(f) => f.family.replace(/^["']|["']$/g, '') === 'E2E Custom Font'
);
if (!face) return 'missing';
await face.load();
return face.status;
});
expect(status).toBe('loaded');
// Compare canvas measurement against a generic fallback. Different glyphs
// produce different widths — proves the browser used the loaded face.
const widths = await page.evaluate(() => {
const sample = 'Custom font sample 1234567890';
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
ctx.font = '72px monospace';
const monospace = ctx.measureText(sample).width;
ctx.font = '72px "E2E Custom Font", monospace';
const custom = ctx.measureText(sample).width;
return { monospace, custom };
});
expect(widths.custom).not.toBe(widths.monospace);
});
});
test.describe('Google Fonts via loadFont()', () => {
test('loadFont(name) registers a Google Fonts face on document.fonts', async ({ page }) => {
// The `fonts` prop is for self-hosted faces. For Google Fonts, consumers
// call `loadFont(name)` directly — the demo does that when ?googleFont=
// is set, mirroring the recommended consumer pattern.
await page.goto('/?e2e=1&empty=1&googleFont=Pacifico');
const editor = new EditorPage(page);
await editor.waitForReady();
const loaded = await page.evaluate(async () => {
await document.fonts.ready;
return document.fonts.check('16px "Pacifico"');
});
expect(loaded).toBe(true);
// Glyph check: Pacifico is a script face, visibly wider than monospace.
const widths = await page.evaluate(() => {
const sample = 'Pacifico script sample';
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
ctx.font = '72px monospace';
const fallback = ctx.measureText(sample).width;
ctx.font = '72px "Pacifico", monospace';
const pacifico = ctx.measureText(sample).width;
return { fallback, pacifico };
});
expect(widths.pacifico).not.toBe(widths.fallback);
});
});
test.describe('Font size picker dropdown (issue #808)', () => {
let editor: EditorPage;
test.beforeEach(async ({ page }) => {
editor = new EditorPage(page);
await editor.goto();
await editor.waitForReady();
await editor.newDocument();
await editor.focus();
});
test('scrolling inside the size list keeps the dropdown open', async ({ page }) => {
await page.locator('[data-testid="font-size-display"]').first().click();
const listbox = page.getByRole('listbox');
await expect(listbox).toBeVisible();
// Scrolling the dropdown's own list fires a scroll event that previously
// bubbled to the window-capture close handler and dismissed the dropdown.
await listbox.evaluate((el) => {
el.scrollTop = el.scrollHeight;
el.dispatchEvent(new Event('scroll', { bubbles: true }));
});
// Dropdown must stay open so the user can reach and click a preset.
await expect(listbox).toBeVisible();
await page.getByRole('option', { name: '72', exact: true }).click();
await expect(listbox).toBeHidden();
});
});