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>
199 lines
6.0 KiB
TypeScript
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();
|
|
});
|
|
});
|
|
});
|