import { browser, expect } from '@wdio/globals'; // Phase C — UI Interaction Tests (C1–C4) // Command palette, search overlay, notification center, keyboard navigation. // ─── Helpers ────────────────────────────────────────────────────────── /** Open command palette via Ctrl+K. */ async function openPalette(): Promise { await browser.execute(() => document.body.focus()); await browser.pause(100); await browser.keys(['Control', 'k']); const palette = await browser.$('[data-testid="command-palette"]'); await palette.waitForDisplayed({ timeout: 3000 }); } /** Close command palette via Escape. */ async function closePalette(): Promise { await browser.keys('Escape'); await browser.pause(300); } /** Type into palette input and get filtered results. */ async function paletteSearch(query: string): Promise { const input = await browser.$('[data-testid="palette-input"]'); await input.setValue(query); await browser.pause(300); return browser.execute(() => { const items = document.querySelectorAll('.palette-item .cmd-label'); return Array.from(items).map(el => el.textContent?.trim() ?? ''); }); } // ─── Scenario C1: Command Palette — Hardening Commands ──────────────── describe('Scenario C1 — Command Palette Hardening Commands', () => { afterEach(async () => { // Ensure palette is closed after each test try { const isVisible = await browser.execute(() => { const el = document.querySelector('[data-testid="command-palette"]'); return el !== null && window.getComputedStyle(el).display !== 'none'; }); if (isVisible) { await closePalette(); } } catch { // Ignore if palette doesn't exist } }); it('should find settings command in palette', async () => { await openPalette(); const results = await paletteSearch('settings'); expect(results.length).toBeGreaterThanOrEqual(1); const hasSettings = results.some(r => r.toLowerCase().includes('settings')); expect(hasSettings).toBe(true); }); it('should find terminal command in palette', async () => { await openPalette(); const results = await paletteSearch('terminal'); expect(results.length).toBeGreaterThanOrEqual(1); const hasTerminal = results.some(r => r.toLowerCase().includes('terminal')); expect(hasTerminal).toBe(true); }); it('should find keyboard shortcuts command in palette', async () => { await openPalette(); const results = await paletteSearch('keyboard'); expect(results.length).toBeGreaterThanOrEqual(1); const hasShortcuts = results.some(r => r.toLowerCase().includes('keyboard')); expect(hasShortcuts).toBe(true); }); it('should list all commands grouped by category when input is empty', async () => { await openPalette(); const input = await browser.$('[data-testid="palette-input"]'); await input.clearValue(); await browser.pause(200); const itemCount = await browser.execute(() => document.querySelectorAll('.palette-item').length, ); // v3 has 18+ commands expect(itemCount).toBeGreaterThanOrEqual(10); // Commands should be organized in groups (categories) const groups = await browser.execute(() => { const headers = document.querySelectorAll('.palette-category'); return Array.from(headers).map(h => h.textContent?.trim() ?? ''); }); // Should have at least 2 command groups expect(groups.length).toBeGreaterThanOrEqual(2); }); }); // ─── Scenario C2: Search Overlay (Ctrl+Shift+F) ────────────────────── describe('Scenario C2 — Search Overlay (FTS5)', () => { it('should open search overlay with Ctrl+Shift+F', async () => { await browser.execute(() => document.body.focus()); await browser.pause(100); await browser.keys(['Control', 'Shift', 'f']); await browser.pause(500); const overlay = await browser.execute(() => { // SearchOverlay uses .search-overlay class const el = document.querySelector('.search-overlay, [data-testid="search-overlay"]'); return el !== null; }); expect(overlay).toBe(true); }); it('should have search input focused', async () => { const isFocused = await browser.execute(() => { const input = document.querySelector('.search-overlay input, [data-testid="search-input"]') as HTMLInputElement | null; if (!input) return false; input.focus(); return input === document.activeElement; }); expect(isFocused).toBe(true); }); it('should show no results for nonsense query', async () => { await browser.execute(() => { const input = document.querySelector('.search-overlay input, [data-testid="search-input"]') as HTMLInputElement | null; if (input) { input.value = 'zzz_nonexistent_xyz_999'; input.dispatchEvent(new Event('input', { bubbles: true })); } }); await browser.pause(500); // 300ms debounce + render time const resultCount = await browser.execute(() => { const results = document.querySelectorAll('.search-result, .search-result-item'); return results.length; }); expect(resultCount).toBe(0); }); it('should close search overlay with Escape', async () => { await browser.keys('Escape'); await browser.pause(300); const overlay = await browser.execute(() => { const el = document.querySelector('.search-overlay, [data-testid="search-overlay"]'); if (!el) return false; const style = window.getComputedStyle(el); return style.display !== 'none' && style.visibility !== 'hidden'; }); expect(overlay).toBe(false); }); }); // ─── Scenario C3: Notification Center ───────────────────────────────── describe('Scenario C3 — Notification Center', () => { it('should render notification bell in status bar', async () => { const hasBell = await browser.execute(() => { const bar = document.querySelector('[data-testid="status-bar"]'); // NotificationCenter is in status bar with bell icon const bell = bar?.querySelector('.notification-bell, .bell-icon, [data-testid="notification-bell"]'); return bell !== null; }); expect(hasBell).toBe(true); }); it('should open notification panel on bell click', async () => { await browser.execute(() => { const bell = document.querySelector('.notification-bell, .bell-icon, [data-testid="notification-bell"]'); if (bell) (bell as HTMLElement).click(); }); await browser.pause(300); const panelOpen = await browser.execute(() => { const panel = document.querySelector('.notification-panel, .notification-dropdown, [data-testid="notification-panel"]'); if (!panel) return false; const style = window.getComputedStyle(panel); return style.display !== 'none'; }); expect(panelOpen).toBe(true); }); it('should show empty state or notification history', async () => { const content = await browser.execute(() => { const panel = document.querySelector('.notification-panel, .notification-dropdown, [data-testid="notification-panel"]'); return panel?.textContent ?? ''; }); // Panel should have some text content (either "No notifications" or actual notifications) expect(content.length).toBeGreaterThan(0); }); it('should close notification panel on outside click', async () => { // Click the backdrop overlay to close the panel await browser.execute(() => { const backdrop = document.querySelector('.notification-center .backdrop'); if (backdrop) (backdrop as HTMLElement).click(); }); await browser.pause(300); const panelOpen = await browser.execute(() => { const panel = document.querySelector('.notification-panel, .notification-dropdown, [data-testid="notification-panel"]'); if (!panel) return false; const style = window.getComputedStyle(panel); return style.display !== 'none'; }); expect(panelOpen).toBe(false); }); }); // ─── Scenario C4: Keyboard Navigation ──────────────────────────────── describe('Scenario C4 — Keyboard-First Navigation', () => { it('should toggle settings with Ctrl+Comma', async () => { await browser.execute(() => document.body.focus()); await browser.pause(100); await browser.keys(['Control', ',']); await browser.pause(500); const settingsVisible = await browser.execute(() => { const panel = document.querySelector('.sidebar-panel'); if (!panel) return false; const style = window.getComputedStyle(panel); return style.display !== 'none'; }); expect(settingsVisible).toBe(true); // Close it await browser.keys('Escape'); await browser.pause(300); }); it('should toggle sidebar with Ctrl+B', async () => { await browser.execute(() => document.body.focus()); await browser.pause(100); // First open settings to have sidebar content await browser.keys(['Control', ',']); await browser.pause(300); const initialState = await browser.execute(() => { const panel = document.querySelector('.sidebar-panel'); return panel !== null && window.getComputedStyle(panel).display !== 'none'; }); // Toggle sidebar await browser.keys(['Control', 'b']); await browser.pause(300); const afterToggle = await browser.execute(() => { const panel = document.querySelector('.sidebar-panel'); if (!panel) return false; return window.getComputedStyle(panel).display !== 'none'; }); // State should have changed if (initialState) { expect(afterToggle).toBe(false); } // Clean up — close sidebar if still open await browser.keys('Escape'); await browser.pause(200); }); it('should focus project with Alt+1', async () => { await browser.execute(() => document.body.focus()); await browser.pause(100); await browser.keys(['Alt', '1']); await browser.pause(300); const hasActive = await browser.execute(() => { const active = document.querySelector('.project-box.active'); return active !== null; }); expect(hasActive).toBe(true); }); });