mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-18 03:30:39 +09:00
feat: polish inbox and issue list workflows
This commit is contained in:
parent
548721248e
commit
dab95740be
37 changed files with 1674 additions and 411 deletions
|
|
@ -7,6 +7,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|||
import type { Issue } from "@paperclipai/shared";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { IssuesList } from "./IssuesList";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
|
||||
const companyState = vi.hoisted(() => ({
|
||||
selectedCompanyId: "company-1",
|
||||
|
|
@ -161,7 +162,9 @@ function renderWithQueryClient(node: ReactNode, container: HTMLDivElement) {
|
|||
act(() => {
|
||||
root.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{node}
|
||||
<TooltipProvider>
|
||||
{node}
|
||||
</TooltipProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
});
|
||||
|
|
@ -297,7 +300,10 @@ describe("IssuesList", () => {
|
|||
);
|
||||
|
||||
await waitForAssertion(() => {
|
||||
expect(container.textContent).toContain("Columns");
|
||||
const columnsButton = Array.from(document.body.querySelectorAll("button")).find(
|
||||
(button) => button.getAttribute("title") === "Columns",
|
||||
);
|
||||
expect(columnsButton).not.toBeUndefined();
|
||||
expect(container.textContent).toContain("PAP-9");
|
||||
expect(container.textContent).toContain("Agent One");
|
||||
expect(container.textContent).not.toContain("Updated");
|
||||
|
|
@ -308,6 +314,77 @@ describe("IssuesList", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("filters the list to a single workspace when a workspace name is clicked", async () => {
|
||||
localStorage.setItem("paperclip:inbox:issue-columns", JSON.stringify(["id", "workspace"]));
|
||||
mockInstanceSettingsApi.getExperimental.mockResolvedValue({ enableIsolatedWorkspaces: true });
|
||||
mockExecutionWorkspacesApi.list.mockResolvedValue([
|
||||
{
|
||||
id: "workspace-alpha",
|
||||
name: "Alpha",
|
||||
mode: "isolated_workspace",
|
||||
status: "active",
|
||||
projectWorkspaceId: null,
|
||||
},
|
||||
{
|
||||
id: "workspace-beta",
|
||||
name: "Beta",
|
||||
mode: "isolated_workspace",
|
||||
status: "active",
|
||||
projectWorkspaceId: null,
|
||||
},
|
||||
]);
|
||||
|
||||
const alphaIssue = createIssue({
|
||||
id: "issue-alpha",
|
||||
identifier: "PAP-20",
|
||||
title: "Alpha issue",
|
||||
executionWorkspaceId: "workspace-alpha",
|
||||
});
|
||||
const betaIssue = createIssue({
|
||||
id: "issue-beta",
|
||||
identifier: "PAP-21",
|
||||
title: "Beta issue",
|
||||
executionWorkspaceId: "workspace-beta",
|
||||
});
|
||||
|
||||
const { root } = renderWithQueryClient(
|
||||
<IssuesList
|
||||
issues={[alphaIssue, betaIssue]}
|
||||
agents={[]}
|
||||
projects={[]}
|
||||
viewStateKey="paperclip:test-issues"
|
||||
onUpdateIssue={() => undefined}
|
||||
/>,
|
||||
container,
|
||||
);
|
||||
|
||||
await waitForAssertion(() => {
|
||||
expect(container.textContent).toContain("Alpha issue");
|
||||
expect(container.textContent).toContain("Beta issue");
|
||||
const workspaceButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(button) => button.textContent === "Alpha",
|
||||
);
|
||||
expect(workspaceButton).not.toBeUndefined();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const workspaceButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(button) => button.textContent === "Alpha",
|
||||
);
|
||||
workspaceButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
await waitForAssertion(() => {
|
||||
expect(container.textContent).toContain("Alpha issue");
|
||||
expect(container.textContent).not.toContain("Beta issue");
|
||||
});
|
||||
|
||||
act(() => {
|
||||
root.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
it("hides routine-backed issues by default and reveals them when the routine filter is enabled", async () => {
|
||||
const manualIssue = createIssue({
|
||||
id: "issue-manual",
|
||||
|
|
@ -341,7 +418,7 @@ describe("IssuesList", () => {
|
|||
|
||||
await act(async () => {
|
||||
const filterButton = Array.from(document.body.querySelectorAll("button")).find(
|
||||
(button) => button.textContent?.includes("Filter"),
|
||||
(button) => button.getAttribute("title") === "Filter",
|
||||
);
|
||||
filterButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
await Promise.resolve();
|
||||
|
|
@ -370,4 +447,75 @@ describe("IssuesList", () => {
|
|||
root.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
it("blurs the search input on Enter without clearing the query", async () => {
|
||||
const { root } = renderWithQueryClient(
|
||||
<IssuesList
|
||||
issues={[createIssue()]}
|
||||
agents={[]}
|
||||
projects={[]}
|
||||
viewStateKey="paperclip:test-issues"
|
||||
initialSearch="bug"
|
||||
onUpdateIssue={() => undefined}
|
||||
/>,
|
||||
container,
|
||||
);
|
||||
|
||||
await waitForAssertion(() => {
|
||||
const input = container.querySelector('input[aria-label="Search issues"]') as HTMLInputElement | null;
|
||||
expect(input).not.toBeNull();
|
||||
input?.focus();
|
||||
expect(document.activeElement).toBe(input);
|
||||
});
|
||||
|
||||
const input = container.querySelector('input[aria-label="Search issues"]') as HTMLInputElement;
|
||||
act(() => {
|
||||
input.dispatchEvent(new KeyboardEvent("keydown", {
|
||||
key: "Enter",
|
||||
bubbles: true,
|
||||
}));
|
||||
});
|
||||
|
||||
expect(document.activeElement).not.toBe(input);
|
||||
expect(input.value).toBe("bug");
|
||||
|
||||
act(() => {
|
||||
root.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
it("blurs the search input on Escape once the field is empty", async () => {
|
||||
const { root } = renderWithQueryClient(
|
||||
<IssuesList
|
||||
issues={[createIssue()]}
|
||||
agents={[]}
|
||||
projects={[]}
|
||||
viewStateKey="paperclip:test-issues"
|
||||
initialSearch=""
|
||||
onUpdateIssue={() => undefined}
|
||||
/>,
|
||||
container,
|
||||
);
|
||||
|
||||
await waitForAssertion(() => {
|
||||
const input = container.querySelector('input[aria-label="Search issues"]') as HTMLInputElement | null;
|
||||
expect(input).not.toBeNull();
|
||||
input?.focus();
|
||||
expect(document.activeElement).toBe(input);
|
||||
});
|
||||
|
||||
const input = container.querySelector('input[aria-label="Search issues"]') as HTMLInputElement;
|
||||
act(() => {
|
||||
input.dispatchEvent(new KeyboardEvent("keydown", {
|
||||
key: "Escape",
|
||||
bubbles: true,
|
||||
}));
|
||||
});
|
||||
|
||||
expect(document.activeElement).not.toBe(input);
|
||||
|
||||
act(() => {
|
||||
root.unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue