diff --git a/src/App.tsx b/src/App.tsx index 4335583a..6d872b4a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -48,10 +48,12 @@ import { filterEntries } from './utils/noteListHelpers' import { openLocalFile } from './utils/url' import './App.css' -// Type declaration for mock content storage +// Type declarations for mock content storage and test overrides declare global { interface Window { __mockContent?: Record + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- mock handler map for Playwright test overrides + __mockHandlers?: Record any> } } diff --git a/src/mock-tauri/index.ts b/src/mock-tauri/index.ts index c0d4aa3e..0ab471c5 100644 --- a/src/mock-tauri/index.ts +++ b/src/mock-tauri/index.ts @@ -14,9 +14,10 @@ export function isTauri(): boolean { return typeof window !== 'undefined' && ('__TAURI__' in window || '__TAURI_INTERNALS__' in window) } -// Initialize window.__mockContent for browser testing +// Initialize window globals for browser testing and Playwright overrides if (typeof window !== 'undefined') { window.__mockContent = MOCK_CONTENT + window.__mockHandlers = mockHandlers } export async function mockInvoke(cmd: string, args?: Record): Promise { diff --git a/tests/smoke/better-sync-error-ux.spec.ts b/tests/smoke/better-sync-error-ux.spec.ts new file mode 100644 index 00000000..32f63c8a --- /dev/null +++ b/tests/smoke/better-sync-error-ux.spec.ts @@ -0,0 +1,71 @@ +import { test, expect } from '@playwright/test' +import { openCommandPalette, executeCommand } from './helpers' + +test.describe('Sync error UX — actionable push error messages', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + }) + + test('rejected push shows actionable "Pull first" message in toast', async ({ page }) => { + // Override git_push mock to return a rejected result + await page.evaluate(() => { + window.__mockHandlers!.git_push = () => ({ + status: 'rejected', + message: 'Push rejected: remote has new commits. Pull first, then push.', + }) + }) + + // Open commit dialog via command palette + await openCommandPalette(page) + await executeCommand(page, 'Commit & Push') + + // Wait for the CommitDialog textarea + const textarea = page.locator('textarea[placeholder="Commit message..."]') + await textarea.waitFor({ timeout: 5000 }) + await textarea.fill('test commit') + + // Click the "Commit & Push" button in the dialog + const commitButton = page.getByRole('button', { name: 'Commit & Push' }) + await commitButton.click() + + // Verify the toast shows the actionable rejection message + const toast = page.locator('.fixed.bottom-8') + await expect(toast).toContainText('Pull first', { timeout: 5000 }) + }) + + test('auth error push shows authentication message in toast', async ({ page }) => { + await page.evaluate(() => { + window.__mockHandlers!.git_push = () => ({ + status: 'auth_error', + message: 'Push failed: authentication error. Check your credentials.', + }) + }) + + await openCommandPalette(page) + await executeCommand(page, 'Commit & Push') + + const textarea = page.locator('textarea[placeholder="Commit message..."]') + await textarea.waitFor({ timeout: 5000 }) + await textarea.fill('test commit') + + await page.getByRole('button', { name: 'Commit & Push' }).click() + + const toast = page.locator('.fixed.bottom-8') + await expect(toast).toContainText('authentication error', { timeout: 5000 }) + }) + + test('successful push shows normal success message', async ({ page }) => { + await openCommandPalette(page) + await executeCommand(page, 'Commit & Push') + + const textarea = page.locator('textarea[placeholder="Commit message..."]') + await textarea.waitFor({ timeout: 5000 }) + await textarea.fill('test commit') + + await page.getByRole('button', { name: 'Commit & Push' }).click() + + const toast = page.locator('.fixed.bottom-8') + await expect(toast).toContainText('Committed and pushed', { timeout: 5000 }) + }) +})