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>
248 lines
8.7 KiB
TypeScript
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);
|
|
});
|
|
});
|