mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-17 10:03:08 +00:00
* Changes from fix/bug-fixes * feat: Refactor worktree iteration and improve error logging across services * feat: Extract URL/port patterns to module level and fix abort condition * fix: Improve IPv6 loopback handling, select component layout, and terminal UI * feat: Add thinking level defaults and adjust list row padding * Update apps/ui/src/store/app-store.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * feat: Add worktree-aware terminal creation and split options, fix npm security issues from audit * feat: Add tracked remote detection to pull dialog flow * feat: Add merge state tracking to git operations * feat: Improve merge detection and add post-merge action preferences * Update apps/ui/src/components/views/board-view/dialogs/git-pull-dialog.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update apps/ui/src/components/views/board-view/dialogs/git-pull-dialog.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: Pass merge detection info to stash reapplication and handle merge state consistently * fix: Call onPulled callback in merge handlers and add validation checks --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
694 lines
20 KiB
TypeScript
694 lines
20 KiB
TypeScript
import { describe, it, expect, beforeEach, vi, type Mock } from 'vitest';
|
|
import {
|
|
ConcurrencyManager,
|
|
type RunningFeature,
|
|
type GetCurrentBranchFn,
|
|
} from '@/services/concurrency-manager.js';
|
|
|
|
describe('ConcurrencyManager', () => {
|
|
let manager: ConcurrencyManager;
|
|
let mockGetCurrentBranch: Mock<GetCurrentBranchFn>;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
// Default: primary branch is 'main'
|
|
mockGetCurrentBranch = vi.fn().mockResolvedValue('main');
|
|
manager = new ConcurrencyManager(mockGetCurrentBranch);
|
|
});
|
|
|
|
describe('acquire', () => {
|
|
it('should create new entry with leaseCount: 1 on first acquire', () => {
|
|
const result = manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
expect(result.featureId).toBe('feature-1');
|
|
expect(result.projectPath).toBe('/test/project');
|
|
expect(result.isAutoMode).toBe(true);
|
|
expect(result.leaseCount).toBe(1);
|
|
expect(result.worktreePath).toBeNull();
|
|
expect(result.branchName).toBeNull();
|
|
expect(result.startTime).toBeDefined();
|
|
expect(result.abortController).toBeInstanceOf(AbortController);
|
|
});
|
|
|
|
it('should increment leaseCount when allowReuse is true for existing feature', () => {
|
|
// First acquire
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
// Second acquire with allowReuse
|
|
const result = manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
allowReuse: true,
|
|
});
|
|
|
|
expect(result.leaseCount).toBe(2);
|
|
});
|
|
|
|
it('should throw "already running" when allowReuse is false for existing feature', () => {
|
|
// First acquire
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
// Second acquire without allowReuse
|
|
expect(() =>
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
})
|
|
).toThrow('already running');
|
|
});
|
|
|
|
it('should throw "already running" when allowReuse is explicitly false', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
expect(() =>
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
allowReuse: false,
|
|
})
|
|
).toThrow('already running');
|
|
});
|
|
|
|
it('should use provided abortController', () => {
|
|
const customAbortController = new AbortController();
|
|
|
|
const result = manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
abortController: customAbortController,
|
|
});
|
|
|
|
expect(result.abortController).toBe(customAbortController);
|
|
});
|
|
|
|
it('should return the existing entry when allowReuse is true', () => {
|
|
const first = manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
const second = manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
allowReuse: true,
|
|
});
|
|
|
|
// Should be the same object reference
|
|
expect(second).toBe(first);
|
|
});
|
|
|
|
it('should allow multiple nested acquire calls with allowReuse', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
allowReuse: true,
|
|
});
|
|
|
|
const result = manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
allowReuse: true,
|
|
});
|
|
|
|
expect(result.leaseCount).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe('release', () => {
|
|
it('should decrement leaseCount on release', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
allowReuse: true,
|
|
});
|
|
|
|
manager.release('feature-1');
|
|
|
|
const entry = manager.getRunningFeature('feature-1');
|
|
expect(entry?.leaseCount).toBe(1);
|
|
});
|
|
|
|
it('should delete entry when leaseCount reaches 0', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
manager.release('feature-1');
|
|
|
|
expect(manager.isRunning('feature-1')).toBe(false);
|
|
expect(manager.getRunningFeature('feature-1')).toBeUndefined();
|
|
});
|
|
|
|
it('should delete entry immediately when force is true regardless of leaseCount', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
allowReuse: true,
|
|
});
|
|
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
allowReuse: true,
|
|
});
|
|
|
|
// leaseCount is 3, but force should still delete
|
|
manager.release('feature-1', { force: true });
|
|
|
|
expect(manager.isRunning('feature-1')).toBe(false);
|
|
});
|
|
|
|
it('should do nothing when releasing non-existent feature', () => {
|
|
// Should not throw
|
|
manager.release('non-existent-feature');
|
|
manager.release('non-existent-feature', { force: true });
|
|
});
|
|
|
|
it('should only delete entry after all leases are released', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
allowReuse: true,
|
|
});
|
|
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
allowReuse: true,
|
|
});
|
|
|
|
// leaseCount is 3
|
|
manager.release('feature-1');
|
|
expect(manager.isRunning('feature-1')).toBe(true);
|
|
|
|
manager.release('feature-1');
|
|
expect(manager.isRunning('feature-1')).toBe(true);
|
|
|
|
manager.release('feature-1');
|
|
expect(manager.isRunning('feature-1')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isRunning', () => {
|
|
it('should return false when feature is not running', () => {
|
|
expect(manager.isRunning('feature-1')).toBe(false);
|
|
});
|
|
|
|
it('should return true when feature is running', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
expect(manager.isRunning('feature-1')).toBe(true);
|
|
});
|
|
|
|
it('should return false after feature is released', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
manager.release('feature-1');
|
|
|
|
expect(manager.isRunning('feature-1')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('getRunningFeature', () => {
|
|
it('should return undefined for non-existent feature', () => {
|
|
expect(manager.getRunningFeature('feature-1')).toBeUndefined();
|
|
});
|
|
|
|
it('should return the RunningFeature entry', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
const entry = manager.getRunningFeature('feature-1');
|
|
expect(entry).toBeDefined();
|
|
expect(entry?.featureId).toBe('feature-1');
|
|
expect(entry?.projectPath).toBe('/test/project');
|
|
});
|
|
});
|
|
|
|
describe('getRunningCount (project-level)', () => {
|
|
it('should return 0 when no features are running', () => {
|
|
expect(manager.getRunningCount('/test/project')).toBe(0);
|
|
});
|
|
|
|
it('should count features for specific project', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
manager.acquire({
|
|
featureId: 'feature-2',
|
|
projectPath: '/test/project',
|
|
isAutoMode: false,
|
|
});
|
|
|
|
expect(manager.getRunningCount('/test/project')).toBe(2);
|
|
});
|
|
|
|
it('should only count features for the specified project', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/project-a',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
manager.acquire({
|
|
featureId: 'feature-2',
|
|
projectPath: '/project-b',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
manager.acquire({
|
|
featureId: 'feature-3',
|
|
projectPath: '/project-a',
|
|
isAutoMode: false,
|
|
});
|
|
|
|
expect(manager.getRunningCount('/project-a')).toBe(2);
|
|
expect(manager.getRunningCount('/project-b')).toBe(1);
|
|
expect(manager.getRunningCount('/project-c')).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('getRunningCountForWorktree', () => {
|
|
it('should return 0 when no features are running', async () => {
|
|
const count = await manager.getRunningCountForWorktree('/test/project', null);
|
|
expect(count).toBe(0);
|
|
});
|
|
|
|
it('should count features with null branchName as main worktree', async () => {
|
|
const entry = manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
// entry.branchName is null by default
|
|
|
|
const count = await manager.getRunningCountForWorktree('/test/project', null);
|
|
expect(count).toBe(1);
|
|
});
|
|
|
|
it('should count features matching primary branch as main worktree', async () => {
|
|
mockGetCurrentBranch.mockResolvedValue('main');
|
|
|
|
const entry = manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
manager.updateRunningFeature('feature-1', { branchName: 'main' });
|
|
|
|
const count = await manager.getRunningCountForWorktree('/test/project', null);
|
|
expect(count).toBe(1);
|
|
});
|
|
|
|
it('should count features with exact branch match for feature worktrees', async () => {
|
|
const entry = manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
manager.updateRunningFeature('feature-1', { branchName: 'feature-branch' });
|
|
|
|
manager.acquire({
|
|
featureId: 'feature-2',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
// feature-2 has null branchName
|
|
|
|
const featureBranchCount = await manager.getRunningCountForWorktree(
|
|
'/test/project',
|
|
'feature-branch'
|
|
);
|
|
expect(featureBranchCount).toBe(1);
|
|
|
|
const mainWorktreeCount = await manager.getRunningCountForWorktree('/test/project', null);
|
|
expect(mainWorktreeCount).toBe(1);
|
|
});
|
|
|
|
it('should respect branch normalization (main is treated as null)', async () => {
|
|
mockGetCurrentBranch.mockResolvedValue('main');
|
|
|
|
// Feature with branchName 'main' should count as main worktree
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
manager.updateRunningFeature('feature-1', { branchName: 'main' });
|
|
|
|
// Feature with branchName null should also count as main worktree
|
|
manager.acquire({
|
|
featureId: 'feature-2',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
const mainCount = await manager.getRunningCountForWorktree('/test/project', null);
|
|
expect(mainCount).toBe(2);
|
|
});
|
|
|
|
it('should count only auto-mode features when autoModeOnly is true', async () => {
|
|
// Auto-mode feature on main worktree
|
|
manager.acquire({
|
|
featureId: 'feature-auto',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
// Manual feature on main worktree
|
|
manager.acquire({
|
|
featureId: 'feature-manual',
|
|
projectPath: '/test/project',
|
|
isAutoMode: false,
|
|
});
|
|
|
|
// Without autoModeOnly: counts both
|
|
const totalCount = await manager.getRunningCountForWorktree('/test/project', null);
|
|
expect(totalCount).toBe(2);
|
|
|
|
// With autoModeOnly: counts only auto-mode features
|
|
const autoModeCount = await manager.getRunningCountForWorktree('/test/project', null, {
|
|
autoModeOnly: true,
|
|
});
|
|
expect(autoModeCount).toBe(1);
|
|
});
|
|
|
|
it('should count only auto-mode features on specific worktree when autoModeOnly is true', async () => {
|
|
// Auto-mode feature on feature branch
|
|
manager.acquire({
|
|
featureId: 'feature-auto',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
manager.updateRunningFeature('feature-auto', { branchName: 'feature-branch' });
|
|
|
|
// Manual feature on same feature branch
|
|
manager.acquire({
|
|
featureId: 'feature-manual',
|
|
projectPath: '/test/project',
|
|
isAutoMode: false,
|
|
});
|
|
manager.updateRunningFeature('feature-manual', { branchName: 'feature-branch' });
|
|
|
|
// Another auto-mode feature on different branch (should not be counted)
|
|
manager.acquire({
|
|
featureId: 'feature-other',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
manager.updateRunningFeature('feature-other', { branchName: 'other-branch' });
|
|
|
|
const autoModeCount = await manager.getRunningCountForWorktree(
|
|
'/test/project',
|
|
'feature-branch',
|
|
{ autoModeOnly: true }
|
|
);
|
|
expect(autoModeCount).toBe(1);
|
|
|
|
const totalCount = await manager.getRunningCountForWorktree(
|
|
'/test/project',
|
|
'feature-branch'
|
|
);
|
|
expect(totalCount).toBe(2);
|
|
});
|
|
|
|
it('should return 0 when autoModeOnly is true and only manual features are running', async () => {
|
|
manager.acquire({
|
|
featureId: 'feature-manual-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: false,
|
|
});
|
|
|
|
manager.acquire({
|
|
featureId: 'feature-manual-2',
|
|
projectPath: '/test/project',
|
|
isAutoMode: false,
|
|
});
|
|
|
|
const autoModeCount = await manager.getRunningCountForWorktree('/test/project', null, {
|
|
autoModeOnly: true,
|
|
});
|
|
expect(autoModeCount).toBe(0);
|
|
});
|
|
|
|
it('should filter by both projectPath and branchName', async () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/project-a',
|
|
isAutoMode: true,
|
|
});
|
|
manager.updateRunningFeature('feature-1', { branchName: 'feature-x' });
|
|
|
|
manager.acquire({
|
|
featureId: 'feature-2',
|
|
projectPath: '/project-b',
|
|
isAutoMode: true,
|
|
});
|
|
manager.updateRunningFeature('feature-2', { branchName: 'feature-x' });
|
|
|
|
const countA = await manager.getRunningCountForWorktree('/project-a', 'feature-x');
|
|
const countB = await manager.getRunningCountForWorktree('/project-b', 'feature-x');
|
|
|
|
expect(countA).toBe(1);
|
|
expect(countB).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('getAllRunning', () => {
|
|
it('should return empty array when no features are running', () => {
|
|
expect(manager.getAllRunning()).toEqual([]);
|
|
});
|
|
|
|
it('should return array with all running features', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/project-a',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
manager.acquire({
|
|
featureId: 'feature-2',
|
|
projectPath: '/project-b',
|
|
isAutoMode: false,
|
|
});
|
|
|
|
const running = manager.getAllRunning();
|
|
expect(running).toHaveLength(2);
|
|
expect(running.map((r) => r.featureId)).toContain('feature-1');
|
|
expect(running.map((r) => r.featureId)).toContain('feature-2');
|
|
});
|
|
|
|
it('should include feature metadata', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/project-a',
|
|
isAutoMode: true,
|
|
});
|
|
manager.updateRunningFeature('feature-1', { model: 'claude-sonnet-4', provider: 'claude' });
|
|
|
|
const running = manager.getAllRunning();
|
|
expect(running[0].model).toBe('claude-sonnet-4');
|
|
expect(running[0].provider).toBe('claude');
|
|
});
|
|
});
|
|
|
|
describe('updateRunningFeature', () => {
|
|
it('should update worktreePath and branchName', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
manager.updateRunningFeature('feature-1', {
|
|
worktreePath: '/worktrees/feature-1',
|
|
branchName: 'feature-1-branch',
|
|
});
|
|
|
|
const entry = manager.getRunningFeature('feature-1');
|
|
expect(entry?.worktreePath).toBe('/worktrees/feature-1');
|
|
expect(entry?.branchName).toBe('feature-1-branch');
|
|
});
|
|
|
|
it('should update model and provider', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
manager.updateRunningFeature('feature-1', {
|
|
model: 'claude-opus-4-5-20251101',
|
|
provider: 'claude',
|
|
});
|
|
|
|
const entry = manager.getRunningFeature('feature-1');
|
|
expect(entry?.model).toBe('claude-opus-4-5-20251101');
|
|
expect(entry?.provider).toBe('claude');
|
|
});
|
|
|
|
it('should do nothing for non-existent feature', () => {
|
|
// Should not throw
|
|
manager.updateRunningFeature('non-existent', { model: 'test' });
|
|
});
|
|
|
|
it('should preserve other properties when updating partial fields', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
const original = manager.getRunningFeature('feature-1');
|
|
const originalStartTime = original?.startTime;
|
|
|
|
manager.updateRunningFeature('feature-1', { model: 'claude-sonnet-4' });
|
|
|
|
const updated = manager.getRunningFeature('feature-1');
|
|
expect(updated?.startTime).toBe(originalStartTime);
|
|
expect(updated?.projectPath).toBe('/test/project');
|
|
expect(updated?.isAutoMode).toBe(true);
|
|
expect(updated?.model).toBe('claude-sonnet-4');
|
|
});
|
|
});
|
|
|
|
describe('edge cases', () => {
|
|
it('should handle multiple features for same project', () => {
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
manager.acquire({
|
|
featureId: 'feature-2',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
manager.acquire({
|
|
featureId: 'feature-3',
|
|
projectPath: '/test/project',
|
|
isAutoMode: false,
|
|
});
|
|
|
|
expect(manager.getRunningCount('/test/project')).toBe(3);
|
|
expect(manager.isRunning('feature-1')).toBe(true);
|
|
expect(manager.isRunning('feature-2')).toBe(true);
|
|
expect(manager.isRunning('feature-3')).toBe(true);
|
|
});
|
|
|
|
it('should handle features across different worktrees', async () => {
|
|
// Main worktree feature
|
|
manager.acquire({
|
|
featureId: 'feature-1',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
|
|
// Worktree A feature
|
|
manager.acquire({
|
|
featureId: 'feature-2',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
manager.updateRunningFeature('feature-2', {
|
|
worktreePath: '/worktrees/a',
|
|
branchName: 'branch-a',
|
|
});
|
|
|
|
// Worktree B feature
|
|
manager.acquire({
|
|
featureId: 'feature-3',
|
|
projectPath: '/test/project',
|
|
isAutoMode: true,
|
|
});
|
|
manager.updateRunningFeature('feature-3', {
|
|
worktreePath: '/worktrees/b',
|
|
branchName: 'branch-b',
|
|
});
|
|
|
|
expect(await manager.getRunningCountForWorktree('/test/project', null)).toBe(1);
|
|
expect(await manager.getRunningCountForWorktree('/test/project', 'branch-a')).toBe(1);
|
|
expect(await manager.getRunningCountForWorktree('/test/project', 'branch-b')).toBe(1);
|
|
expect(manager.getRunningCount('/test/project')).toBe(3);
|
|
});
|
|
|
|
it('should return 0 counts and empty arrays for empty state', () => {
|
|
expect(manager.getRunningCount('/any/project')).toBe(0);
|
|
expect(manager.getAllRunning()).toEqual([]);
|
|
expect(manager.isRunning('any-feature')).toBe(false);
|
|
expect(manager.getRunningFeature('any-feature')).toBeUndefined();
|
|
});
|
|
});
|
|
});
|