mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 08:33:36 +00:00
feat(tests): implement test runner functionality with API integration
- Added Test Runner Service to manage test execution processes for worktrees. - Introduced endpoints for starting and stopping tests, and retrieving test logs. - Created UI components for displaying test logs and managing test sessions. - Integrated test runner events for real-time updates in the UI. - Updated project settings to include configurable test commands. This enhancement allows users to run tests directly from the UI, view logs in real-time, and manage test sessions effectively.
This commit is contained in:
@@ -33,10 +33,11 @@ import {
|
||||
SplitSquareHorizontal,
|
||||
Undo2,
|
||||
Zap,
|
||||
FlaskConical,
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { WorktreeInfo, DevServerInfo, PRInfo, GitRepoStatus } from '../types';
|
||||
import type { WorktreeInfo, DevServerInfo, PRInfo, GitRepoStatus, TestSessionInfo } from '../types';
|
||||
import { TooltipWrapper } from './tooltip-wrapper';
|
||||
import { useAvailableEditors, useEffectiveDefaultEditor } from '../hooks/use-available-editors';
|
||||
import {
|
||||
@@ -63,6 +64,14 @@ interface WorktreeActionsDropdownProps {
|
||||
standalone?: boolean;
|
||||
/** Whether auto mode is running for this worktree */
|
||||
isAutoModeRunning?: boolean;
|
||||
/** Whether a test command is configured in project settings */
|
||||
hasTestCommand?: boolean;
|
||||
/** Whether tests are being started for this worktree */
|
||||
isStartingTests?: boolean;
|
||||
/** Whether tests are currently running for this worktree */
|
||||
isTestRunning?: boolean;
|
||||
/** Active test session info for this worktree */
|
||||
testSessionInfo?: TestSessionInfo;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onPull: (worktree: WorktreeInfo) => void;
|
||||
onPush: (worktree: WorktreeInfo) => void;
|
||||
@@ -84,6 +93,12 @@ interface WorktreeActionsDropdownProps {
|
||||
onRunInitScript: (worktree: WorktreeInfo) => void;
|
||||
onToggleAutoMode?: (worktree: WorktreeInfo) => void;
|
||||
onMerge: (worktree: WorktreeInfo) => void;
|
||||
/** Start running tests for this worktree */
|
||||
onStartTests?: (worktree: WorktreeInfo) => void;
|
||||
/** Stop running tests for this worktree */
|
||||
onStopTests?: (worktree: WorktreeInfo) => void;
|
||||
/** View test logs for this worktree */
|
||||
onViewTestLogs?: (worktree: WorktreeInfo) => void;
|
||||
hasInitScript: boolean;
|
||||
}
|
||||
|
||||
@@ -101,6 +116,10 @@ export function WorktreeActionsDropdown({
|
||||
gitRepoStatus,
|
||||
standalone = false,
|
||||
isAutoModeRunning = false,
|
||||
hasTestCommand = false,
|
||||
isStartingTests = false,
|
||||
isTestRunning = false,
|
||||
testSessionInfo,
|
||||
onOpenChange,
|
||||
onPull,
|
||||
onPush,
|
||||
@@ -122,6 +141,9 @@ export function WorktreeActionsDropdown({
|
||||
onRunInitScript,
|
||||
onToggleAutoMode,
|
||||
onMerge,
|
||||
onStartTests,
|
||||
onStopTests,
|
||||
onViewTestLogs,
|
||||
hasInitScript,
|
||||
}: WorktreeActionsDropdownProps) {
|
||||
// Get available editors for the "Open In" submenu
|
||||
@@ -231,6 +253,65 @@ export function WorktreeActionsDropdown({
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
{/* Test Runner section - only show when test command is configured */}
|
||||
{hasTestCommand && onStartTests && (
|
||||
<>
|
||||
{isTestRunning ? (
|
||||
<>
|
||||
<DropdownMenuLabel className="text-xs flex items-center gap-2">
|
||||
<span className="w-2 h-2 rounded-full bg-blue-500 animate-pulse" />
|
||||
Tests Running
|
||||
</DropdownMenuLabel>
|
||||
{onViewTestLogs && (
|
||||
<DropdownMenuItem onClick={() => onViewTestLogs(worktree)} className="text-xs">
|
||||
<ScrollText className="w-3.5 h-3.5 mr-2" />
|
||||
View Test Logs
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{onStopTests && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => onStopTests(worktree)}
|
||||
className="text-xs text-destructive focus:text-destructive"
|
||||
>
|
||||
<Square className="w-3.5 h-3.5 mr-2" />
|
||||
Stop Tests
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<DropdownMenuItem
|
||||
onClick={() => onStartTests(worktree)}
|
||||
disabled={isStartingTests}
|
||||
className="text-xs"
|
||||
>
|
||||
<FlaskConical
|
||||
className={cn('w-3.5 h-3.5 mr-2', isStartingTests && 'animate-pulse')}
|
||||
/>
|
||||
{isStartingTests ? 'Starting Tests...' : 'Run Tests'}
|
||||
</DropdownMenuItem>
|
||||
{onViewTestLogs && testSessionInfo && (
|
||||
<DropdownMenuItem onClick={() => onViewTestLogs(worktree)} className="text-xs">
|
||||
<ScrollText className="w-3.5 h-3.5 mr-2" />
|
||||
View Last Test Results
|
||||
{testSessionInfo.status === 'passed' && (
|
||||
<span className="ml-auto text-[10px] bg-green-500/20 text-green-600 px-1.5 py-0.5 rounded">
|
||||
passed
|
||||
</span>
|
||||
)}
|
||||
{testSessionInfo.status === 'failed' && (
|
||||
<span className="ml-auto text-[10px] bg-red-500/20 text-red-600 px-1.5 py-0.5 rounded">
|
||||
failed
|
||||
</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* Auto Mode toggle */}
|
||||
{onToggleAutoMode && (
|
||||
<>
|
||||
|
||||
@@ -5,7 +5,14 @@ import { Spinner } from '@/components/ui/spinner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { useDroppable } from '@dnd-kit/core';
|
||||
import type { WorktreeInfo, BranchInfo, DevServerInfo, PRInfo, GitRepoStatus } from '../types';
|
||||
import type {
|
||||
WorktreeInfo,
|
||||
BranchInfo,
|
||||
DevServerInfo,
|
||||
PRInfo,
|
||||
GitRepoStatus,
|
||||
TestSessionInfo,
|
||||
} from '../types';
|
||||
import { BranchSwitchDropdown } from './branch-switch-dropdown';
|
||||
import { WorktreeActionsDropdown } from './worktree-actions-dropdown';
|
||||
|
||||
@@ -33,6 +40,12 @@ interface WorktreeTabProps {
|
||||
gitRepoStatus: GitRepoStatus;
|
||||
/** Whether auto mode is running for this worktree */
|
||||
isAutoModeRunning?: boolean;
|
||||
/** Whether tests are being started for this worktree */
|
||||
isStartingTests?: boolean;
|
||||
/** Whether tests are currently running for this worktree */
|
||||
isTestRunning?: boolean;
|
||||
/** Active test session info for this worktree */
|
||||
testSessionInfo?: TestSessionInfo;
|
||||
onSelectWorktree: (worktree: WorktreeInfo) => void;
|
||||
onBranchDropdownOpenChange: (open: boolean) => void;
|
||||
onActionsDropdownOpenChange: (open: boolean) => void;
|
||||
@@ -59,7 +72,15 @@ interface WorktreeTabProps {
|
||||
onViewDevServerLogs: (worktree: WorktreeInfo) => void;
|
||||
onRunInitScript: (worktree: WorktreeInfo) => void;
|
||||
onToggleAutoMode?: (worktree: WorktreeInfo) => void;
|
||||
/** Start running tests for this worktree */
|
||||
onStartTests?: (worktree: WorktreeInfo) => void;
|
||||
/** Stop running tests for this worktree */
|
||||
onStopTests?: (worktree: WorktreeInfo) => void;
|
||||
/** View test logs for this worktree */
|
||||
onViewTestLogs?: (worktree: WorktreeInfo) => void;
|
||||
hasInitScript: boolean;
|
||||
/** Whether a test command is configured in project settings */
|
||||
hasTestCommand?: boolean;
|
||||
}
|
||||
|
||||
export function WorktreeTab({
|
||||
@@ -85,6 +106,9 @@ export function WorktreeTab({
|
||||
hasRemoteBranch,
|
||||
gitRepoStatus,
|
||||
isAutoModeRunning = false,
|
||||
isStartingTests = false,
|
||||
isTestRunning = false,
|
||||
testSessionInfo,
|
||||
onSelectWorktree,
|
||||
onBranchDropdownOpenChange,
|
||||
onActionsDropdownOpenChange,
|
||||
@@ -111,7 +135,11 @@ export function WorktreeTab({
|
||||
onViewDevServerLogs,
|
||||
onRunInitScript,
|
||||
onToggleAutoMode,
|
||||
onStartTests,
|
||||
onStopTests,
|
||||
onViewTestLogs,
|
||||
hasInitScript,
|
||||
hasTestCommand = false,
|
||||
}: WorktreeTabProps) {
|
||||
// Make the worktree tab a drop target for feature cards
|
||||
const { setNodeRef, isOver } = useDroppable({
|
||||
@@ -395,6 +423,10 @@ export function WorktreeTab({
|
||||
devServerInfo={devServerInfo}
|
||||
gitRepoStatus={gitRepoStatus}
|
||||
isAutoModeRunning={isAutoModeRunning}
|
||||
hasTestCommand={hasTestCommand}
|
||||
isStartingTests={isStartingTests}
|
||||
isTestRunning={isTestRunning}
|
||||
testSessionInfo={testSessionInfo}
|
||||
onOpenChange={onActionsDropdownOpenChange}
|
||||
onPull={onPull}
|
||||
onPush={onPush}
|
||||
@@ -416,6 +448,9 @@ export function WorktreeTab({
|
||||
onViewDevServerLogs={onViewDevServerLogs}
|
||||
onRunInitScript={onRunInitScript}
|
||||
onToggleAutoMode={onToggleAutoMode}
|
||||
onStartTests={onStartTests}
|
||||
onStopTests={onStopTests}
|
||||
onViewTestLogs={onViewTestLogs}
|
||||
hasInitScript={hasInitScript}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -30,6 +30,19 @@ export interface DevServerInfo {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface TestSessionInfo {
|
||||
sessionId: string;
|
||||
worktreePath: string;
|
||||
/** The test command being run (from project settings) */
|
||||
command: string;
|
||||
status: 'pending' | 'running' | 'passed' | 'failed' | 'cancelled';
|
||||
testFile?: string;
|
||||
startedAt: string;
|
||||
finishedAt?: string;
|
||||
exitCode?: number | null;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
export interface FeatureInfo {
|
||||
id: string;
|
||||
branchName?: string;
|
||||
|
||||
@@ -6,8 +6,15 @@ import { pathsEqual } from '@/lib/utils';
|
||||
import { toast } from 'sonner';
|
||||
import { getHttpApiClient } from '@/lib/http-api-client';
|
||||
import { useIsMobile } from '@/hooks/use-media-query';
|
||||
import { useWorktreeInitScript } from '@/hooks/queries';
|
||||
import type { WorktreePanelProps, WorktreeInfo } from './types';
|
||||
import { useWorktreeInitScript, useProjectSettings } from '@/hooks/queries';
|
||||
import { useTestRunnerEvents } from '@/hooks/use-test-runners';
|
||||
import { useTestRunnersStore } from '@/store/test-runners-store';
|
||||
import type {
|
||||
TestRunnerStartedEvent,
|
||||
TestRunnerOutputEvent,
|
||||
TestRunnerCompletedEvent,
|
||||
} from '@/types/electron';
|
||||
import type { WorktreePanelProps, WorktreeInfo, TestSessionInfo } from './types';
|
||||
import {
|
||||
useWorktrees,
|
||||
useDevServers,
|
||||
@@ -25,6 +32,7 @@ import {
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { ViewWorktreeChangesDialog, PushToRemoteDialog, MergeWorktreeDialog } from '../dialogs';
|
||||
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||
import { TestLogsPanel } from '@/components/ui/test-logs-panel';
|
||||
import { Undo2 } from 'lucide-react';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
|
||||
@@ -161,6 +169,191 @@ export function WorktreePanel({
|
||||
const { data: initScriptData } = useWorktreeInitScript(projectPath);
|
||||
const hasInitScript = initScriptData?.exists ?? false;
|
||||
|
||||
// Check if test command is configured in project settings
|
||||
const { data: projectSettings } = useProjectSettings(projectPath);
|
||||
const hasTestCommand = !!projectSettings?.testCommand;
|
||||
|
||||
// Test runner state management
|
||||
// Use the test runners store to get global state for all worktrees
|
||||
const testRunnersStore = useTestRunnersStore();
|
||||
const [isStartingTests, setIsStartingTests] = useState(false);
|
||||
|
||||
// Subscribe to test runner events to update store state in real-time
|
||||
// This ensures the UI updates when tests start, output is received, or tests complete
|
||||
useTestRunnerEvents(
|
||||
// onStarted - a new test run has begun
|
||||
useCallback(
|
||||
(event: TestRunnerStartedEvent) => {
|
||||
testRunnersStore.startSession({
|
||||
sessionId: event.sessionId,
|
||||
worktreePath: event.worktreePath,
|
||||
command: event.command,
|
||||
status: 'running',
|
||||
testFile: event.testFile,
|
||||
startedAt: event.timestamp,
|
||||
});
|
||||
},
|
||||
[testRunnersStore]
|
||||
),
|
||||
// onOutput - test output received
|
||||
useCallback(
|
||||
(event: TestRunnerOutputEvent) => {
|
||||
testRunnersStore.appendOutput(event.sessionId, event.content);
|
||||
},
|
||||
[testRunnersStore]
|
||||
),
|
||||
// onCompleted - test run finished
|
||||
useCallback(
|
||||
(event: TestRunnerCompletedEvent) => {
|
||||
testRunnersStore.completeSession(
|
||||
event.sessionId,
|
||||
event.status,
|
||||
event.exitCode,
|
||||
event.duration
|
||||
);
|
||||
// Show toast notification for test completion
|
||||
const statusEmoji =
|
||||
event.status === 'passed' ? '✅' : event.status === 'failed' ? '❌' : '⏹️';
|
||||
const statusText =
|
||||
event.status === 'passed' ? 'passed' : event.status === 'failed' ? 'failed' : 'stopped';
|
||||
toast(`${statusEmoji} Tests ${statusText}`, {
|
||||
description: `Exit code: ${event.exitCode ?? 'N/A'}`,
|
||||
duration: 4000,
|
||||
});
|
||||
},
|
||||
[testRunnersStore]
|
||||
)
|
||||
);
|
||||
|
||||
// Test logs panel state
|
||||
const [testLogsPanelOpen, setTestLogsPanelOpen] = useState(false);
|
||||
const [testLogsPanelWorktree, setTestLogsPanelWorktree] = useState<WorktreeInfo | null>(null);
|
||||
|
||||
// Helper to check if tests are running for a specific worktree
|
||||
const isTestRunningForWorktree = useCallback(
|
||||
(worktree: WorktreeInfo): boolean => {
|
||||
return testRunnersStore.isWorktreeRunning(worktree.path);
|
||||
},
|
||||
[testRunnersStore]
|
||||
);
|
||||
|
||||
// Helper to get test session info for a specific worktree
|
||||
const getTestSessionInfo = useCallback(
|
||||
(worktree: WorktreeInfo): TestSessionInfo | undefined => {
|
||||
const session = testRunnersStore.getActiveSession(worktree.path);
|
||||
if (!session) {
|
||||
// Check for completed sessions to show last result
|
||||
const allSessions = Object.values(testRunnersStore.sessions).filter(
|
||||
(s) => s.worktreePath === worktree.path
|
||||
);
|
||||
const lastSession = allSessions.sort(
|
||||
(a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime()
|
||||
)[0];
|
||||
if (lastSession) {
|
||||
return {
|
||||
sessionId: lastSession.sessionId,
|
||||
worktreePath: lastSession.worktreePath,
|
||||
command: lastSession.command,
|
||||
status: lastSession.status as TestSessionInfo['status'],
|
||||
testFile: lastSession.testFile,
|
||||
startedAt: lastSession.startedAt,
|
||||
finishedAt: lastSession.finishedAt,
|
||||
exitCode: lastSession.exitCode,
|
||||
duration: lastSession.duration,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
sessionId: session.sessionId,
|
||||
worktreePath: session.worktreePath,
|
||||
command: session.command,
|
||||
status: session.status as TestSessionInfo['status'],
|
||||
testFile: session.testFile,
|
||||
startedAt: session.startedAt,
|
||||
finishedAt: session.finishedAt,
|
||||
exitCode: session.exitCode,
|
||||
duration: session.duration,
|
||||
};
|
||||
},
|
||||
[testRunnersStore]
|
||||
);
|
||||
|
||||
// Handler to start tests for a worktree
|
||||
const handleStartTests = useCallback(async (worktree: WorktreeInfo) => {
|
||||
setIsStartingTests(true);
|
||||
try {
|
||||
const api = getElectronAPI();
|
||||
if (!api?.worktree?.startTests) {
|
||||
toast.error('Test runner API not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await api.worktree.startTests(worktree.path, { projectPath });
|
||||
if (result.success) {
|
||||
toast.success('Tests started', {
|
||||
description: `Running tests in ${worktree.branch}`,
|
||||
});
|
||||
} else {
|
||||
toast.error('Failed to start tests', {
|
||||
description: result.error || 'Unknown error',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Failed to start tests', {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
} finally {
|
||||
setIsStartingTests(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Handler to stop tests for a worktree
|
||||
const handleStopTests = useCallback(
|
||||
async (worktree: WorktreeInfo) => {
|
||||
try {
|
||||
const session = testRunnersStore.getActiveSession(worktree.path);
|
||||
if (!session) {
|
||||
toast.error('No active test session to stop');
|
||||
return;
|
||||
}
|
||||
|
||||
const api = getElectronAPI();
|
||||
if (!api?.worktree?.stopTests) {
|
||||
toast.error('Test runner API not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await api.worktree.stopTests(session.sessionId);
|
||||
if (result.success) {
|
||||
toast.success('Tests stopped', {
|
||||
description: `Stopped tests in ${worktree.branch}`,
|
||||
});
|
||||
} else {
|
||||
toast.error('Failed to stop tests', {
|
||||
description: result.error || 'Unknown error',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error('Failed to stop tests', {
|
||||
description: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
},
|
||||
[testRunnersStore]
|
||||
);
|
||||
|
||||
// Handler to view test logs for a worktree
|
||||
const handleViewTestLogs = useCallback((worktree: WorktreeInfo) => {
|
||||
setTestLogsPanelWorktree(worktree);
|
||||
setTestLogsPanelOpen(true);
|
||||
}, []);
|
||||
|
||||
// Handler to close test logs panel
|
||||
const handleCloseTestLogsPanel = useCallback(() => {
|
||||
setTestLogsPanelOpen(false);
|
||||
}, []);
|
||||
|
||||
// View changes dialog state
|
||||
const [viewChangesDialogOpen, setViewChangesDialogOpen] = useState(false);
|
||||
const [viewChangesWorktree, setViewChangesWorktree] = useState<WorktreeInfo | null>(null);
|
||||
@@ -392,6 +585,10 @@ export function WorktreePanel({
|
||||
devServerInfo={getDevServerInfo(selectedWorktree)}
|
||||
gitRepoStatus={gitRepoStatus}
|
||||
isAutoModeRunning={isAutoModeRunningForWorktree(selectedWorktree)}
|
||||
hasTestCommand={hasTestCommand}
|
||||
isStartingTests={isStartingTests}
|
||||
isTestRunning={isTestRunningForWorktree(selectedWorktree)}
|
||||
testSessionInfo={getTestSessionInfo(selectedWorktree)}
|
||||
onOpenChange={handleActionsDropdownOpenChange(selectedWorktree)}
|
||||
onPull={handlePull}
|
||||
onPush={handlePush}
|
||||
@@ -413,7 +610,11 @@ export function WorktreePanel({
|
||||
onViewDevServerLogs={handleViewDevServerLogs}
|
||||
onRunInitScript={handleRunInitScript}
|
||||
onToggleAutoMode={handleToggleAutoMode}
|
||||
onStartTests={handleStartTests}
|
||||
onStopTests={handleStopTests}
|
||||
onViewTestLogs={handleViewTestLogs}
|
||||
hasInitScript={hasInitScript}
|
||||
hasTestCommand={hasTestCommand}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -494,6 +695,17 @@ export function WorktreePanel({
|
||||
onMerged={handleMerged}
|
||||
onCreateConflictResolutionFeature={onCreateMergeConflictResolutionFeature}
|
||||
/>
|
||||
|
||||
{/* Test Logs Panel */}
|
||||
<TestLogsPanel
|
||||
open={testLogsPanelOpen}
|
||||
onClose={handleCloseTestLogsPanel}
|
||||
worktreePath={testLogsPanelWorktree?.path ?? null}
|
||||
branch={testLogsPanelWorktree?.branch}
|
||||
onStopTests={
|
||||
testLogsPanelWorktree ? () => handleStopTests(testLogsPanelWorktree) : undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -530,6 +742,9 @@ export function WorktreePanel({
|
||||
hasRemoteBranch={hasRemoteBranch}
|
||||
gitRepoStatus={gitRepoStatus}
|
||||
isAutoModeRunning={isAutoModeRunningForWorktree(mainWorktree)}
|
||||
isStartingTests={isStartingTests}
|
||||
isTestRunning={isTestRunningForWorktree(mainWorktree)}
|
||||
testSessionInfo={getTestSessionInfo(mainWorktree)}
|
||||
onSelectWorktree={handleSelectWorktree}
|
||||
onBranchDropdownOpenChange={handleBranchDropdownOpenChange(mainWorktree)}
|
||||
onActionsDropdownOpenChange={handleActionsDropdownOpenChange(mainWorktree)}
|
||||
@@ -556,7 +771,11 @@ export function WorktreePanel({
|
||||
onViewDevServerLogs={handleViewDevServerLogs}
|
||||
onRunInitScript={handleRunInitScript}
|
||||
onToggleAutoMode={handleToggleAutoMode}
|
||||
onStartTests={handleStartTests}
|
||||
onStopTests={handleStopTests}
|
||||
onViewTestLogs={handleViewTestLogs}
|
||||
hasInitScript={hasInitScript}
|
||||
hasTestCommand={hasTestCommand}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -596,6 +815,9 @@ export function WorktreePanel({
|
||||
hasRemoteBranch={hasRemoteBranch}
|
||||
gitRepoStatus={gitRepoStatus}
|
||||
isAutoModeRunning={isAutoModeRunningForWorktree(worktree)}
|
||||
isStartingTests={isStartingTests}
|
||||
isTestRunning={isTestRunningForWorktree(worktree)}
|
||||
testSessionInfo={getTestSessionInfo(worktree)}
|
||||
onSelectWorktree={handleSelectWorktree}
|
||||
onBranchDropdownOpenChange={handleBranchDropdownOpenChange(worktree)}
|
||||
onActionsDropdownOpenChange={handleActionsDropdownOpenChange(worktree)}
|
||||
@@ -622,7 +844,11 @@ export function WorktreePanel({
|
||||
onViewDevServerLogs={handleViewDevServerLogs}
|
||||
onRunInitScript={handleRunInitScript}
|
||||
onToggleAutoMode={handleToggleAutoMode}
|
||||
onStartTests={handleStartTests}
|
||||
onStopTests={handleStopTests}
|
||||
onViewTestLogs={handleViewTestLogs}
|
||||
hasInitScript={hasInitScript}
|
||||
hasTestCommand={hasTestCommand}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -703,6 +929,17 @@ export function WorktreePanel({
|
||||
onMerged={handleMerged}
|
||||
onCreateConflictResolutionFeature={onCreateMergeConflictResolutionFeature}
|
||||
/>
|
||||
|
||||
{/* Test Logs Panel */}
|
||||
<TestLogsPanel
|
||||
open={testLogsPanelOpen}
|
||||
onClose={handleCloseTestLogsPanel}
|
||||
worktreePath={testLogsPanelWorktree?.path ?? null}
|
||||
branch={testLogsPanelWorktree?.branch}
|
||||
onStopTests={
|
||||
testLogsPanelWorktree ? () => handleStopTests(testLogsPanelWorktree) : undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user