feat: implement Inbox view with triage actions
- Add InboxItem types and TriageAction/DeferDuration types - Create Inbox component with: - Accept/Defer/Archive actions for each item - Keyboard shortcuts (A/D/X) for fast triage - Defer duration picker popup - Inbox Zero celebration state - Type-specific badges with colors - Add comprehensive tests for all functionality Closes bd-qvc Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
180
tests/components/Inbox.test.tsx
Normal file
180
tests/components/Inbox.test.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* Tests for Inbox component.
|
||||
*
|
||||
* TDD: These tests define the expected behavior before implementation.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { Inbox } from "@/components/Inbox";
|
||||
import type { InboxItem, TriageAction, DeferDuration } from "@/lib/types";
|
||||
|
||||
const mockNewItems: InboxItem[] = [
|
||||
{
|
||||
id: "1",
|
||||
type: "mention",
|
||||
title: "You were mentioned in #312",
|
||||
triaged: false,
|
||||
createdAt: "2026-02-26T10:00:00Z",
|
||||
snippet: "@user can you look at this?",
|
||||
actor: "alice",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
type: "mr_feedback",
|
||||
title: "Comment on MR !847",
|
||||
triaged: false,
|
||||
createdAt: "2026-02-26T09:00:00Z",
|
||||
snippet: "This needs some refactoring",
|
||||
actor: "bob",
|
||||
},
|
||||
];
|
||||
|
||||
describe("Inbox", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("shows only untriaged items", () => {
|
||||
const items: InboxItem[] = [
|
||||
...mockNewItems,
|
||||
{
|
||||
id: "3",
|
||||
type: "mention",
|
||||
title: "Already triaged",
|
||||
triaged: true,
|
||||
createdAt: "2026-02-26T08:00:00Z",
|
||||
},
|
||||
];
|
||||
render(<Inbox items={items} onTriage={vi.fn()} />);
|
||||
|
||||
const inboxItems = screen.getAllByTestId("inbox-item");
|
||||
expect(inboxItems).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("shows inbox zero state when empty", () => {
|
||||
render(<Inbox items={[]} onTriage={vi.fn()} />);
|
||||
|
||||
expect(screen.getByText(/Inbox Zero/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/All caught up/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows inbox zero when all items are triaged", () => {
|
||||
const items: InboxItem[] = [
|
||||
{
|
||||
id: "1",
|
||||
type: "mention",
|
||||
title: "Triaged item",
|
||||
triaged: true,
|
||||
createdAt: "2026-02-26T10:00:00Z",
|
||||
},
|
||||
];
|
||||
render(<Inbox items={items} onTriage={vi.fn()} />);
|
||||
|
||||
expect(screen.getByText(/Inbox Zero/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("accept moves item to queue", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onTriage = vi.fn();
|
||||
render(<Inbox items={mockNewItems} onTriage={onTriage} />);
|
||||
|
||||
const acceptButtons = screen.getAllByRole("button", { name: /accept/i });
|
||||
await user.click(acceptButtons[0]);
|
||||
|
||||
expect(onTriage).toHaveBeenCalledWith("1", "accept", undefined);
|
||||
});
|
||||
|
||||
it("defer shows duration picker", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<Inbox items={mockNewItems} onTriage={vi.fn()} />);
|
||||
|
||||
const deferButtons = screen.getAllByRole("button", { name: /defer/i });
|
||||
await user.click(deferButtons[0]);
|
||||
|
||||
// Defer picker should show duration options
|
||||
expect(screen.getByText("1 hour")).toBeInTheDocument();
|
||||
expect(screen.getByText("Tomorrow")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("defer with duration calls onTriage", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onTriage = vi.fn();
|
||||
render(<Inbox items={mockNewItems} onTriage={onTriage} />);
|
||||
|
||||
const deferButtons = screen.getAllByRole("button", { name: /defer/i });
|
||||
await user.click(deferButtons[0]);
|
||||
await user.click(screen.getByText("1 hour"));
|
||||
|
||||
expect(onTriage).toHaveBeenCalledWith("1", "defer", "1h");
|
||||
});
|
||||
|
||||
it("archive removes item from view", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onTriage = vi.fn();
|
||||
render(<Inbox items={mockNewItems} onTriage={onTriage} />);
|
||||
|
||||
const archiveButtons = screen.getAllByRole("button", { name: /archive/i });
|
||||
await user.click(archiveButtons[0]);
|
||||
|
||||
expect(onTriage).toHaveBeenCalledWith("1", "archive", undefined);
|
||||
});
|
||||
|
||||
it("displays item metadata", () => {
|
||||
render(<Inbox items={mockNewItems} onTriage={vi.fn()} />);
|
||||
|
||||
expect(screen.getByText("You were mentioned in #312")).toBeInTheDocument();
|
||||
expect(screen.getByText("@user can you look at this?")).toBeInTheDocument();
|
||||
expect(screen.getByText("alice")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays item count in header", () => {
|
||||
render(<Inbox items={mockNewItems} onTriage={vi.fn()} />);
|
||||
|
||||
expect(screen.getByText(/Inbox \(2\)/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("keyboard shortcuts", () => {
|
||||
it("pressing 'a' on focused item triggers accept", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onTriage = vi.fn();
|
||||
render(<Inbox items={mockNewItems} onTriage={onTriage} />);
|
||||
|
||||
// Focus the first inbox item
|
||||
const firstItem = screen.getAllByTestId("inbox-item")[0];
|
||||
firstItem.focus();
|
||||
|
||||
await user.keyboard("a");
|
||||
|
||||
expect(onTriage).toHaveBeenCalledWith("1", "accept", undefined);
|
||||
});
|
||||
|
||||
it("pressing 'd' on focused item opens defer picker", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<Inbox items={mockNewItems} onTriage={vi.fn()} />);
|
||||
|
||||
// Focus the first inbox item
|
||||
const firstItem = screen.getAllByTestId("inbox-item")[0];
|
||||
firstItem.focus();
|
||||
|
||||
await user.keyboard("d");
|
||||
|
||||
expect(screen.getByText("1 hour")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("pressing 'x' on focused item triggers archive", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onTriage = vi.fn();
|
||||
render(<Inbox items={mockNewItems} onTriage={onTriage} />);
|
||||
|
||||
// Focus the first inbox item
|
||||
const firstItem = screen.getAllByTestId("inbox-item")[0];
|
||||
firstItem.focus();
|
||||
|
||||
await user.keyboard("x");
|
||||
|
||||
expect(onTriage).toHaveBeenCalledWith("1", "archive", undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user