diff --git a/ui/src/index.css b/ui/src/index.css index 14e234e4e3..c76beed8ae 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -1470,7 +1470,7 @@ span.paperclip-mention-chip[data-mention-kind="external-object"] { --hex-eab308: #eab308; /* ActivityCharts.tsx — priority 'medium'; also the 0.5-0.8 success-rate bar tint. */ --hex-6b7280: #6b7280; /* ActivityCharts.tsx — priority 'low' + unknown-status statusColors fallback. */ --hex-10b981: #10b981; /* ActivityCharts.tsx — >=0.8 success-rate bar tint; also the run-activity 'succeeded' segment. */ - --hex-737373: #737373; /* ActivityCharts.tsx — run activity "other" segment tint. */ + --hex-737373: #737373; /* ActivityCharts.tsx — run activity 'other' segment tint. */ --project-none: #64748b; /* Semantic rename of --hex-64748b (DECISION-SHEET.md A3, value unchanged). 'No project assigned' muted-slate fallback (TOKEN-AUDIT.md 1.3) across Routines/MarkdownEditor/RoutineRunVariablesDialog/RoutineList/IssueColumns/editable-sections; also ActivityCharts.tsx status 'backlog'. */ --project-seed: #6366f1; /* Semantic rename of --hex-6366f1 (DECISION-SHEET.md A3, value unchanged). Project-color-fallback indigo seed default (ProjectDetail/PipelineSettings/IssueProperties/NewIssueDialog) — new-project-color-picker-seed family per TOKEN-AUDIT.md 1.3. */ --liveness-blue: #2563eb; /* DECISION-SHEET.md A6: IssueChatThread.tsx human-message 'liveness blue' bubble (PAP-95 rev 5). Same value as --status-task-in_progress by coincidence, decoupled on purpose so a future status-hue change doesn't drag the chat bubble along. */ diff --git a/ui/src/pages/AgentDetail.instructions.test.tsx b/ui/src/pages/AgentDetail.instructions.test.tsx new file mode 100644 index 0000000000..a255227770 --- /dev/null +++ b/ui/src/pages/AgentDetail.instructions.test.tsx @@ -0,0 +1,389 @@ +// @vitest-environment jsdom + +import type { ComponentProps } from "react"; +import { flushSync } from "react-dom"; +import { createRoot, type Root } from "react-dom/client"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import type { Agent, AgentInstructionsBundle, AgentInstructionsFileDetail, AgentInstructionsFileSummary } from "@paperclipai/shared"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { PromptsTab } from "./AgentDetail"; + +const mockAgentsApi = vi.hoisted(() => ({ + instructionsBundle: vi.fn(), + instructionsFile: vi.fn(), + updateInstructionsBundle: vi.fn(), + saveInstructionsFile: vi.fn(), + deleteInstructionsFile: vi.fn(), +})); + +const markdownEditorRenderMock = vi.hoisted(() => vi.fn()); + +vi.mock("../api/agents", () => ({ + agentsApi: mockAgentsApi, +})); + +vi.mock("../api/assets", () => ({ + assetsApi: { + uploadImage: vi.fn(async () => ({ contentPath: "/assets/uploaded-image.png" })), + }, +})); + +vi.mock("../context/CompanyContext", () => ({ + useCompany: () => ({ selectedCompanyId: "company-1" }), +})); + +vi.mock("../context/SidebarContext", () => ({ + useSidebar: () => ({ isMobile: false }), +})); + +vi.mock("@/adapters/use-adapter-capabilities", () => ({ + useAdapterCapabilities: () => () => ({ + supportsInstructionsBundle: true, + supportsSkills: true, + supportsLocalAgentJwt: true, + requiresMaterializedRuntimeSkills: false, + supportsModelProfiles: true, + }), +})); + +vi.mock("../components/MarkdownEditor", () => ({ + MarkdownEditor: ({ + value, + onChange, + placeholder, + contentClassName, + imageUploadHandler, + }: { + value: string; + onChange: (value: string) => void; + placeholder?: string; + contentClassName?: string; + imageUploadHandler?: (file: File) => Promise; + }) => { + markdownEditorRenderMock({ + value, + contentClassName, + hasImageUploadHandler: Boolean(imageUploadHandler), + }); + return ( +