refactor(e2e): split spec files under 300-line limit

- phase-c.test.ts (626 lines) → phase-c-ui.test.ts (279), phase-c-tabs.test.ts
  (272), phase-c-llm.test.ts (76) — all 11 scenarios preserved
- agor.test.ts (799 lines) → smoke.test.ts (47), workspace.test.ts (79),
  settings.test.ts (247), features.test.ts (488) — split in progress
- Reset-to-home-state hooks added to stateful before() blocks
- wdio.conf.js specs array updated for all new filenames
This commit is contained in:
Hibryda 2026-03-18 03:09:29 +01:00
parent e76bc341f2
commit f08c4b18cf
9 changed files with 1495 additions and 628 deletions

View file

@ -0,0 +1,279 @@
import { browser, expect } from '@wdio/globals';
// Phase C — UI Interaction Tests (C1C4)
// Command palette, search overlay, notification center, keyboard navigation.
// ─── Helpers ──────────────────────────────────────────────────────────
/** Open command palette via Ctrl+K. */
async function openPalette(): Promise<void> {
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<void> {
await browser.keys('Escape');
await browser.pause(300);
}
/** Type into palette input and get filtered results. */
async function paletteSearch(query: string): Promise<string[]> {
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);
});
});