Files
mission-control/tests/components/TrayPopover.test.tsx
teernisse 32d7e8ee74 feat: add TrayPopover component for menu bar quick access
Shows THE ONE THING with:
- Focus item title, type badge, project, and age
- Quick actions: Start, Defer (1h), Skip
- Queue and inbox counts
- Link to open full window
- Empty state when nothing focused

Includes 18 tests covering all states and interactions.

bd-wlg

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-26 10:38:33 -05:00

199 lines
6.0 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { TrayPopover } from "@/components/TrayPopover";
import { makeFocusItem } from "../helpers/fixtures";
describe("TrayPopover", () => {
describe("with focus item", () => {
it("shows 'THE ONE THING' header", () => {
const item = makeFocusItem({ title: "Review MR !847" });
render(<TrayPopover focusItem={item} queueCount={0} inboxCount={0} />);
expect(screen.getByText("THE ONE THING")).toBeInTheDocument();
});
it("shows focus item title", () => {
const item = makeFocusItem({ title: "Fix auth bug" });
render(<TrayPopover focusItem={item} queueCount={0} inboxCount={0} />);
expect(screen.getByText("Fix auth bug")).toBeInTheDocument();
});
it("shows project path", () => {
const item = makeFocusItem({ project: "platform/core" });
render(<TrayPopover focusItem={item} queueCount={0} inboxCount={0} />);
expect(screen.getByText(/platform\/core/)).toBeInTheDocument();
});
it("shows type badge", () => {
const item = makeFocusItem({ type: "mr_review" });
render(<TrayPopover focusItem={item} queueCount={0} inboxCount={0} />);
expect(screen.getByText(/review/i)).toBeInTheDocument();
});
it("shows Start button", () => {
const item = makeFocusItem();
render(<TrayPopover focusItem={item} queueCount={0} inboxCount={0} />);
expect(screen.getByRole("button", { name: /start/i })).toBeInTheDocument();
});
it("shows Defer button", () => {
const item = makeFocusItem();
render(<TrayPopover focusItem={item} queueCount={0} inboxCount={0} />);
expect(screen.getByRole("button", { name: /defer/i })).toBeInTheDocument();
});
it("shows Skip button", () => {
const item = makeFocusItem();
render(<TrayPopover focusItem={item} queueCount={0} inboxCount={0} />);
expect(screen.getByRole("button", { name: /skip/i })).toBeInTheDocument();
});
});
describe("empty state", () => {
it("shows empty message when no focus item", () => {
render(<TrayPopover focusItem={null} queueCount={0} inboxCount={0} />);
expect(screen.getByText(/nothing focused/i)).toBeInTheDocument();
});
it("does not show action buttons when empty", () => {
render(<TrayPopover focusItem={null} queueCount={0} inboxCount={0} />);
expect(screen.queryByRole("button", { name: /start/i })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: /defer/i })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: /skip/i })).not.toBeInTheDocument();
});
});
describe("counts", () => {
it("shows queue count", () => {
render(<TrayPopover focusItem={null} queueCount={4} inboxCount={0} />);
expect(screen.getByText("Queue: 4")).toBeInTheDocument();
});
it("shows inbox count", () => {
render(<TrayPopover focusItem={null} queueCount={0} inboxCount={3} />);
expect(screen.getByText("Inbox: 3")).toBeInTheDocument();
});
it("shows both counts", () => {
render(<TrayPopover focusItem={null} queueCount={5} inboxCount={2} />);
expect(screen.getByText("Queue: 5")).toBeInTheDocument();
expect(screen.getByText("Inbox: 2")).toBeInTheDocument();
});
});
describe("full window link", () => {
it("shows keyboard shortcut hint", () => {
render(<TrayPopover focusItem={null} queueCount={0} inboxCount={0} />);
// Should show some reference to opening full window
expect(screen.getByText(/full window/i)).toBeInTheDocument();
});
});
describe("actions", () => {
it("calls onStart when Start clicked", async () => {
const user = userEvent.setup();
const onStart = vi.fn();
const item = makeFocusItem();
render(
<TrayPopover
focusItem={item}
queueCount={0}
inboxCount={0}
onStart={onStart}
/>
);
await user.click(screen.getByRole("button", { name: /start/i }));
expect(onStart).toHaveBeenCalledTimes(1);
});
it("calls onDefer with duration when Defer clicked", async () => {
const user = userEvent.setup();
const onDefer = vi.fn();
const item = makeFocusItem();
render(
<TrayPopover
focusItem={item}
queueCount={0}
inboxCount={0}
onDefer={onDefer}
/>
);
await user.click(screen.getByRole("button", { name: /defer/i }));
expect(onDefer).toHaveBeenCalledWith("1h");
});
it("calls onSkip when Skip clicked", async () => {
const user = userEvent.setup();
const onSkip = vi.fn();
const item = makeFocusItem();
render(
<TrayPopover
focusItem={item}
queueCount={0}
inboxCount={0}
onSkip={onSkip}
/>
);
await user.click(screen.getByRole("button", { name: /skip/i }));
expect(onSkip).toHaveBeenCalledTimes(1);
});
it("calls onOpenFull when full window link clicked", async () => {
const user = userEvent.setup();
const onOpenFull = vi.fn();
render(
<TrayPopover
focusItem={null}
queueCount={0}
inboxCount={0}
onOpenFull={onOpenFull}
/>
);
await user.click(screen.getByText(/full window/i));
expect(onOpenFull).toHaveBeenCalledTimes(1);
});
});
describe("relative time", () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-02-26T12:00:00Z"));
});
afterEach(() => {
vi.useRealTimers();
});
it("shows age for items", () => {
const item = makeFocusItem({
updatedAt: new Date("2026-02-24T12:00:00Z").toISOString(), // 2 days ago
});
render(<TrayPopover focusItem={item} queueCount={0} inboxCount={0} />);
expect(screen.getByText(/2d/)).toBeInTheDocument();
});
});
});