mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 10:30:37 +09:00
Add generic issue-linked board approvals
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
6b4f3b56e4
commit
365b6d9bd8
10 changed files with 345 additions and 39 deletions
|
|
@ -4,7 +4,7 @@ import { act } from "react";
|
|||
import type { ReactNode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import type { Agent } from "@paperclipai/shared";
|
||||
import type { Agent, Approval } from "@paperclipai/shared";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { CommentThread } from "./CommentThread";
|
||||
|
||||
|
|
@ -33,6 +33,25 @@ vi.mock("./InlineEntitySelector", () => ({
|
|||
InlineEntitySelector: () => null,
|
||||
}));
|
||||
|
||||
vi.mock("./ApprovalCard", () => ({
|
||||
ApprovalCard: ({
|
||||
approval,
|
||||
onApprove,
|
||||
onReject,
|
||||
}: {
|
||||
approval: Approval;
|
||||
onApprove?: () => void;
|
||||
onReject?: () => void;
|
||||
}) => (
|
||||
<div>
|
||||
<div>{approval.type}</div>
|
||||
<div>{String(approval.payload.title ?? "")}</div>
|
||||
{onApprove ? <button type="button" onClick={onApprove}>Approve</button> : null}
|
||||
{onReject ? <button type="button" onClick={onReject}>Reject</button> : null}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@/plugins/slots", () => ({
|
||||
PluginSlotOutlet: () => null,
|
||||
}));
|
||||
|
|
@ -144,4 +163,75 @@ describe("CommentThread", () => {
|
|||
root.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
it("renders linked approvals inline in the timeline", () => {
|
||||
const root = createRoot(container);
|
||||
const agent: Agent = {
|
||||
id: "agent-1",
|
||||
companyId: "company-1",
|
||||
name: "CodexCoder",
|
||||
urlKey: "codexcoder",
|
||||
role: "engineer",
|
||||
title: null,
|
||||
icon: "code",
|
||||
status: "active",
|
||||
reportsTo: null,
|
||||
capabilities: null,
|
||||
adapterType: "process",
|
||||
adapterConfig: {},
|
||||
runtimeConfig: {},
|
||||
budgetMonthlyCents: 0,
|
||||
spentMonthlyCents: 0,
|
||||
pauseReason: null,
|
||||
pausedAt: null,
|
||||
permissions: { canCreateAgents: false },
|
||||
lastHeartbeatAt: null,
|
||||
metadata: null,
|
||||
createdAt: new Date("2026-03-11T00:00:00.000Z"),
|
||||
updatedAt: new Date("2026-03-11T00:00:00.000Z"),
|
||||
};
|
||||
const approval: Approval = {
|
||||
id: "approval-1",
|
||||
companyId: "company-1",
|
||||
type: "request_board_approval",
|
||||
requestedByAgentId: "agent-1",
|
||||
requestedByUserId: null,
|
||||
status: "pending",
|
||||
payload: {
|
||||
title: "Approve hosting spend",
|
||||
text: "Estimated monthly cost is $42.",
|
||||
},
|
||||
decisionNote: null,
|
||||
decidedByUserId: null,
|
||||
decidedAt: null,
|
||||
createdAt: new Date("2026-03-11T09:00:00.000Z"),
|
||||
updatedAt: new Date("2026-03-11T09:00:00.000Z"),
|
||||
};
|
||||
|
||||
act(() => {
|
||||
root.render(
|
||||
<MemoryRouter>
|
||||
<CommentThread
|
||||
comments={[]}
|
||||
linkedApprovals={[approval]}
|
||||
agentMap={new Map([["agent-1", agent]])}
|
||||
onAdd={async () => {}}
|
||||
onApproveApproval={async () => {}}
|
||||
onRejectApproval={async () => {}}
|
||||
/>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
});
|
||||
|
||||
const approvalRow = container.querySelector("#approval-approval-1") as HTMLDivElement | null;
|
||||
expect(approvalRow).not.toBeNull();
|
||||
expect(container.textContent).toContain("request_board_approval");
|
||||
expect(container.textContent).toContain("Approve hosting spend");
|
||||
expect(container.textContent).toContain("Approve");
|
||||
expect(container.textContent).toContain("Reject");
|
||||
|
||||
act(() => {
|
||||
root.unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue