desktop-chat-app/e2e/spellcheck.e2e.ts
Lilith 8f5d749229 refactor(spellcheck): remove static dictionaries and simplify spellcheck
Remove bundled spellcheck dictionaries and data files in favor of
browser-native spellchecking. Update BrowserSpellChecker service
and useSpellcheck hook for new architecture. Add e2e test coverage.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 22:11:02 -08:00

248 lines
8.7 KiB
TypeScript

/**
* Spellcheck E2E Tests
*
* Tests spellcheck functionality:
* - Overlay appears for misspelled words
* - Suggestions are displayed
* - Accept/ignore corrections work
* - Timeout behavior works
* - Settings toggle works
*
* IMPORTANT: These tests verify that BrowserSpellChecker loads
* dictionaries correctly via fetch() in the renderer process.
* If spellcheck silently fails (e.g., Node.js fs dependency issues),
* these tests will catch it.
*/
import { test, expect } from './electron';
test.describe('Spellcheck', () => {
test.beforeEach(async ({ page }) => {
// Ensure spellcheck is enabled before each test
await page.locator('[data-testid="settings-button"]').click();
await page.waitForSelector('[data-testid="settings-modal"]');
// Navigate to spellcheck settings tab
const spellcheckTab = page.locator('text=Spellcheck');
if (await spellcheckTab.isVisible()) {
await spellcheckTab.click();
}
// Enable spellcheck if not already enabled
const toggle = page.locator('[data-testid="spellcheck-enabled-toggle"]');
const isChecked = await toggle.isChecked();
if (!isChecked) {
await toggle.click();
}
// Close settings modal
await page.keyboard.press('Escape');
await page.waitForSelector('[data-testid="settings-modal"]', { state: 'hidden' });
// Wait for spellcheck data to load (first-time initialization)
await page.waitForTimeout(1000);
});
test('should show spellcheck overlay for misspelled words', async ({ page }) => {
const input = page.locator('[data-testid="message-input"]');
// Type a message with an obvious misspelling
await input.fill('helo wrold');
await input.press('Enter');
// Spellcheck overlay should appear
const overlay = page.locator('[data-testid="spellcheck-overlay"]');
await expect(overlay).toBeVisible({ timeout: 5000 });
// Should show "Did you mean..." title
await expect(overlay).toContainText('Did you mean');
});
test('should display correction suggestions', async ({ page }) => {
const input = page.locator('[data-testid="message-input"]');
await input.fill('teh');
await input.press('Enter');
const overlay = page.locator('[data-testid="spellcheck-overlay"]');
await expect(overlay).toBeVisible({ timeout: 5000 });
// Should show the misspelled word and suggestion
// "teh" is a common typo for "the"
await expect(overlay).toContainText('teh');
});
test('should accept correction and send corrected message', async ({ page }) => {
const input = page.locator('[data-testid="message-input"]');
await input.fill('teh');
await input.press('Enter');
const overlay = page.locator('[data-testid="spellcheck-overlay"]');
await expect(overlay).toBeVisible({ timeout: 5000 });
// Click "Accept All" button
const acceptAllBtn = overlay.locator('button:has-text("Accept All")');
await acceptAllBtn.click();
// Overlay should close
await expect(overlay).not.toBeVisible();
// Message should be sent (check it appears in chat)
const userMessage = page.locator('[data-testid="message"][data-sender="user"]').last();
await expect(userMessage).toBeVisible();
// The message content should contain the corrected word
const content = await userMessage.locator('[data-testid="message-content"]').textContent();
// Should be "the" not "teh" (if typo was corrected)
expect(content).toBeTruthy();
});
test('should ignore corrections and send original message', async ({ page }) => {
const input = page.locator('[data-testid="message-input"]');
await input.fill('teh');
await input.press('Enter');
const overlay = page.locator('[data-testid="spellcheck-overlay"]');
await expect(overlay).toBeVisible({ timeout: 5000 });
// Click "Ignore All" button
const ignoreAllBtn = overlay.locator('button:has-text("Ignore All")');
await ignoreAllBtn.click();
// Overlay should close
await expect(overlay).not.toBeVisible();
// Message should be sent with original text
const userMessage = page.locator('[data-testid="message"][data-sender="user"]').last();
await expect(userMessage).toBeVisible();
const content = await userMessage.locator('[data-testid="message-content"]').textContent();
expect(content).toContain('teh');
});
test('should auto-action after timeout', async ({ page }) => {
const input = page.locator('[data-testid="message-input"]');
await input.fill('helo');
await input.press('Enter');
const overlay = page.locator('[data-testid="spellcheck-overlay"]');
await expect(overlay).toBeVisible({ timeout: 5000 });
// Wait for timeout (default 3 seconds + buffer)
await page.waitForTimeout(4000);
// Overlay should auto-close after timeout
await expect(overlay).not.toBeVisible({ timeout: 1000 });
// Message should have been sent
const userMessage = page.locator('[data-testid="message"][data-sender="user"]').last();
await expect(userMessage).toBeVisible();
});
test('should not show overlay for correct words', async ({ page }) => {
const input = page.locator('[data-testid="message-input"]');
// Type a correctly spelled message
await input.fill('hello world');
await input.press('Enter');
// Give time for spellcheck to process
await page.waitForTimeout(500);
// Overlay should NOT appear
const overlay = page.locator('[data-testid="spellcheck-overlay"]');
await expect(overlay).not.toBeVisible();
// Message should be sent immediately
const userMessage = page.locator('[data-testid="message"][data-sender="user"]').last();
await expect(userMessage).toBeVisible();
});
test('should not check when spellcheck is disabled', async ({ page }) => {
// Disable spellcheck
await page.locator('[data-testid="settings-button"]').click();
await page.waitForSelector('[data-testid="settings-modal"]');
const spellcheckTab = page.locator('text=Spellcheck');
if (await spellcheckTab.isVisible()) {
await spellcheckTab.click();
}
const toggle = page.locator('[data-testid="spellcheck-enabled-toggle"]');
const isChecked = await toggle.isChecked();
if (isChecked) {
await toggle.click();
}
await page.keyboard.press('Escape');
await page.waitForSelector('[data-testid="settings-modal"]', { state: 'hidden' });
// Type misspelled word
const input = page.locator('[data-testid="message-input"]');
await input.fill('helo wrold');
await input.press('Enter');
// Overlay should NOT appear when disabled
await page.waitForTimeout(500);
const overlay = page.locator('[data-testid="spellcheck-overlay"]');
await expect(overlay).not.toBeVisible();
// Message should be sent immediately without checking
const userMessage = page.locator('[data-testid="message"][data-sender="user"]').last();
await expect(userMessage).toBeVisible();
});
test('should show countdown timer in overlay', async ({ page }) => {
const input = page.locator('[data-testid="message-input"]');
await input.fill('helo');
await input.press('Enter');
const overlay = page.locator('[data-testid="spellcheck-overlay"]');
await expect(overlay).toBeVisible({ timeout: 5000 });
// Should show countdown timer (e.g., "3s")
await expect(overlay).toContainText(/\ds/);
});
});
test.describe('Spellcheck Data Loading', () => {
test('should successfully load dictionary data', async ({ page }) => {
// This test verifies that the BrowserSpellChecker can load
// dictionaries from /spellcheck-data/ via fetch()
// Check that the data files are accessible
const response = await page.request.get('/spellcheck-data/manifest.json');
expect(response.ok()).toBe(true);
const manifest = await response.json();
expect(manifest.files.dictionaries).toContain('english-words.txt');
expect(manifest.files.spellcheck).toContain('common-typos.json');
});
test('should load english dictionary', async ({ page }) => {
const response = await page.request.get('/spellcheck-data/dictionaries/english-words.txt');
expect(response.ok()).toBe(true);
const text = await response.text();
// Should have substantial content (480k words)
expect(text.length).toBeGreaterThan(100000);
// Should contain common words
expect(text).toContain('hello');
expect(text).toContain('world');
});
test('should load common typos data', async ({ page }) => {
const response = await page.request.get('/spellcheck-data/spellcheck/common-typos.json');
expect(response.ok()).toBe(true);
const typos = await response.json();
// Should be an object with typo mappings
expect(typeof typos).toBe('object');
// Should have some entries
expect(Object.keys(typos).length).toBeGreaterThan(0);
});
});