diff --git a/ui-electrobun/tests/e2e/specs/comms.test.ts b/ui-electrobun/tests/e2e/specs/comms.test.ts new file mode 100644 index 0000000..6bfa7a3 --- /dev/null +++ b/ui-electrobun/tests/e2e/specs/comms.test.ts @@ -0,0 +1,85 @@ +/** + * Communications tab tests — channels, DMs, message area, send form. + */ + +describe("Communications tab", () => { + it("should render the comms tab container", async () => { + const comms = await $(".comms-tab"); + if (await comms.isExisting()) { + expect(await comms.isDisplayed()).toBe(true); + } + }); + + it("should show mode toggle bar with Channels and DMs", async () => { + const modeBar = await $(".comms-mode-bar"); + if (!(await modeBar.isExisting())) return; + + const buttons = await $$(".mode-btn"); + expect(buttons.length).toBe(2); + + const texts = await Promise.all(buttons.map((b) => b.getText())); + expect(texts).toContain("Channels"); + expect(texts).toContain("DMs"); + }); + + it("should highlight the active mode button", async () => { + const activeBtn = await $(".mode-btn.active"); + if (await activeBtn.isExisting()) { + expect(await activeBtn.isDisplayed()).toBe(true); + } + }); + + it("should show the comms sidebar", async () => { + const sidebar = await $(".comms-sidebar"); + if (await sidebar.isExisting()) { + expect(await sidebar.isDisplayed()).toBe(true); + } + }); + + it("should show channel list with hash prefix", async () => { + const hashes = await $$(".ch-hash"); + if (hashes.length > 0) { + const text = await hashes[0].getText(); + expect(text).toBe("#"); + } + }); + + it("should show message area", async () => { + const messages = await $(".comms-messages"); + if (await messages.isExisting()) { + expect(await messages.isDisplayed()).toBe(true); + } + }); + + it("should show the message input bar", async () => { + const inputBar = await $(".msg-input-bar"); + if (await inputBar.isExisting()) { + expect(await inputBar.isDisplayed()).toBe(true); + } + }); + + it("should have a send button that is disabled when input is empty", async () => { + const sendBtn = await $(".msg-send-btn"); + if (!(await sendBtn.isExisting())) return; + + const disabled = await sendBtn.getAttribute("disabled"); + // When input is empty, send button should be disabled + expect(disabled).not.toBeNull(); + }); + + it("should switch to DMs mode on DMs button click", async () => { + const buttons = await $$(".mode-btn"); + if (buttons.length < 2) return; + + // Click DMs button + await buttons[1].click(); + await browser.pause(300); + + const cls = await buttons[1].getAttribute("class"); + expect(cls).toContain("active"); + + // Switch back to channels for other tests + await buttons[0].click(); + await browser.pause(300); + }); +}); diff --git a/ui-electrobun/tests/e2e/specs/diagnostics.test.ts b/ui-electrobun/tests/e2e/specs/diagnostics.test.ts new file mode 100644 index 0000000..2e0978a --- /dev/null +++ b/ui-electrobun/tests/e2e/specs/diagnostics.test.ts @@ -0,0 +1,69 @@ +/** + * Diagnostics settings tab tests — connection status, fleet info, refresh. + */ + +describe("Diagnostics tab", () => { + before(async () => { + // Open settings + const gear = await $(".sidebar-icon"); + await gear.click(); + await browser.pause(500); + + // Click Diagnostics tab (last category) + const cats = await $$(".cat-btn"); + const diagCat = cats[cats.length - 1]; + if (diagCat) { + await diagCat.click(); + await browser.pause(300); + } + }); + + after(async () => { + await browser.keys("Escape"); + await browser.pause(300); + }); + + it("should render the diagnostics container", async () => { + const diag = await $(".diagnostics"); + if (await diag.isExisting()) { + expect(await diag.isDisplayed()).toBe(true); + } + }); + + it("should show 'Transport Diagnostics' heading", async () => { + const heading = await $(".diagnostics .sh"); + if (await heading.isExisting()) { + expect(await heading.getText()).toContain("Transport Diagnostics"); + } + }); + + it("should show PTY daemon connection status", async () => { + const keys = await $$(".diag-key"); + if (keys.length > 0) { + const texts = await Promise.all(keys.map((k) => k.getText())); + expect(texts.some((t) => t.includes("PTY"))).toBe(true); + } + }); + + it("should show agent fleet section", async () => { + const labels = await $$(".diag-label"); + if (labels.length > 0) { + const texts = await Promise.all(labels.map((l) => l.getText())); + expect(texts.some((t) => t.toLowerCase().includes("agent fleet"))).toBe(true); + } + }); + + it("should show last refresh timestamp", async () => { + const footer = await $(".diag-footer"); + if (await footer.isExisting()) { + expect(await footer.isDisplayed()).toBe(true); + } + }); + + it("should have a refresh button", async () => { + const refreshBtn = await $(".refresh-btn"); + if (await refreshBtn.isExisting()) { + expect(await refreshBtn.isClickable()).toBe(true); + } + }); +}); diff --git a/ui-electrobun/tests/e2e/specs/files.test.ts b/ui-electrobun/tests/e2e/specs/files.test.ts new file mode 100644 index 0000000..be2599f --- /dev/null +++ b/ui-electrobun/tests/e2e/specs/files.test.ts @@ -0,0 +1,106 @@ +/** + * File browser tests — tree, file viewer, editor, image/pdf/csv support. + */ + +describe("File browser", () => { + it("should render the file browser container", async () => { + // File browser lives inside a project card tab + const fb = await $(".file-browser"); + if (await fb.isExisting()) { + expect(await fb.isDisplayed()).toBe(true); + } + }); + + it("should show the tree panel", async () => { + const tree = await $(".fb-tree"); + if (await tree.isExisting()) { + expect(await tree.isDisplayed()).toBe(true); + } + }); + + it("should show the viewer panel", async () => { + const viewer = await $(".fb-viewer"); + if (await viewer.isExisting()) { + expect(await viewer.isDisplayed()).toBe(true); + } + }); + + it("should show directory rows in tree", async () => { + const dirs = await $$(".fb-dir"); + if (dirs.length > 0) { + expect(dirs[0]).toBeDefined(); + expect(await dirs[0].isDisplayed()).toBe(true); + } + }); + + it("should show file rows in tree", async () => { + const files = await $$(".fb-file"); + if (files.length > 0) { + expect(files[0]).toBeDefined(); + } + }); + + it("should show 'Select a file' placeholder when no file selected", async () => { + const empty = await $(".fb-empty"); + if (await empty.isExisting()) { + const text = await empty.getText(); + expect(text.toLowerCase()).toContain("select"); + } + }); + + it("should expand a directory on click", async () => { + const dirs = await $$(".fb-dir"); + if (dirs.length === 0) return; + + await dirs[0].click(); + await browser.pause(500); + + // Check if chevron rotated (open class) + const chevron = await dirs[0].$(".fb-chevron"); + if (await chevron.isExisting()) { + const cls = await chevron.getAttribute("class"); + expect(cls).toContain("open"); + } + }); + + it("should select a file and show editor header", async () => { + const files = await $$(".fb-file"); + if (files.length === 0) return; + + await files[0].click(); + await browser.pause(500); + + // Should show either editor header or image or empty + const header = await $(".fb-editor-header"); + const image = await $(".fb-image-wrap"); + const error = await $(".fb-error"); + const loading = await $(".fb-empty"); + + const anyVisible = + (await header.isExisting() && await header.isDisplayed()) || + (await image.isExisting() && await image.isDisplayed()) || + (await error.isExisting()) || + (await loading.isExisting()); + + expect(anyVisible).toBe(true); + }); + + it("should show file type icon in tree", async () => { + const icons = await $$(".file-type"); + if (icons.length > 0) { + const text = await icons[0].getText(); + expect(text.length).toBeGreaterThan(0); + } + }); + + it("should show selected state on clicked file", async () => { + const files = await $$(".fb-file"); + if (files.length === 0) return; + + await files[0].click(); + await browser.pause(300); + + const cls = await files[0].getAttribute("class"); + expect(cls).toContain("selected"); + }); +}); diff --git a/ui-electrobun/tests/e2e/specs/groups.test.ts b/ui-electrobun/tests/e2e/specs/groups.test.ts new file mode 100644 index 0000000..7f7e5cf --- /dev/null +++ b/ui-electrobun/tests/e2e/specs/groups.test.ts @@ -0,0 +1,69 @@ +/** + * Group sidebar tests — numbered circles, switching, active state, add button, badges. + */ + +describe("Group sidebar", () => { + it("should show group buttons in sidebar", async () => { + const groups = await $$(".group-btn"); + expect(groups.length).toBeGreaterThanOrEqual(1); + }); + + it("should show numbered circle for each group", async () => { + const circles = await $$(".group-circle"); + expect(circles.length).toBeGreaterThanOrEqual(1); + + const text = await circles[0].getText(); + // First group should show "1" + expect(text).toBe("1"); + }); + + it("should highlight the active group", async () => { + const activeGroups = await $$(".group-btn.active"); + expect(activeGroups.length).toBe(1); + }); + + it("should show add group button", async () => { + const addBtn = await $(".add-group-btn"); + if (await addBtn.isExisting()) { + expect(await addBtn.isDisplayed()).toBe(true); + + const circle = await addBtn.$(".group-circle"); + const text = await circle.getText(); + expect(text).toBe("+"); + } + }); + + it("should switch active group on click", async () => { + const groups = await $$(".group-btn:not(.add-group-btn)"); + if (groups.length < 2) return; + + // Click second group + await groups[1].click(); + await browser.pause(300); + + const cls = await groups[1].getAttribute("class"); + expect(cls).toContain("active"); + + // Switch back to first + await groups[0].click(); + await browser.pause(300); + }); + + it("should show notification badge when group has new activity", async () => { + // Badge may or may not exist depending on state + const badges = await $$(".group-badge"); + // Just verify the badge element structure exists in DOM + expect(badges).toBeDefined(); + }); + + it("should show project grid for active group", async () => { + const grid = await $(".project-grid"); + expect(await grid.isDisplayed()).toBe(true); + }); + + it("should display project cards matching active group", async () => { + const cards = await $$(".project-card"); + // Should show at least the cards for the active group + expect(cards).toBeDefined(); + }); +}); diff --git a/ui-electrobun/tests/e2e/specs/keyboard.test.ts b/ui-electrobun/tests/e2e/specs/keyboard.test.ts new file mode 100644 index 0000000..d4d802b --- /dev/null +++ b/ui-electrobun/tests/e2e/specs/keyboard.test.ts @@ -0,0 +1,65 @@ +/** + * Keyboard / command palette tests — open, commands, filtering, close. + */ + +describe("Command palette", () => { + it("should open via Ctrl+K", async () => { + await browser.keys(["Control", "k"]); + await browser.pause(400); + + const backdrop = await $(".palette-backdrop"); + if (await backdrop.isExisting()) { + const display = await backdrop.getCSSProperty("display"); + expect(display.value).not.toBe("none"); + } + }); + + it("should show the palette panel with input", async () => { + const panel = await $(".palette-panel"); + if (await panel.isExisting()) { + expect(await panel.isDisplayed()).toBe(true); + } + + const input = await $(".palette-input"); + if (await input.isExisting()) { + expect(await input.isDisplayed()).toBe(true); + } + }); + + it("should list 18 commands", async () => { + const items = await $$(".palette-item"); + expect(items.length).toBe(18); + }); + + it("should show command labels and shortcuts", async () => { + const labels = await $$(".cmd-label"); + expect(labels.length).toBeGreaterThan(0); + + const shortcuts = await $$(".cmd-shortcut"); + expect(shortcuts.length).toBeGreaterThan(0); + }); + + it("should filter commands on text input", async () => { + const input = await $(".palette-input"); + if (!(await input.isExisting())) return; + + await input.setValue("terminal"); + await browser.pause(200); + + const items = await $$(".palette-item"); + // Should have fewer than 18 after filtering + expect(items.length).toBeLessThan(18); + expect(items.length).toBeGreaterThan(0); + }); + + it("should close on Escape key", async () => { + await browser.keys("Escape"); + await browser.pause(300); + + const backdrop = await $(".palette-backdrop"); + if (await backdrop.isExisting()) { + const display = await backdrop.getCSSProperty("display"); + expect(display.value).toBe("none"); + } + }); +}); diff --git a/ui-electrobun/tests/e2e/specs/notifications.test.ts b/ui-electrobun/tests/e2e/specs/notifications.test.ts new file mode 100644 index 0000000..d3ebfd6 --- /dev/null +++ b/ui-electrobun/tests/e2e/specs/notifications.test.ts @@ -0,0 +1,62 @@ +/** + * Notification system tests — bell, drawer, clear, toast, history. + */ + +describe("Notification system", () => { + it("should show the notification bell button", async () => { + const bell = await $(".notif-btn"); + expect(await bell.isDisplayed()).toBe(true); + }); + + it("should open notification drawer on bell click", async () => { + const bell = await $(".notif-btn"); + await bell.click(); + await browser.pause(400); + + const drawer = await $(".notif-drawer"); + if (await drawer.isExisting()) { + const display = await drawer.getCSSProperty("display"); + expect(display.value).not.toBe("none"); + } + }); + + it("should show drawer header with title", async () => { + const title = await $(".drawer-title"); + if (await title.isExisting()) { + expect(await title.getText()).toBe("Notifications"); + } + }); + + it("should show clear all button", async () => { + const clearBtn = await $(".clear-btn"); + if (await clearBtn.isExisting()) { + expect(await clearBtn.isDisplayed()).toBe(true); + expect(await clearBtn.getText()).toContain("Clear"); + } + }); + + it("should show empty state or notification items", async () => { + const empty = await $(".notif-empty"); + const items = await $$(".notif-item"); + + // Either empty state message or notification items should be present + const hasContent = + (await empty.isExisting() && await empty.isDisplayed()) || + items.length > 0; + expect(hasContent).toBe(true); + }); + + it("should close drawer on backdrop click", async () => { + const backdrop = await $(".notif-backdrop"); + if (await backdrop.isExisting()) { + await backdrop.click(); + await browser.pause(300); + + const drawer = await $(".notif-drawer"); + if (await drawer.isExisting()) { + const display = await drawer.getCSSProperty("display"); + expect(display.value).toBe("none"); + } + } + }); +}); diff --git a/ui-electrobun/tests/e2e/specs/search.test.ts b/ui-electrobun/tests/e2e/specs/search.test.ts new file mode 100644 index 0000000..122bbc6 --- /dev/null +++ b/ui-electrobun/tests/e2e/specs/search.test.ts @@ -0,0 +1,80 @@ +/** + * Search overlay tests — open/close, input, results display, grouping. + */ + +describe("Search overlay", () => { + it("should open via Ctrl+Shift+F", async () => { + await browser.keys(["Control", "Shift", "f"]); + await browser.pause(400); + + const backdrop = await $(".overlay-backdrop"); + if (await backdrop.isExisting()) { + const display = await backdrop.getCSSProperty("display"); + expect(display.value).not.toBe("none"); + } + }); + + it("should focus the search input on open", async () => { + const input = await $(".search-input"); + if (await input.isExisting()) { + const focused = await browser.execute(() => { + return document.activeElement?.classList.contains("search-input"); + }); + expect(focused).toBe(true); + } + }); + + it("should show the overlay panel", async () => { + const panel = await $(".overlay-panel"); + if (await panel.isExisting()) { + expect(await panel.isDisplayed()).toBe(true); + } + }); + + it("should show 'No results' for non-matching query", async () => { + const input = await $(".search-input"); + if (!(await input.isExisting())) return; + + await input.setValue("zzz_nonexistent_query_zzz"); + await browser.pause(500); // debounce 300ms + render + + const noResults = await $(".no-results"); + if (await noResults.isExisting()) { + expect(await noResults.isDisplayed()).toBe(true); + } + }); + + it("should show Esc hint badge", async () => { + const hint = await $(".esc-hint"); + if (await hint.isExisting()) { + expect(await hint.getText()).toBe("Esc"); + } + }); + + it("should show loading indicator while searching", async () => { + // Loading dot appears briefly during search + const dot = await $(".loading-dot"); + // May or may not be visible depending on timing — just verify class exists + expect(dot).toBeDefined(); + }); + + it("should have grouped results structure", async () => { + // Results list and group labels exist in the DOM structure + const resultsList = await $(".results-list"); + const groupLabel = await $(".group-label"); + // These may not be visible if no results, but structure should exist + expect(resultsList).toBeDefined(); + expect(groupLabel).toBeDefined(); + }); + + it("should close on Escape key", async () => { + await browser.keys("Escape"); + await browser.pause(300); + + const backdrop = await $(".overlay-backdrop"); + if (await backdrop.isExisting()) { + const display = await backdrop.getCSSProperty("display"); + expect(display.value).toBe("none"); + } + }); +}); diff --git a/ui-electrobun/tests/e2e/specs/splash.test.ts b/ui-electrobun/tests/e2e/specs/splash.test.ts new file mode 100644 index 0000000..78627f7 --- /dev/null +++ b/ui-electrobun/tests/e2e/specs/splash.test.ts @@ -0,0 +1,36 @@ +/** + * Splash screen tests — logo, version, loading indicator, auto-dismiss. + * + * Note: The splash screen auto-fades once the app is ready. + * These tests verify structure using display toggle (style:display). + */ + +describe("Splash screen", () => { + it("should have splash element in DOM", async () => { + const splash = await $(".splash"); + // Splash is always in DOM (display toggle), may already be hidden + expect(await splash.isExisting()).toBe(true); + }); + + it("should show the AGOR logo text", async () => { + const logo = await $(".logo-text"); + if (await logo.isExisting()) { + expect(await logo.getText()).toBe("AGOR"); + } + }); + + it("should show version string", async () => { + const version = await $(".splash .version"); + if (await version.isExisting()) { + const text = await version.getText(); + expect(text).toMatch(/^v/); + } + }); + + it("should have loading indicator dots", async () => { + const dots = await $$(".splash .dot"); + if (dots.length > 0) { + expect(dots.length).toBe(3); + } + }); +}); diff --git a/ui-electrobun/tests/e2e/specs/tasks.test.ts b/ui-electrobun/tests/e2e/specs/tasks.test.ts new file mode 100644 index 0000000..742d093 --- /dev/null +++ b/ui-electrobun/tests/e2e/specs/tasks.test.ts @@ -0,0 +1,77 @@ +/** + * Task board tests — kanban columns, cards, create form, drag-drop. + */ + +describe("Task board", () => { + it("should render the task board container", async () => { + const board = await $(".task-board"); + if (await board.isExisting()) { + expect(await board.isDisplayed()).toBe(true); + } + }); + + it("should show the toolbar with title", async () => { + const title = await $(".tb-title"); + if (await title.isExisting()) { + expect(await title.getText()).toBe("Task Board"); + } + }); + + it("should have 5 kanban columns", async () => { + const columns = await $$(".tb-column"); + if (columns.length > 0) { + expect(columns.length).toBe(5); + } + }); + + it("should show column headers with labels", async () => { + const labels = await $$(".tb-col-label"); + if (labels.length === 0) return; + + const texts = await Promise.all(labels.map((l) => l.getText())); + const expected = ["TO DO", "IN PROGRESS", "REVIEW", "DONE", "BLOCKED"]; + for (const exp of expected) { + expect(texts.some((t) => t.toUpperCase().includes(exp))).toBe(true); + } + }); + + it("should show column counts", async () => { + const counts = await $$(".tb-col-count"); + if (counts.length > 0) { + // Each column should have a count badge + expect(counts.length).toBe(5); + } + }); + + it("should show add task button", async () => { + const addBtn = await $(".tb-add-btn"); + if (await addBtn.isExisting()) { + expect(await addBtn.isClickable()).toBe(true); + } + }); + + it("should toggle create form on add button click", async () => { + const addBtn = await $(".tb-add-btn"); + if (!(await addBtn.isExisting())) return; + + await addBtn.click(); + await browser.pause(300); + + const form = await $(".tb-create-form"); + if (await form.isExisting()) { + expect(await form.isDisplayed()).toBe(true); + + // Close form + await addBtn.click(); + await browser.pause(200); + } + }); + + it("should show task count in toolbar", async () => { + const count = await $(".tb-count"); + if (await count.isExisting()) { + const text = await count.getText(); + expect(text).toMatch(/\d+ tasks?/); + } + }); +}); diff --git a/ui-electrobun/tests/e2e/specs/theme.test.ts b/ui-electrobun/tests/e2e/specs/theme.test.ts new file mode 100644 index 0000000..fa29529 --- /dev/null +++ b/ui-electrobun/tests/e2e/specs/theme.test.ts @@ -0,0 +1,96 @@ +/** + * Theme tests — dropdown, groups, switching, custom editor, font changes. + */ + +describe("Theme system", () => { + // Open settings first + before(async () => { + const gear = await $(".sidebar-icon"); + await gear.click(); + await browser.pause(500); + + // Click Appearance tab (first category) + const cats = await $$(".cat-btn"); + if (cats.length > 0) { + await cats[0].click(); + await browser.pause(300); + } + }); + + after(async () => { + // Close settings + await browser.keys("Escape"); + await browser.pause(300); + }); + + it("should show theme dropdown button", async () => { + const ddBtn = await $(".dd-btn"); + if (await ddBtn.isExisting()) { + expect(await ddBtn.isDisplayed()).toBe(true); + } + }); + + it("should open theme dropdown on click", async () => { + const ddBtn = await $(".dd-btn"); + if (!(await ddBtn.isExisting())) return; + + await ddBtn.click(); + await browser.pause(300); + + const dropdown = await $(".dd-list"); + if (await dropdown.isExisting()) { + expect(await dropdown.isDisplayed()).toBe(true); + } + }); + + it("should show theme groups (Catppuccin, Editor, Deep Dark)", async () => { + const groupHeaders = await $$(".dd-group-label"); + if (groupHeaders.length === 0) return; + + const texts = await Promise.all(groupHeaders.map((g) => g.getText())); + expect(texts.some((t) => t.includes("Catppuccin"))).toBe(true); + expect(texts.some((t) => t.includes("Editor"))).toBe(true); + expect(texts.some((t) => t.includes("Deep Dark"))).toBe(true); + }); + + it("should list at least 17 theme options", async () => { + const items = await $$(".dd-item"); + if (items.length > 0) { + expect(items.length).toBeGreaterThanOrEqual(17); + } + }); + + it("should highlight the currently selected theme", async () => { + const activeItems = await $$(".dd-item.selected"); + if (activeItems.length > 0) { + expect(activeItems.length).toBe(1); + } + }); + + it("should apply CSS variables when theme changes", async () => { + // Read initial --ctp-base value + const before = await browser.execute(() => { + return getComputedStyle(document.documentElement).getPropertyValue("--ctp-base").trim(); + }); + + expect(before.length).toBeGreaterThan(0); + }); + + it("should show font size stepper controls", async () => { + // Close theme dropdown first + await browser.keys("Escape"); + await browser.pause(200); + + const steppers = await $$(".size-stepper"); + if (steppers.length > 0) { + expect(steppers.length).toBeGreaterThanOrEqual(1); + } + }); + + it("should show theme action buttons (Edit Theme, Custom)", async () => { + const actionBtns = await $$(".theme-action-btn"); + if (actionBtns.length > 0) { + expect(actionBtns.length).toBeGreaterThanOrEqual(1); + } + }); +}); diff --git a/ui-electrobun/tests/e2e/wdio.conf.js b/ui-electrobun/tests/e2e/wdio.conf.js index 4fb3915..2050748 100644 --- a/ui-electrobun/tests/e2e/wdio.conf.js +++ b/ui-electrobun/tests/e2e/wdio.conf.js @@ -41,10 +41,20 @@ export const config = { // ── Specs ── specs: [ + resolve(__dirname, "specs/splash.test.ts"), resolve(__dirname, "specs/smoke.test.ts"), + resolve(__dirname, "specs/groups.test.ts"), resolve(__dirname, "specs/settings.test.ts"), + resolve(__dirname, "specs/theme.test.ts"), resolve(__dirname, "specs/terminal.test.ts"), resolve(__dirname, "specs/agent.test.ts"), + resolve(__dirname, "specs/keyboard.test.ts"), + resolve(__dirname, "specs/search.test.ts"), + resolve(__dirname, "specs/notifications.test.ts"), + resolve(__dirname, "specs/files.test.ts"), + resolve(__dirname, "specs/comms.test.ts"), + resolve(__dirname, "specs/tasks.test.ts"), + resolve(__dirname, "specs/diagnostics.test.ts"), ], // ── Capabilities ──