takt/src/__tests__/runSelector.test.ts
nrslib 620e384251 interactiveモジュールの分割とタスク再キュー方式への移行
interactive.tsからsummary/runSelector/runSessionReader/selectorUtilsを分離し、
run session参照をrouting層からinstructMode層に移動。instructBranchで新タスク
作成の代わりに既存タスクのrequeueを使用する方式に変更。worktree確認プロンプトを
廃止し常時有効化。
2026-02-18 18:49:21 +09:00

92 lines
2.9 KiB
TypeScript

/**
* Tests for runSelector
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
vi.mock('../shared/prompt/index.js', () => ({
selectOption: vi.fn(),
}));
vi.mock('../shared/i18n/index.js', () => ({
getLabel: vi.fn((key: string) => key),
}));
vi.mock('../shared/ui/index.js', () => ({
info: vi.fn(),
}));
vi.mock('../features/interactive/runSessionReader.js', () => ({
listRecentRuns: vi.fn(),
}));
import { selectOption } from '../shared/prompt/index.js';
import { info } from '../shared/ui/index.js';
import { listRecentRuns } from '../features/interactive/runSessionReader.js';
import { selectRun } from '../features/interactive/runSelector.js';
const mockListRecentRuns = vi.mocked(listRecentRuns);
const mockSelectOption = vi.mocked(selectOption);
const mockInfo = vi.mocked(info);
describe('selectRun', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should return null and show message when no runs exist', async () => {
mockListRecentRuns.mockReturnValue([]);
const result = await selectRun('/some/path', 'en');
expect(result).toBeNull();
expect(mockInfo).toHaveBeenCalledWith('interactive.runSelector.noRuns');
});
it('should present run options and return selected slug', async () => {
mockListRecentRuns.mockReturnValue([
{ slug: 'run-1', task: 'First task', piece: 'default', status: 'completed', startTime: '2026-02-01T10:00:00Z' },
{ slug: 'run-2', task: 'Second task', piece: 'custom', status: 'aborted', startTime: '2026-01-15T08:00:00Z' },
]);
mockSelectOption.mockResolvedValue('run-1');
const result = await selectRun('/some/path', 'en');
expect(result).toBe('run-1');
expect(mockSelectOption).toHaveBeenCalledTimes(1);
const callArgs = mockSelectOption.mock.calls[0];
expect(callArgs[0]).toBe('interactive.runSelector.prompt');
const options = callArgs[1];
expect(options).toHaveLength(2);
expect(options[0].value).toBe('run-1');
expect(options[0].label).toBe('First task');
expect(options[1].value).toBe('run-2');
expect(options[1].label).toBe('Second task');
});
it('should return null when user cancels selection', async () => {
mockListRecentRuns.mockReturnValue([
{ slug: 'run-1', task: 'Task', piece: 'default', status: 'completed', startTime: '2026-02-01T00:00:00Z' },
]);
mockSelectOption.mockResolvedValue(null);
const result = await selectRun('/some/path', 'en');
expect(result).toBeNull();
});
it('should truncate long task labels', async () => {
const longTask = 'A'.repeat(100);
mockListRecentRuns.mockReturnValue([
{ slug: 'run-1', task: longTask, piece: 'default', status: 'completed', startTime: '2026-02-01T00:00:00Z' },
]);
mockSelectOption.mockResolvedValue('run-1');
await selectRun('/some/path', 'en');
const options = mockSelectOption.mock.calls[0][1];
expect(options[0].label.length).toBeLessThanOrEqual(61); // 60 + '…'
});
});