agent-orchestrator/tests/e2e/specs/phase-c-ui.test.ts
Hibryda f08c4b18cf 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
2026-03-18 03:09:29 +01:00

279 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
});
});