takt: add-task-instruction-doc (#174)
This commit is contained in:
parent
a481346945
commit
6f937b70b5
@ -37,7 +37,9 @@ describe('listTasks non-interactive text output', () => {
|
||||
|
||||
// Then
|
||||
const calls = logSpy.mock.calls.map((c) => c[0] as string);
|
||||
expect(calls).toContainEqual(expect.stringContaining('[pending] my-task'));
|
||||
expect(calls).toContainEqual(expect.stringContaining('[running] my-task'));
|
||||
expect(calls).not.toContainEqual(expect.stringContaining('[pending] my-task'));
|
||||
expect(calls).not.toContainEqual(expect.stringContaining('[pendig] my-task'));
|
||||
expect(calls).toContainEqual(expect.stringContaining('Fix the login bug'));
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
@ -77,7 +79,9 @@ describe('listTasks non-interactive text output', () => {
|
||||
|
||||
// Then
|
||||
const calls = logSpy.mock.calls.map((c) => c[0] as string);
|
||||
expect(calls).toContainEqual(expect.stringContaining('[pending] pending-one'));
|
||||
expect(calls).toContainEqual(expect.stringContaining('[running] pending-one'));
|
||||
expect(calls).not.toContainEqual(expect.stringContaining('[pending] pending-one'));
|
||||
expect(calls).not.toContainEqual(expect.stringContaining('[pendig] pending-one'));
|
||||
expect(calls).toContainEqual(expect.stringContaining('[failed] failed-one'));
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
|
||||
109
src/__tests__/listTasksInteractivePendingLabel.test.ts
Normal file
109
src/__tests__/listTasksInteractivePendingLabel.test.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { TaskListItem } from '../infra/task/types.js';
|
||||
|
||||
const {
|
||||
mockSelectOption,
|
||||
mockHeader,
|
||||
mockInfo,
|
||||
mockBlankLine,
|
||||
mockConfirm,
|
||||
mockListPendingTaskItems,
|
||||
mockListFailedTasks,
|
||||
mockDeletePendingTask,
|
||||
} = vi.hoisted(() => ({
|
||||
mockSelectOption: vi.fn(),
|
||||
mockHeader: vi.fn(),
|
||||
mockInfo: vi.fn(),
|
||||
mockBlankLine: vi.fn(),
|
||||
mockConfirm: vi.fn(),
|
||||
mockListPendingTaskItems: vi.fn(),
|
||||
mockListFailedTasks: vi.fn(),
|
||||
mockDeletePendingTask: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../infra/task/index.js', () => ({
|
||||
listTaktBranches: vi.fn(() => []),
|
||||
buildListItems: vi.fn(() => []),
|
||||
detectDefaultBranch: vi.fn(() => 'main'),
|
||||
TaskRunner: class {
|
||||
listPendingTaskItems() {
|
||||
return mockListPendingTaskItems();
|
||||
}
|
||||
listFailedTasks() {
|
||||
return mockListFailedTasks();
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../shared/prompt/index.js', () => ({
|
||||
selectOption: mockSelectOption,
|
||||
confirm: mockConfirm,
|
||||
}));
|
||||
|
||||
vi.mock('../shared/ui/index.js', () => ({
|
||||
info: mockInfo,
|
||||
header: mockHeader,
|
||||
blankLine: mockBlankLine,
|
||||
}));
|
||||
|
||||
vi.mock('../features/tasks/list/taskActions.js', () => ({
|
||||
showFullDiff: vi.fn(),
|
||||
showDiffAndPromptAction: vi.fn(),
|
||||
tryMergeBranch: vi.fn(),
|
||||
mergeBranch: vi.fn(),
|
||||
deleteBranch: vi.fn(),
|
||||
instructBranch: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../features/tasks/list/taskDeleteActions.js', () => ({
|
||||
deletePendingTask: mockDeletePendingTask,
|
||||
deleteFailedTask: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../features/tasks/list/taskRetryActions.js', () => ({
|
||||
retryFailedTask: vi.fn(),
|
||||
}));
|
||||
|
||||
import { listTasks } from '../features/tasks/list/index.js';
|
||||
|
||||
describe('listTasks interactive pending label regression', () => {
|
||||
const pendingTask: TaskListItem = {
|
||||
kind: 'pending',
|
||||
name: 'my-task',
|
||||
createdAt: '2026-02-09T00:00:00',
|
||||
filePath: '/tmp/my-task.md',
|
||||
content: 'Fix running status label',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockListPendingTaskItems.mockReturnValue([pendingTask]);
|
||||
mockListFailedTasks.mockReturnValue([]);
|
||||
});
|
||||
|
||||
it('should show [running] in interactive menu for pending tasks', async () => {
|
||||
mockSelectOption.mockResolvedValueOnce(null);
|
||||
|
||||
await listTasks('/project');
|
||||
|
||||
expect(mockSelectOption).toHaveBeenCalledTimes(1);
|
||||
const menuOptions = mockSelectOption.mock.calls[0]![1] as Array<{ label: string; value: string }>;
|
||||
expect(menuOptions).toContainEqual(expect.objectContaining({ label: '[running] my-task', value: 'pending:0' }));
|
||||
expect(menuOptions.some((opt) => opt.label.includes('[pending]'))).toBe(false);
|
||||
expect(menuOptions.some((opt) => opt.label.includes('[pendig]'))).toBe(false);
|
||||
});
|
||||
|
||||
it('should show [running] header when pending task is selected', async () => {
|
||||
mockSelectOption
|
||||
.mockResolvedValueOnce('pending:0')
|
||||
.mockResolvedValueOnce(null)
|
||||
.mockResolvedValueOnce(null);
|
||||
|
||||
await listTasks('/project');
|
||||
|
||||
expect(mockHeader).toHaveBeenCalledWith('[running] my-task');
|
||||
const headerTexts = mockHeader.mock.calls.map(([text]) => String(text));
|
||||
expect(headerTexts.some((text) => text.includes('[pending]'))).toBe(false);
|
||||
expect(headerTexts.some((text) => text.includes('[pendig]'))).toBe(false);
|
||||
});
|
||||
});
|
||||
@ -30,6 +30,7 @@ import {
|
||||
import { deletePendingTask, deleteFailedTask } from './taskDeleteActions.js';
|
||||
import { retryFailedTask } from './taskRetryActions.js';
|
||||
import { listTasksNonInteractive, type ListNonInteractiveOptions } from './listNonInteractive.js';
|
||||
import { formatTaskStatusLabel } from './taskStatusLabel.js';
|
||||
|
||||
export type { ListNonInteractiveOptions } from './listNonInteractive.js';
|
||||
|
||||
@ -54,7 +55,7 @@ type FailedTaskAction = 'retry' | 'delete';
|
||||
* Returns the selected action, or null if cancelled.
|
||||
*/
|
||||
async function showPendingTaskAndPromptAction(task: TaskListItem): Promise<PendingTaskAction | null> {
|
||||
header(`[${task.kind}] ${task.name}`);
|
||||
header(formatTaskStatusLabel(task));
|
||||
info(` Created: ${task.createdAt}`);
|
||||
if (task.content) {
|
||||
info(` ${task.content}`);
|
||||
@ -72,7 +73,7 @@ async function showPendingTaskAndPromptAction(task: TaskListItem): Promise<Pendi
|
||||
* Returns the selected action, or null if cancelled.
|
||||
*/
|
||||
async function showFailedTaskAndPromptAction(task: TaskListItem): Promise<FailedTaskAction | null> {
|
||||
header(`[${task.kind}] ${task.name}`);
|
||||
header(formatTaskStatusLabel(task));
|
||||
info(` Failed at: ${task.createdAt}`);
|
||||
if (task.content) {
|
||||
info(` ${task.content}`);
|
||||
@ -129,12 +130,12 @@ export async function listTasks(
|
||||
};
|
||||
}),
|
||||
...pendingTasks.map((task, idx) => ({
|
||||
label: `[pending] ${task.name}`,
|
||||
label: formatTaskStatusLabel(task),
|
||||
value: `pending:${idx}`,
|
||||
description: task.content,
|
||||
})),
|
||||
...failedTasks.map((task, idx) => ({
|
||||
label: `[failed] ${task.name}`,
|
||||
label: formatTaskStatusLabel(task),
|
||||
value: `failed:${idx}`,
|
||||
description: task.content,
|
||||
})),
|
||||
|
||||
@ -20,6 +20,7 @@ import {
|
||||
mergeBranch,
|
||||
deleteBranch,
|
||||
} from './taskActions.js';
|
||||
import { formatTaskStatusLabel } from './taskStatusLabel.js';
|
||||
|
||||
export interface ListNonInteractiveOptions {
|
||||
enabled: boolean;
|
||||
@ -56,11 +57,11 @@ function printNonInteractiveList(
|
||||
}
|
||||
|
||||
for (const task of pendingTasks) {
|
||||
info(`[pending] ${task.name} - ${task.content}`);
|
||||
info(`${formatTaskStatusLabel(task)} - ${task.content}`);
|
||||
}
|
||||
|
||||
for (const task of failedTasks) {
|
||||
info(`[failed] ${task.name} - ${task.content}`);
|
||||
info(`${formatTaskStatusLabel(task)} - ${task.content}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
10
src/features/tasks/list/taskStatusLabel.ts
Normal file
10
src/features/tasks/list/taskStatusLabel.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import type { TaskListItem } from '../../../infra/task/index.js';
|
||||
|
||||
const TASK_STATUS_BY_KIND: Record<TaskListItem['kind'], string> = {
|
||||
pending: 'running',
|
||||
failed: 'failed',
|
||||
};
|
||||
|
||||
export function formatTaskStatusLabel(task: TaskListItem): string {
|
||||
return `[${TASK_STATUS_BY_KIND[task.kind]}] ${task.name}`;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user