merge main: resolve db.test.ts import conflict
Combined both branches: TaskStore import from main + rmSync import from our branch (needed for fresh DB test cleanup). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -96,6 +96,14 @@ describe("Agent CSS classes", () => {
|
||||
expect(hasClass(".agent-system-filter")).toBe(true);
|
||||
expect(hasClass(".agent-controls-actions")).toBe(true);
|
||||
expect(hasClass(".agent-global-controls")).toBe(true);
|
||||
expect(hasClass(".agent-org-chart-shell")).toBe(true);
|
||||
expect(hasClass(".agent-org-chart-controls")).toBe(true);
|
||||
expect(hasClass(".agent-org-chart-viewport")).toBe(true);
|
||||
expect(hasClass(".agent-org-chart-canvas")).toBe(true);
|
||||
expect(hasClass(".agent-org-chart-canvas--zoom-75")).toBe(true);
|
||||
expect(hasClass(".agent-org-chart-canvas--zoom-100")).toBe(true);
|
||||
expect(hasClass(".agent-org-chart-canvas--zoom-125")).toBe(true);
|
||||
expect(hasClass(".agent-org-chart-canvas--zoom-150")).toBe(true);
|
||||
expect(hasClass(".agent-board")).toBe(true);
|
||||
expect(hasClass(".agent-board-card")).toBe(true);
|
||||
expect(hasClass(".agent-board-card--idle")).toBe(true);
|
||||
@@ -177,6 +185,8 @@ describe("Agent CSS classes", () => {
|
||||
expect(orgChartSection).toContain("padding: var(--space-lg)");
|
||||
expect(orgChartSection).toContain("--org-chart-node-width: calc(var(--space-xl) * 9 + var(--space-xs))");
|
||||
expect(orgChartSection).toContain("min-height: var(--org-chart-node-width)");
|
||||
expect(orgChartSection).toContain("touch-action: pan-x pan-y");
|
||||
expect(orgChartSection).toContain("transform-origin: top left");
|
||||
expect(orgChartSection).toContain("border: 1px solid var(--border)");
|
||||
expect(orgChartSection).toContain("color: var(--text)");
|
||||
expect(orgChartSection).toContain("color: var(--text-muted)");
|
||||
|
||||
@@ -790,6 +790,57 @@
|
||||
}
|
||||
|
||||
/* === FN-1167: Agent Org Chart + Chain of Command === */
|
||||
.agent-org-chart-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.agent-org-chart-controls {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.agent-org-chart-controls__zoom-label {
|
||||
min-width: calc(var(--space-2xl) * 2);
|
||||
font-family: var(--font-mono);
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.agent-org-chart-controls__fit-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.agent-org-chart-viewport {
|
||||
overflow: auto;
|
||||
max-width: 100%;
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
|
||||
.agent-org-chart-canvas {
|
||||
width: max-content;
|
||||
transform-origin: top left;
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.agent-org-chart-canvas--zoom-75 {
|
||||
transform: scale(0.75);
|
||||
}
|
||||
|
||||
.agent-org-chart-canvas--zoom-100 {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.agent-org-chart-canvas--zoom-125 {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
|
||||
.agent-org-chart-canvas--zoom-150 {
|
||||
transform: scale(1.5);
|
||||
}
|
||||
|
||||
.agent-org-chart {
|
||||
--org-chart-node-width: calc(var(--space-xl) * 9 + var(--space-xs));
|
||||
--org-chart-connector-gap: var(--space-xs);
|
||||
@@ -798,8 +849,6 @@
|
||||
justify-content: flex-start;
|
||||
gap: var(--space-xl);
|
||||
padding: var(--space-lg);
|
||||
overflow-x: auto;
|
||||
overflow-y: visible;
|
||||
min-height: var(--org-chart-node-width);
|
||||
}
|
||||
|
||||
@@ -1007,6 +1056,25 @@
|
||||
font-size: calc(var(--space-sm) + var(--space-xs) * 0.5);
|
||||
}
|
||||
|
||||
.agent-org-chart-shell {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.agent-org-chart-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.agent-org-chart-controls .btn-icon,
|
||||
.agent-org-chart-controls .agent-org-chart-controls__fit-btn {
|
||||
min-height: calc(var(--space-lg) * 2 + var(--space-xs));
|
||||
}
|
||||
|
||||
.agent-org-chart-viewport {
|
||||
min-height: calc(var(--space-2xl) * 4);
|
||||
}
|
||||
|
||||
.agent-org-chart {
|
||||
--org-chart-node-width: calc(var(--space-2xl) * 5);
|
||||
padding: var(--space-sm);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "./AgentsView.css";
|
||||
import { useState, useEffect, useCallback, useRef, useMemo, useId, lazy, Suspense } from "react";
|
||||
import { Plus, Play, Pause, Activity, Trash2, RefreshCw, Bot, List, ChevronRight, ChevronDown, ChevronUp, Filter, Upload, Network, SlidersHorizontal, Copy, Check } from "lucide-react";
|
||||
import { Plus, Play, Pause, Activity, Trash2, RefreshCw, Bot, List, ChevronRight, ChevronDown, ChevronUp, Filter, Upload, Network, SlidersHorizontal, Copy, Check, ZoomIn, ZoomOut, Minimize2 } from "lucide-react";
|
||||
import type { Agent, AgentCapability, AgentOnboardingSummary, AgentState, OrgTreeNode } from "../api";
|
||||
import { updateAgent, updateAgentState, deleteAgent, startAgentRun, fetchOrgTree, fetchSettings, updateSettings } from "../api";
|
||||
|
||||
@@ -47,6 +47,7 @@ const AGENT_ROLES: { value: AgentCapability; label: string; icon: string }[] = [
|
||||
const HEARTBEAT_MULTIPLIER_PRESETS = [0.1, 0.25, 0.5, 1, 2, 3, 5, 10] as const;
|
||||
|
||||
const SKILL_PATH_LABEL_PATTERN = /(?:^|\/)skills\/([^/]+)\/SKILL\.md$/i;
|
||||
const ORG_CHART_ZOOM_LEVELS = [0.75, 1, 1.25, 1.5] as const;
|
||||
|
||||
export function formatAgentSkillBadgeLabel(skillId: string): string {
|
||||
const trimmedSkillId = skillId.trim();
|
||||
@@ -267,6 +268,7 @@ export function AgentsView({ addToast, projectId, onOpenTaskLogs, agentOnboardin
|
||||
const [isOrgTreeLoading, setIsOrgTreeLoading] = useState(false);
|
||||
const [isControlsPanelOpen, setIsControlsPanelOpen] = useState(false);
|
||||
const [isOverviewOpen, setIsOverviewOpen] = useState(false);
|
||||
const [orgChartZoomIndex, setOrgChartZoomIndex] = useState(1);
|
||||
const controlsPanelRef = useRef<HTMLDivElement>(null);
|
||||
const { confirm } = useConfirm();
|
||||
const controlsTriggerRef = useRef<HTMLButtonElement>(null);
|
||||
@@ -706,6 +708,9 @@ export function AgentsView({ addToast, projectId, onOpenTaskLogs, agentOnboardin
|
||||
|
||||
const handleAgentViewChange = useCallback((nextView: "list" | "board" | "org") => {
|
||||
setAgentView(nextView);
|
||||
if (nextView !== "org") {
|
||||
setOrgChartZoomIndex(1);
|
||||
}
|
||||
if (isMobileViewport && selectedAgentId) {
|
||||
handleCloseDetail();
|
||||
}
|
||||
@@ -714,6 +719,7 @@ export function AgentsView({ addToast, projectId, onOpenTaskLogs, agentOnboardin
|
||||
const getRoleLabel = (role: AgentCapability) => AGENT_ROLES.find(r => r.value === role)?.label ?? role;
|
||||
const getRoleIcon = (role: AgentCapability) => AGENT_ROLES.find(r => r.value === role)?.icon ?? "◆";
|
||||
const selectedAgent = selectedAgentId ? displayAgents.find((agent) => agent.id === selectedAgentId) ?? null : null;
|
||||
const orgChartZoom = ORG_CHART_ZOOM_LEVELS[orgChartZoomIndex];
|
||||
|
||||
/** Get skill badges from agent metadata */
|
||||
const getSkillBadges = (agent: Agent): string[] => {
|
||||
@@ -966,27 +972,68 @@ export function AgentsView({ addToast, projectId, onOpenTaskLogs, agentOnboardin
|
||||
<span>Loading agents...</span>
|
||||
</div>
|
||||
) : agentView === "org" ? (
|
||||
<div className="agent-org-chart" data-testid="agent-org-chart">
|
||||
{isOrgTreeLoading ? (
|
||||
<div className="agent-org-chart__loading" role="status" aria-live="polite">
|
||||
<RefreshCw size={18} className="spin" />
|
||||
<span>Loading org chart...</span>
|
||||
<div className="agent-org-chart-shell" data-testid="agent-org-chart-shell">
|
||||
{isMobileViewport ? (
|
||||
<div className="agent-org-chart-controls" data-testid="agent-org-chart-controls">
|
||||
<button
|
||||
type="button"
|
||||
className="btn-icon touch-target"
|
||||
onClick={() => setOrgChartZoomIndex((value) => Math.max(0, value - 1))}
|
||||
disabled={orgChartZoomIndex === 0}
|
||||
aria-label="Zoom out org chart"
|
||||
title="Zoom out"
|
||||
>
|
||||
<ZoomOut size={16} />
|
||||
</button>
|
||||
<span className="agent-org-chart-controls__zoom-label" aria-live="polite">{Math.round(orgChartZoom * 100)}%</span>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-icon touch-target"
|
||||
onClick={() => setOrgChartZoomIndex((value) => Math.min(ORG_CHART_ZOOM_LEVELS.length - 1, value + 1))}
|
||||
disabled={orgChartZoomIndex === ORG_CHART_ZOOM_LEVELS.length - 1}
|
||||
aria-label="Zoom in org chart"
|
||||
title="Zoom in"
|
||||
>
|
||||
<ZoomIn size={16} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn touch-target btn-sm agent-org-chart-controls__fit-btn"
|
||||
onClick={() => setOrgChartZoomIndex(1)}
|
||||
aria-label="Fit org chart"
|
||||
title="Fit org chart"
|
||||
>
|
||||
<Minimize2 size={16} />
|
||||
Fit
|
||||
</button>
|
||||
</div>
|
||||
) : displayOrgTree.length === 0 ? (
|
||||
<AgentEmptyState onCtaClick={handleOpenNewAgent} />
|
||||
) : (
|
||||
displayOrgTree.map((node) => (
|
||||
<OrgChartNode
|
||||
key={node.agent.id}
|
||||
node={node}
|
||||
onSelect={openAgentDetail}
|
||||
getHealthStatus={getHealthStatus}
|
||||
getRoleIcon={getRoleIcon}
|
||||
getSkillBadges={getSkillBadges}
|
||||
selectedAgentId={selectedAgentId}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
) : null}
|
||||
<div className="agent-org-chart-viewport" data-testid="agent-org-chart-viewport">
|
||||
<div className={`agent-org-chart-canvas agent-org-chart-canvas--zoom-${Math.round(orgChartZoom * 100)}`}>
|
||||
<div className="agent-org-chart" data-testid="agent-org-chart">
|
||||
{isOrgTreeLoading ? (
|
||||
<div className="agent-org-chart__loading" role="status" aria-live="polite">
|
||||
<RefreshCw size={18} className="spin" />
|
||||
<span>Loading org chart...</span>
|
||||
</div>
|
||||
) : displayOrgTree.length === 0 ? (
|
||||
<AgentEmptyState onCtaClick={handleOpenNewAgent} />
|
||||
) : (
|
||||
displayOrgTree.map((node) => (
|
||||
<OrgChartNode
|
||||
key={node.agent.id}
|
||||
node={node}
|
||||
onSelect={openAgentDetail}
|
||||
getHealthStatus={getHealthStatus}
|
||||
getRoleIcon={getRoleIcon}
|
||||
getSkillBadges={getSkillBadges}
|
||||
selectedAgentId={selectedAgentId}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : agentView === "board" ? (
|
||||
<div className="agent-board">
|
||||
|
||||
@@ -92,7 +92,8 @@
|
||||
@media (max-width: 768px) {
|
||||
.modal-overlay.settings-modal-overlay,
|
||||
.modal-overlay:has(.settings-modal) {
|
||||
padding-top: 0;
|
||||
padding: 0;
|
||||
inset: 0;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
}
|
||||
@@ -102,16 +103,18 @@
|
||||
min-width: 0;
|
||||
max-width: 100vw;
|
||||
height: 100dvh;
|
||||
min-height: 0;
|
||||
min-height: 100dvh;
|
||||
max-height: 100dvh;
|
||||
margin: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
resize: none;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.modal.settings-modal[style*="--keyboard-overlap"] {
|
||||
height: var(--vv-height, 100dvh);
|
||||
min-height: var(--vv-height, 100dvh);
|
||||
max-height: var(--vv-height, 100dvh);
|
||||
transform: translateY(var(--vv-offset-top, 0px));
|
||||
will-change: transform;
|
||||
@@ -366,16 +369,22 @@
|
||||
.settings-section-heading {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
padding: var(--space-lg) 0 var(--space-md);
|
||||
margin: 0;
|
||||
padding: var(--space-lg) var(--space-xl) var(--space-md);
|
||||
margin: 0 0 var(--space-md);
|
||||
color: var(--text);
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
/* First heading inside the section drops top padding to remove a redundant
|
||||
gap stacked on top of the settings-content container's own top padding. */
|
||||
.settings-content > .settings-section-heading:first-child,
|
||||
.settings-modal-section > .settings-section-heading:first-child {
|
||||
padding-top: var(--space-md);
|
||||
}
|
||||
|
||||
/* Spacing modifier for settings-section-heading that need extra top margin */
|
||||
.settings-section-heading--spaced {
|
||||
margin-top: var(--space-xl);
|
||||
margin-top: var(--space-lg);
|
||||
}
|
||||
|
||||
.settings-section-description {
|
||||
@@ -1485,8 +1494,8 @@
|
||||
}
|
||||
|
||||
.settings-section-heading {
|
||||
padding: var(--space-lg) 0 var(--space-md);
|
||||
margin: 0;
|
||||
padding: var(--space-lg) var(--space-lg) var(--space-md);
|
||||
margin: 0 0 var(--space-md);
|
||||
}
|
||||
|
||||
.settings-plugins-subsection-toggle {
|
||||
|
||||
@@ -1291,6 +1291,40 @@ describe("AgentsView", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("shows mobile zoom controls for org chart and keeps node selection working", async () => {
|
||||
mockViewportMode.mockReturnValue("mobile");
|
||||
mockFetchOrgTree.mockResolvedValue(orgTree);
|
||||
const { container } = render(<AgentsView addToast={mockAddToast} />);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Org Chart view" }));
|
||||
|
||||
const controls = await screen.findByTestId("agent-org-chart-controls");
|
||||
expect(controls).toBeTruthy();
|
||||
expect(screen.getByText("100%")).toBeTruthy();
|
||||
|
||||
const viewport = screen.getByTestId("agent-org-chart-viewport");
|
||||
expect(viewport).toBeTruthy();
|
||||
const canvas = container.querySelector(".agent-org-chart-canvas");
|
||||
expect(canvas?.className).toContain("agent-org-chart-canvas--zoom-100");
|
||||
|
||||
fireEvent.click(within(controls).getByTitle("Zoom in"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("125%")).toBeTruthy();
|
||||
expect(container.querySelector(".agent-org-chart-canvas")?.className).toContain("agent-org-chart-canvas--zoom-125");
|
||||
});
|
||||
|
||||
fireEvent.click(within(controls).getByTitle("Fit org chart"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("100%")).toBeTruthy();
|
||||
expect(container.querySelector(".agent-org-chart-canvas")?.className).toContain("agent-org-chart-canvas--zoom-100");
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByText("Director One"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("agent-detail-view")).toHaveTextContent("agent-child-1");
|
||||
});
|
||||
});
|
||||
|
||||
it("shows org chart empty state when API returns no nodes", async () => {
|
||||
mockFetchOrgTree.mockResolvedValue([]);
|
||||
render(<AgentsView addToast={mockAddToast} />);
|
||||
|
||||
@@ -259,7 +259,10 @@ describe("agents-view mobile CSS", () => {
|
||||
expect(block).toContain("flex-wrap: wrap");
|
||||
});
|
||||
|
||||
it("defines mobile org chart sizing rules", () => {
|
||||
it("defines mobile org chart sizing and pan/zoom controls rules", () => {
|
||||
expect(extractRuleBlock(mobileMediaBlock, ".agent-org-chart-controls")).toContain("display: flex");
|
||||
expect(extractRuleBlock(mobileMediaBlock, ".agent-org-chart-controls")).toContain("gap: var(--space-sm)");
|
||||
expect(extractRuleBlock(mobileMediaBlock, ".agent-org-chart-viewport")).toContain("min-height: calc(var(--space-2xl) * 4)");
|
||||
expect(extractRuleBlock(mobileMediaBlock, ".agent-org-chart")).toContain("gap: var(--space-sm)");
|
||||
expect(extractRuleBlock(mobileMediaBlock, ".agent-org-chart")).toContain("--org-chart-node-width: calc(var(--space-2xl) * 5)");
|
||||
expect(extractRuleBlock(mobileMediaBlock, ".org-chart-node-card")).toContain("padding: var(--space-sm)");
|
||||
|
||||
@@ -725,7 +725,7 @@ describe("POST /github/issues/batch-import", () => {
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/github/issues/batch-import",
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1, 2, 3], delayMs: 10 }),
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1, 2, 3], delayMs: 1 }),
|
||||
{ "Content-Type": "application/json" }
|
||||
);
|
||||
|
||||
@@ -776,7 +776,7 @@ describe("POST /github/issues/batch-import", () => {
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/github/issues/batch-import",
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1], delayMs: 10 }),
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1], delayMs: 1 }),
|
||||
{ "Content-Type": "application/json" }
|
||||
);
|
||||
|
||||
@@ -803,7 +803,7 @@ describe("POST /github/issues/batch-import", () => {
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/github/issues/batch-import",
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1], delayMs: 10 }),
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1], delayMs: 1 }),
|
||||
{ "Content-Type": "application/json" }
|
||||
);
|
||||
|
||||
@@ -872,7 +872,7 @@ describe("POST /github/issues/batch-import", () => {
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/github/issues/batch-import",
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1, 2, 3], delayMs: 10 }),
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1, 2, 3], delayMs: 1 }),
|
||||
{ "Content-Type": "application/json" }
|
||||
);
|
||||
|
||||
@@ -896,7 +896,7 @@ describe("POST /github/issues/batch-import", () => {
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/github/issues/batch-import",
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1], delayMs: 10 }),
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1], delayMs: 1 }),
|
||||
{ "Content-Type": "application/json" }
|
||||
);
|
||||
|
||||
@@ -915,7 +915,7 @@ describe("POST /github/issues/batch-import", () => {
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/github/issues/batch-import",
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1], delayMs: 10 }),
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1], delayMs: 1 }),
|
||||
{ "Content-Type": "application/json" }
|
||||
);
|
||||
|
||||
@@ -924,7 +924,7 @@ describe("POST /github/issues/batch-import", () => {
|
||||
expect(res.body.results[0].success).toBe(true);
|
||||
expect(res.body.results[0].taskId).toBeDefined();
|
||||
expect(throttledSpy).toHaveBeenCalledTimes(1);
|
||||
}, 10000); // Increase timeout for retry delay
|
||||
});
|
||||
|
||||
it("returns error after max retries exceeded on 429", async () => {
|
||||
const throttledSpy = vi.spyOn(GitHubClient.prototype, "fetchThrottled").mockResolvedValueOnce({
|
||||
@@ -969,7 +969,7 @@ describe("POST /github/issues/batch-import", () => {
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/github/issues/batch-import",
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1, 2, 3], delayMs: 50 }),
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1, 2, 3], delayMs: 1 }),
|
||||
{ "Content-Type": "application/json" }
|
||||
);
|
||||
|
||||
@@ -1025,7 +1025,7 @@ describe("POST /github/issues/batch-import", () => {
|
||||
buildApp(),
|
||||
"POST",
|
||||
"/api/github/issues/batch-import",
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1], delayMs: 10 }),
|
||||
JSON.stringify({ owner: "owner", repo: "repo", issueNumbers: [1], delayMs: 1 }),
|
||||
{ "Content-Type": "application/json" }
|
||||
);
|
||||
|
||||
@@ -1954,7 +1954,7 @@ describe("GET /tasks/:id/diff", () => {
|
||||
});
|
||||
|
||||
describe("done tasks with commit SHA", () => {
|
||||
it("attempts git diff when commitSha is present", { timeout: 30_000 }, async () => {
|
||||
it("attempts git diff when commitSha is present", async () => {
|
||||
const gitRepo = getSharedGitTestRepo();
|
||||
const localStore = createMockStore({
|
||||
getRootDir: vi.fn().mockReturnValue(gitRepo.repoDir),
|
||||
|
||||
Reference in New Issue
Block a user