chore: more linting
This commit is contained in:
@@ -1,404 +1,404 @@
|
|||||||
/**
|
/**
|
||||||
* Tests for the add-task.js module
|
* Tests for the add-task.js module
|
||||||
*/
|
*/
|
||||||
import { jest } from "@jest/globals";
|
import { jest } from '@jest/globals';
|
||||||
|
|
||||||
// Mock the dependencies before importing the module under test
|
// Mock the dependencies before importing the module under test
|
||||||
jest.unstable_mockModule("../../../../../scripts/modules/utils.js", () => ({
|
jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
|
||||||
readJSON: jest.fn(),
|
readJSON: jest.fn(),
|
||||||
writeJSON: jest.fn(),
|
writeJSON: jest.fn(),
|
||||||
log: jest.fn(),
|
log: jest.fn(),
|
||||||
CONFIG: {
|
CONFIG: {
|
||||||
model: "mock-claude-model",
|
model: 'mock-claude-model',
|
||||||
maxTokens: 4000,
|
maxTokens: 4000,
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
debug: false,
|
debug: false
|
||||||
},
|
},
|
||||||
truncate: jest.fn((text) => text),
|
truncate: jest.fn((text) => text)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.unstable_mockModule("../../../../../scripts/modules/ui.js", () => ({
|
jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({
|
||||||
displayBanner: jest.fn(),
|
displayBanner: jest.fn(),
|
||||||
getStatusWithColor: jest.fn((status) => status),
|
getStatusWithColor: jest.fn((status) => status),
|
||||||
startLoadingIndicator: jest.fn(),
|
startLoadingIndicator: jest.fn(),
|
||||||
stopLoadingIndicator: jest.fn(),
|
stopLoadingIndicator: jest.fn(),
|
||||||
succeedLoadingIndicator: jest.fn(),
|
succeedLoadingIndicator: jest.fn(),
|
||||||
failLoadingIndicator: jest.fn(),
|
failLoadingIndicator: jest.fn(),
|
||||||
warnLoadingIndicator: jest.fn(),
|
warnLoadingIndicator: jest.fn(),
|
||||||
infoLoadingIndicator: jest.fn(),
|
infoLoadingIndicator: jest.fn(),
|
||||||
displayAiUsageSummary: jest.fn(),
|
displayAiUsageSummary: jest.fn()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.unstable_mockModule(
|
jest.unstable_mockModule(
|
||||||
"../../../../../scripts/modules/ai-services-unified.js",
|
'../../../../../scripts/modules/ai-services-unified.js',
|
||||||
() => ({
|
() => ({
|
||||||
generateObjectService: jest.fn().mockResolvedValue({
|
generateObjectService: jest.fn().mockResolvedValue({
|
||||||
mainResult: {
|
mainResult: {
|
||||||
object: {
|
object: {
|
||||||
title: "Task from prompt: Create a new authentication system",
|
title: 'Task from prompt: Create a new authentication system',
|
||||||
description:
|
description:
|
||||||
"Task generated from: Create a new authentication system",
|
'Task generated from: Create a new authentication system',
|
||||||
details:
|
details:
|
||||||
"Implementation details for task generated from prompt: Create a new authentication system",
|
'Implementation details for task generated from prompt: Create a new authentication system',
|
||||||
testStrategy: "Write unit tests to verify functionality",
|
testStrategy: 'Write unit tests to verify functionality',
|
||||||
dependencies: [],
|
dependencies: []
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
telemetryData: {
|
telemetryData: {
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
userId: "1234567890",
|
userId: '1234567890',
|
||||||
commandName: "add-task",
|
commandName: 'add-task',
|
||||||
modelUsed: "claude-3-5-sonnet",
|
modelUsed: 'claude-3-5-sonnet',
|
||||||
providerName: "anthropic",
|
providerName: 'anthropic',
|
||||||
inputTokens: 1000,
|
inputTokens: 1000,
|
||||||
outputTokens: 500,
|
outputTokens: 500,
|
||||||
totalTokens: 1500,
|
totalTokens: 1500,
|
||||||
totalCost: 0.012414,
|
totalCost: 0.012414,
|
||||||
currency: "USD",
|
currency: 'USD'
|
||||||
},
|
}
|
||||||
}),
|
})
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
jest.unstable_mockModule(
|
jest.unstable_mockModule(
|
||||||
"../../../../../scripts/modules/config-manager.js",
|
'../../../../../scripts/modules/config-manager.js',
|
||||||
() => ({
|
() => ({
|
||||||
getDefaultPriority: jest.fn(() => "medium"),
|
getDefaultPriority: jest.fn(() => 'medium')
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
jest.unstable_mockModule(
|
jest.unstable_mockModule(
|
||||||
"../../../../../scripts/modules/task-manager/generate-task-files.js",
|
'../../../../../scripts/modules/task-manager/generate-task-files.js',
|
||||||
() => ({
|
() => ({
|
||||||
default: jest.fn().mockResolvedValue(),
|
default: jest.fn().mockResolvedValue()
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mock external UI libraries
|
// Mock external UI libraries
|
||||||
jest.unstable_mockModule("chalk", () => ({
|
jest.unstable_mockModule('chalk', () => ({
|
||||||
default: {
|
default: {
|
||||||
white: { bold: jest.fn((text) => text) },
|
white: { bold: jest.fn((text) => text) },
|
||||||
cyan: Object.assign(
|
cyan: Object.assign(
|
||||||
jest.fn((text) => text),
|
jest.fn((text) => text),
|
||||||
{
|
{
|
||||||
bold: jest.fn((text) => text),
|
bold: jest.fn((text) => text)
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
green: jest.fn((text) => text),
|
green: jest.fn((text) => text),
|
||||||
yellow: jest.fn((text) => text),
|
yellow: jest.fn((text) => text),
|
||||||
bold: jest.fn((text) => text),
|
bold: jest.fn((text) => text)
|
||||||
},
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.unstable_mockModule("boxen", () => ({
|
jest.unstable_mockModule('boxen', () => ({
|
||||||
default: jest.fn((text) => text),
|
default: jest.fn((text) => text)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.unstable_mockModule("cli-table3", () => ({
|
jest.unstable_mockModule('cli-table3', () => ({
|
||||||
default: jest.fn().mockImplementation(() => ({
|
default: jest.fn().mockImplementation(() => ({
|
||||||
push: jest.fn(),
|
push: jest.fn(),
|
||||||
toString: jest.fn(() => "mocked table"),
|
toString: jest.fn(() => 'mocked table')
|
||||||
})),
|
}))
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Import the mocked modules
|
// Import the mocked modules
|
||||||
const { readJSON, writeJSON, log } = await import(
|
const { readJSON, writeJSON, log } = await import(
|
||||||
"../../../../../scripts/modules/utils.js"
|
'../../../../../scripts/modules/utils.js'
|
||||||
);
|
);
|
||||||
|
|
||||||
const { generateObjectService } = await import(
|
const { generateObjectService } = await import(
|
||||||
"../../../../../scripts/modules/ai-services-unified.js"
|
'../../../../../scripts/modules/ai-services-unified.js'
|
||||||
);
|
);
|
||||||
|
|
||||||
const generateTaskFiles = await import(
|
const generateTaskFiles = await import(
|
||||||
"../../../../../scripts/modules/task-manager/generate-task-files.js"
|
'../../../../../scripts/modules/task-manager/generate-task-files.js'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Import the module under test
|
// Import the module under test
|
||||||
const { default: addTask } = await import(
|
const { default: addTask } = await import(
|
||||||
"../../../../../scripts/modules/task-manager/add-task.js"
|
'../../../../../scripts/modules/task-manager/add-task.js'
|
||||||
);
|
);
|
||||||
|
|
||||||
describe("addTask", () => {
|
describe('addTask', () => {
|
||||||
const sampleTasks = {
|
const sampleTasks = {
|
||||||
tasks: [
|
tasks: [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "Task 1",
|
title: 'Task 1',
|
||||||
description: "First task",
|
description: 'First task',
|
||||||
status: "pending",
|
status: 'pending',
|
||||||
dependencies: [],
|
dependencies: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
title: "Task 2",
|
title: 'Task 2',
|
||||||
description: "Second task",
|
description: 'Second task',
|
||||||
status: "pending",
|
status: 'pending',
|
||||||
dependencies: [],
|
dependencies: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
title: "Task 3",
|
title: 'Task 3',
|
||||||
description: "Third task",
|
description: 'Third task',
|
||||||
status: "pending",
|
status: 'pending',
|
||||||
dependencies: [1],
|
dependencies: [1]
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a helper function for consistent mcpLog mock
|
// Create a helper function for consistent mcpLog mock
|
||||||
const createMcpLogMock = () => ({
|
const createMcpLogMock = () => ({
|
||||||
info: jest.fn(),
|
info: jest.fn(),
|
||||||
warn: jest.fn(),
|
warn: jest.fn(),
|
||||||
error: jest.fn(),
|
error: jest.fn(),
|
||||||
debug: jest.fn(),
|
debug: jest.fn(),
|
||||||
success: jest.fn(),
|
success: jest.fn()
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
readJSON.mockReturnValue(JSON.parse(JSON.stringify(sampleTasks)));
|
readJSON.mockReturnValue(JSON.parse(JSON.stringify(sampleTasks)));
|
||||||
|
|
||||||
// Mock console.log to avoid output during tests
|
// Mock console.log to avoid output during tests
|
||||||
jest.spyOn(console, "log").mockImplementation(() => {});
|
jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
console.log.mockRestore();
|
console.log.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should add a new task using AI", async () => {
|
test('should add a new task using AI', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const prompt = "Create a new authentication system";
|
const prompt = 'Create a new authentication system';
|
||||||
const context = {
|
const context = {
|
||||||
mcpLog: createMcpLogMock(),
|
mcpLog: createMcpLogMock()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const result = await addTask(
|
const result = await addTask(
|
||||||
"tasks/tasks.json",
|
'tasks/tasks.json',
|
||||||
prompt,
|
prompt,
|
||||||
[],
|
[],
|
||||||
"medium",
|
'medium',
|
||||||
context,
|
context,
|
||||||
"json"
|
'json'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(readJSON).toHaveBeenCalledWith("tasks/tasks.json");
|
expect(readJSON).toHaveBeenCalledWith('tasks/tasks.json');
|
||||||
expect(generateObjectService).toHaveBeenCalledWith(expect.any(Object));
|
expect(generateObjectService).toHaveBeenCalledWith(expect.any(Object));
|
||||||
expect(writeJSON).toHaveBeenCalledWith(
|
expect(writeJSON).toHaveBeenCalledWith(
|
||||||
"tasks/tasks.json",
|
'tasks/tasks.json',
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
tasks: expect.arrayContaining([
|
tasks: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: 4, // Next ID after existing tasks
|
id: 4, // Next ID after existing tasks
|
||||||
title: expect.stringContaining(
|
title: expect.stringContaining(
|
||||||
"Create a new authentication system"
|
'Create a new authentication system'
|
||||||
),
|
),
|
||||||
status: "pending",
|
status: 'pending'
|
||||||
}),
|
})
|
||||||
]),
|
])
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(generateTaskFiles.default).toHaveBeenCalled();
|
expect(generateTaskFiles.default).toHaveBeenCalled();
|
||||||
expect(result).toEqual(
|
expect(result).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
newTaskId: 4,
|
newTaskId: 4,
|
||||||
telemetryData: expect.any(Object),
|
telemetryData: expect.any(Object)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should validate dependencies when adding a task", async () => {
|
test('should validate dependencies when adding a task', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const prompt = "Create a new authentication system";
|
const prompt = 'Create a new authentication system';
|
||||||
const validDependencies = [1, 2]; // These exist in sampleTasks
|
const validDependencies = [1, 2]; // These exist in sampleTasks
|
||||||
const context = {
|
const context = {
|
||||||
mcpLog: createMcpLogMock(),
|
mcpLog: createMcpLogMock()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const result = await addTask(
|
const result = await addTask(
|
||||||
"tasks/tasks.json",
|
'tasks/tasks.json',
|
||||||
prompt,
|
prompt,
|
||||||
validDependencies,
|
validDependencies,
|
||||||
"medium",
|
'medium',
|
||||||
context,
|
context,
|
||||||
"json"
|
'json'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(writeJSON).toHaveBeenCalledWith(
|
expect(writeJSON).toHaveBeenCalledWith(
|
||||||
"tasks/tasks.json",
|
'tasks/tasks.json',
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
tasks: expect.arrayContaining([
|
tasks: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: 4,
|
id: 4,
|
||||||
dependencies: validDependencies,
|
dependencies: validDependencies
|
||||||
}),
|
})
|
||||||
]),
|
])
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should filter out invalid dependencies", async () => {
|
test('should filter out invalid dependencies', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const prompt = "Create a new authentication system";
|
const prompt = 'Create a new authentication system';
|
||||||
const invalidDependencies = [999]; // Non-existent task ID
|
const invalidDependencies = [999]; // Non-existent task ID
|
||||||
const context = { mcpLog: createMcpLogMock() };
|
const context = { mcpLog: createMcpLogMock() };
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const result = await addTask(
|
const result = await addTask(
|
||||||
"tasks/tasks.json",
|
'tasks/tasks.json',
|
||||||
prompt,
|
prompt,
|
||||||
invalidDependencies,
|
invalidDependencies,
|
||||||
"medium",
|
'medium',
|
||||||
context,
|
context,
|
||||||
"json"
|
'json'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(writeJSON).toHaveBeenCalledWith(
|
expect(writeJSON).toHaveBeenCalledWith(
|
||||||
"tasks/tasks.json",
|
'tasks/tasks.json',
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
tasks: expect.arrayContaining([
|
tasks: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: 4,
|
id: 4,
|
||||||
dependencies: [], // Invalid dependencies should be filtered out
|
dependencies: [] // Invalid dependencies should be filtered out
|
||||||
}),
|
})
|
||||||
]),
|
])
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(context.mcpLog.warn).toHaveBeenCalledWith(
|
expect(context.mcpLog.warn).toHaveBeenCalledWith(
|
||||||
expect.stringContaining(
|
expect.stringContaining(
|
||||||
"The following dependencies do not exist or are invalid: 999"
|
'The following dependencies do not exist or are invalid: 999'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should use specified priority", async () => {
|
test('should use specified priority', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const prompt = "Create a new authentication system";
|
const prompt = 'Create a new authentication system';
|
||||||
const priority = "high";
|
const priority = 'high';
|
||||||
const context = {
|
const context = {
|
||||||
mcpLog: createMcpLogMock(),
|
mcpLog: createMcpLogMock()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await addTask("tasks/tasks.json", prompt, [], priority, context, "json");
|
await addTask('tasks/tasks.json', prompt, [], priority, context, 'json');
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(writeJSON).toHaveBeenCalledWith(
|
expect(writeJSON).toHaveBeenCalledWith(
|
||||||
"tasks/tasks.json",
|
'tasks/tasks.json',
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
tasks: expect.arrayContaining([
|
tasks: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
priority: priority,
|
priority: priority
|
||||||
}),
|
})
|
||||||
]),
|
])
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle empty tasks file", async () => {
|
test('should handle empty tasks file', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
readJSON.mockReturnValue({ tasks: [] });
|
readJSON.mockReturnValue({ tasks: [] });
|
||||||
const prompt = "Create a new authentication system";
|
const prompt = 'Create a new authentication system';
|
||||||
const context = {
|
const context = {
|
||||||
mcpLog: createMcpLogMock(),
|
mcpLog: createMcpLogMock()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const result = await addTask(
|
const result = await addTask(
|
||||||
"tasks/tasks.json",
|
'tasks/tasks.json',
|
||||||
prompt,
|
prompt,
|
||||||
[],
|
[],
|
||||||
"medium",
|
'medium',
|
||||||
context,
|
context,
|
||||||
"json"
|
'json'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(result.newTaskId).toBe(1); // First task should have ID 1
|
expect(result.newTaskId).toBe(1); // First task should have ID 1
|
||||||
expect(writeJSON).toHaveBeenCalledWith(
|
expect(writeJSON).toHaveBeenCalledWith(
|
||||||
"tasks/tasks.json",
|
'tasks/tasks.json',
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
tasks: expect.arrayContaining([
|
tasks: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: 1,
|
id: 1
|
||||||
}),
|
})
|
||||||
]),
|
])
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle missing tasks file", async () => {
|
test('should handle missing tasks file', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
readJSON.mockReturnValue(null);
|
readJSON.mockReturnValue(null);
|
||||||
const prompt = "Create a new authentication system";
|
const prompt = 'Create a new authentication system';
|
||||||
const context = {
|
const context = {
|
||||||
mcpLog: createMcpLogMock(),
|
mcpLog: createMcpLogMock()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const result = await addTask(
|
const result = await addTask(
|
||||||
"tasks/tasks.json",
|
'tasks/tasks.json',
|
||||||
prompt,
|
prompt,
|
||||||
[],
|
[],
|
||||||
"medium",
|
'medium',
|
||||||
context,
|
context,
|
||||||
"json"
|
'json'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(result.newTaskId).toBe(1); // First task should have ID 1
|
expect(result.newTaskId).toBe(1); // First task should have ID 1
|
||||||
expect(writeJSON).toHaveBeenCalledTimes(2); // Once to create file, once to add task
|
expect(writeJSON).toHaveBeenCalledTimes(2); // Once to create file, once to add task
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle AI service errors", async () => {
|
test('should handle AI service errors', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
generateObjectService.mockRejectedValueOnce(new Error("AI service failed"));
|
generateObjectService.mockRejectedValueOnce(new Error('AI service failed'));
|
||||||
const prompt = "Create a new authentication system";
|
const prompt = 'Create a new authentication system';
|
||||||
const context = {
|
const context = {
|
||||||
mcpLog: createMcpLogMock(),
|
mcpLog: createMcpLogMock()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
await expect(
|
await expect(
|
||||||
addTask("tasks/tasks.json", prompt, [], "medium", context, "json")
|
addTask('tasks/tasks.json', prompt, [], 'medium', context, 'json')
|
||||||
).rejects.toThrow("AI service failed");
|
).rejects.toThrow('AI service failed');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle file read errors", async () => {
|
test('should handle file read errors', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
readJSON.mockImplementation(() => {
|
readJSON.mockImplementation(() => {
|
||||||
throw new Error("File read failed");
|
throw new Error('File read failed');
|
||||||
});
|
});
|
||||||
const prompt = "Create a new authentication system";
|
const prompt = 'Create a new authentication system';
|
||||||
const context = {
|
const context = {
|
||||||
mcpLog: createMcpLogMock(),
|
mcpLog: createMcpLogMock()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
await expect(
|
await expect(
|
||||||
addTask("tasks/tasks.json", prompt, [], "medium", context, "json")
|
addTask('tasks/tasks.json', prompt, [], 'medium', context, 'json')
|
||||||
).rejects.toThrow("File read failed");
|
).rejects.toThrow('File read failed');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle file write errors", async () => {
|
test('should handle file write errors', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
writeJSON.mockImplementation(() => {
|
writeJSON.mockImplementation(() => {
|
||||||
throw new Error("File write failed");
|
throw new Error('File write failed');
|
||||||
});
|
});
|
||||||
const prompt = "Create a new authentication system";
|
const prompt = 'Create a new authentication system';
|
||||||
const context = {
|
const context = {
|
||||||
mcpLog: createMcpLogMock(),
|
mcpLog: createMcpLogMock()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
await expect(
|
await expect(
|
||||||
addTask("tasks/tasks.json", prompt, [], "medium", context, "json")
|
addTask('tasks/tasks.json', prompt, [], 'medium', context, 'json')
|
||||||
).rejects.toThrow("File write failed");
|
).rejects.toThrow('File write failed');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,245 +2,245 @@
|
|||||||
* UI module tests
|
* UI module tests
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { jest } from "@jest/globals";
|
import { jest } from '@jest/globals';
|
||||||
import {
|
import {
|
||||||
getStatusWithColor,
|
getStatusWithColor,
|
||||||
formatDependenciesWithStatus,
|
formatDependenciesWithStatus,
|
||||||
createProgressBar,
|
createProgressBar,
|
||||||
getComplexityWithColor,
|
getComplexityWithColor
|
||||||
} from "../../scripts/modules/ui.js";
|
} from '../../scripts/modules/ui.js';
|
||||||
import { sampleTasks } from "../fixtures/sample-tasks.js";
|
import { sampleTasks } from '../fixtures/sample-tasks.js';
|
||||||
|
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
jest.mock("chalk", () => {
|
jest.mock('chalk', () => {
|
||||||
const origChalkFn = (text) => text;
|
const origChalkFn = (text) => text;
|
||||||
const chalk = origChalkFn;
|
const chalk = origChalkFn;
|
||||||
chalk.green = (text) => text; // Return text as-is for status functions
|
chalk.green = (text) => text; // Return text as-is for status functions
|
||||||
chalk.yellow = (text) => text;
|
chalk.yellow = (text) => text;
|
||||||
chalk.red = (text) => text;
|
chalk.red = (text) => text;
|
||||||
chalk.cyan = (text) => text;
|
chalk.cyan = (text) => text;
|
||||||
chalk.blue = (text) => text;
|
chalk.blue = (text) => text;
|
||||||
chalk.gray = (text) => text;
|
chalk.gray = (text) => text;
|
||||||
chalk.white = (text) => text;
|
chalk.white = (text) => text;
|
||||||
chalk.bold = (text) => text;
|
chalk.bold = (text) => text;
|
||||||
chalk.dim = (text) => text;
|
chalk.dim = (text) => text;
|
||||||
|
|
||||||
// Add hex and other methods
|
// Add hex and other methods
|
||||||
chalk.hex = () => origChalkFn;
|
chalk.hex = () => origChalkFn;
|
||||||
chalk.rgb = () => origChalkFn;
|
chalk.rgb = () => origChalkFn;
|
||||||
|
|
||||||
return chalk;
|
return chalk;
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock("figlet", () => ({
|
jest.mock('figlet', () => ({
|
||||||
textSync: jest.fn(() => "Task Master Banner"),
|
textSync: jest.fn(() => 'Task Master Banner')
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("boxen", () => jest.fn((text) => `[boxed: ${text}]`));
|
jest.mock('boxen', () => jest.fn((text) => `[boxed: ${text}]`));
|
||||||
|
|
||||||
jest.mock("ora", () =>
|
jest.mock('ora', () =>
|
||||||
jest.fn(() => ({
|
jest.fn(() => ({
|
||||||
start: jest.fn(),
|
start: jest.fn(),
|
||||||
succeed: jest.fn(),
|
succeed: jest.fn(),
|
||||||
fail: jest.fn(),
|
fail: jest.fn(),
|
||||||
stop: jest.fn(),
|
stop: jest.fn()
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
jest.mock("cli-table3", () =>
|
jest.mock('cli-table3', () =>
|
||||||
jest.fn().mockImplementation(() => ({
|
jest.fn().mockImplementation(() => ({
|
||||||
push: jest.fn(),
|
push: jest.fn(),
|
||||||
toString: jest.fn(() => "Table Content"),
|
toString: jest.fn(() => 'Table Content')
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
jest.mock("gradient-string", () => jest.fn(() => jest.fn((text) => text)));
|
jest.mock('gradient-string', () => jest.fn(() => jest.fn((text) => text)));
|
||||||
|
|
||||||
jest.mock("../../scripts/modules/utils.js", () => ({
|
jest.mock('../../scripts/modules/utils.js', () => ({
|
||||||
CONFIG: {
|
CONFIG: {
|
||||||
projectName: "Test Project",
|
projectName: 'Test Project',
|
||||||
projectVersion: "1.0.0",
|
projectVersion: '1.0.0'
|
||||||
},
|
},
|
||||||
log: jest.fn(),
|
log: jest.fn(),
|
||||||
findTaskById: jest.fn(),
|
findTaskById: jest.fn(),
|
||||||
readJSON: jest.fn(),
|
readJSON: jest.fn(),
|
||||||
readComplexityReport: jest.fn(),
|
readComplexityReport: jest.fn(),
|
||||||
truncate: jest.fn((text) => text),
|
truncate: jest.fn((text) => text)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("../../scripts/modules/task-manager.js", () => ({
|
jest.mock('../../scripts/modules/task-manager.js', () => ({
|
||||||
findNextTask: jest.fn(),
|
findNextTask: jest.fn(),
|
||||||
analyzeTaskComplexity: jest.fn(),
|
analyzeTaskComplexity: jest.fn()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe("UI Module", () => {
|
describe('UI Module', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getStatusWithColor function", () => {
|
describe('getStatusWithColor function', () => {
|
||||||
test("should return done status with emoji for console output", () => {
|
test('should return done status with emoji for console output', () => {
|
||||||
const result = getStatusWithColor("done");
|
const result = getStatusWithColor('done');
|
||||||
expect(result).toMatch(/done/);
|
expect(result).toMatch(/done/);
|
||||||
expect(result).toContain("✓");
|
expect(result).toContain('✓');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return pending status with emoji for console output", () => {
|
test('should return pending status with emoji for console output', () => {
|
||||||
const result = getStatusWithColor("pending");
|
const result = getStatusWithColor('pending');
|
||||||
expect(result).toMatch(/pending/);
|
expect(result).toMatch(/pending/);
|
||||||
expect(result).toContain("○");
|
expect(result).toContain('○');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return deferred status with emoji for console output", () => {
|
test('should return deferred status with emoji for console output', () => {
|
||||||
const result = getStatusWithColor("deferred");
|
const result = getStatusWithColor('deferred');
|
||||||
expect(result).toMatch(/deferred/);
|
expect(result).toMatch(/deferred/);
|
||||||
expect(result).toContain("x");
|
expect(result).toContain('x');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return in-progress status with emoji for console output", () => {
|
test('should return in-progress status with emoji for console output', () => {
|
||||||
const result = getStatusWithColor("in-progress");
|
const result = getStatusWithColor('in-progress');
|
||||||
expect(result).toMatch(/in-progress/);
|
expect(result).toMatch(/in-progress/);
|
||||||
expect(result).toContain("🔄");
|
expect(result).toContain('🔄');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return unknown status with emoji for console output", () => {
|
test('should return unknown status with emoji for console output', () => {
|
||||||
const result = getStatusWithColor("unknown");
|
const result = getStatusWithColor('unknown');
|
||||||
expect(result).toMatch(/unknown/);
|
expect(result).toMatch(/unknown/);
|
||||||
expect(result).toContain("❌");
|
expect(result).toContain('❌');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should use simple icons when forTable is true", () => {
|
test('should use simple icons when forTable is true', () => {
|
||||||
const doneResult = getStatusWithColor("done", true);
|
const doneResult = getStatusWithColor('done', true);
|
||||||
expect(doneResult).toMatch(/done/);
|
expect(doneResult).toMatch(/done/);
|
||||||
expect(doneResult).toContain("✓");
|
expect(doneResult).toContain('✓');
|
||||||
|
|
||||||
const pendingResult = getStatusWithColor("pending", true);
|
const pendingResult = getStatusWithColor('pending', true);
|
||||||
expect(pendingResult).toMatch(/pending/);
|
expect(pendingResult).toMatch(/pending/);
|
||||||
expect(pendingResult).toContain("○");
|
expect(pendingResult).toContain('○');
|
||||||
|
|
||||||
const inProgressResult = getStatusWithColor("in-progress", true);
|
const inProgressResult = getStatusWithColor('in-progress', true);
|
||||||
expect(inProgressResult).toMatch(/in-progress/);
|
expect(inProgressResult).toMatch(/in-progress/);
|
||||||
expect(inProgressResult).toContain("►");
|
expect(inProgressResult).toContain('►');
|
||||||
|
|
||||||
const deferredResult = getStatusWithColor("deferred", true);
|
const deferredResult = getStatusWithColor('deferred', true);
|
||||||
expect(deferredResult).toMatch(/deferred/);
|
expect(deferredResult).toMatch(/deferred/);
|
||||||
expect(deferredResult).toContain("x");
|
expect(deferredResult).toContain('x');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("formatDependenciesWithStatus function", () => {
|
describe('formatDependenciesWithStatus function', () => {
|
||||||
test("should format dependencies as plain IDs when forConsole is false (default)", () => {
|
test('should format dependencies as plain IDs when forConsole is false (default)', () => {
|
||||||
const dependencies = [1, 2, 3];
|
const dependencies = [1, 2, 3];
|
||||||
const allTasks = [
|
const allTasks = [
|
||||||
{ id: 1, status: "done" },
|
{ id: 1, status: 'done' },
|
||||||
{ id: 2, status: "pending" },
|
{ id: 2, status: 'pending' },
|
||||||
{ id: 3, status: "deferred" },
|
{ id: 3, status: 'deferred' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = formatDependenciesWithStatus(dependencies, allTasks);
|
const result = formatDependenciesWithStatus(dependencies, allTasks);
|
||||||
|
|
||||||
// With recent changes, we expect just plain IDs when forConsole is false
|
// With recent changes, we expect just plain IDs when forConsole is false
|
||||||
expect(result).toBe("1, 2, 3");
|
expect(result).toBe('1, 2, 3');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should format dependencies with status indicators when forConsole is true", () => {
|
test('should format dependencies with status indicators when forConsole is true', () => {
|
||||||
const dependencies = [1, 2, 3];
|
const dependencies = [1, 2, 3];
|
||||||
const allTasks = [
|
const allTasks = [
|
||||||
{ id: 1, status: "done" },
|
{ id: 1, status: 'done' },
|
||||||
{ id: 2, status: "pending" },
|
{ id: 2, status: 'pending' },
|
||||||
{ id: 3, status: "deferred" },
|
{ id: 3, status: 'deferred' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const result = formatDependenciesWithStatus(dependencies, allTasks, true);
|
const result = formatDependenciesWithStatus(dependencies, allTasks, true);
|
||||||
|
|
||||||
// We can't test for exact color formatting due to our chalk mocks
|
// We can't test for exact color formatting due to our chalk mocks
|
||||||
// Instead, test that the result contains all the expected IDs
|
// Instead, test that the result contains all the expected IDs
|
||||||
expect(result).toContain("1");
|
expect(result).toContain('1');
|
||||||
expect(result).toContain("2");
|
expect(result).toContain('2');
|
||||||
expect(result).toContain("3");
|
expect(result).toContain('3');
|
||||||
|
|
||||||
// Test that it's a comma-separated list
|
// Test that it's a comma-separated list
|
||||||
expect(result.split(", ").length).toBe(3);
|
expect(result.split(', ').length).toBe(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return "None" for empty dependencies', () => {
|
test('should return "None" for empty dependencies', () => {
|
||||||
const result = formatDependenciesWithStatus([], []);
|
const result = formatDependenciesWithStatus([], []);
|
||||||
expect(result).toBe("None");
|
expect(result).toBe('None');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle missing tasks in the task list", () => {
|
test('should handle missing tasks in the task list', () => {
|
||||||
const dependencies = [1, 999];
|
const dependencies = [1, 999];
|
||||||
const allTasks = [{ id: 1, status: "done" }];
|
const allTasks = [{ id: 1, status: 'done' }];
|
||||||
|
|
||||||
const result = formatDependenciesWithStatus(dependencies, allTasks);
|
const result = formatDependenciesWithStatus(dependencies, allTasks);
|
||||||
expect(result).toBe("1, 999 (Not found)");
|
expect(result).toBe('1, 999 (Not found)');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("createProgressBar function", () => {
|
describe('createProgressBar function', () => {
|
||||||
test("should create a progress bar with the correct percentage", () => {
|
test('should create a progress bar with the correct percentage', () => {
|
||||||
const result = createProgressBar(50, 10, {
|
const result = createProgressBar(50, 10, {
|
||||||
pending: 20,
|
pending: 20,
|
||||||
"in-progress": 15,
|
'in-progress': 15,
|
||||||
blocked: 5,
|
blocked: 5
|
||||||
});
|
});
|
||||||
expect(result).toContain("50%");
|
expect(result).toContain('50%');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle 0% progress", () => {
|
test('should handle 0% progress', () => {
|
||||||
const result = createProgressBar(0, 10);
|
const result = createProgressBar(0, 10);
|
||||||
expect(result).toContain("0%");
|
expect(result).toContain('0%');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle 100% progress", () => {
|
test('should handle 100% progress', () => {
|
||||||
const result = createProgressBar(100, 10);
|
const result = createProgressBar(100, 10);
|
||||||
expect(result).toContain("100%");
|
expect(result).toContain('100%');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle invalid percentages by clamping", () => {
|
test('should handle invalid percentages by clamping', () => {
|
||||||
const result1 = createProgressBar(0, 10);
|
const result1 = createProgressBar(0, 10);
|
||||||
expect(result1).toContain("0%");
|
expect(result1).toContain('0%');
|
||||||
|
|
||||||
const result2 = createProgressBar(100, 10);
|
const result2 = createProgressBar(100, 10);
|
||||||
expect(result2).toContain("100%");
|
expect(result2).toContain('100%');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should support status breakdown in the progress bar", () => {
|
test('should support status breakdown in the progress bar', () => {
|
||||||
const result = createProgressBar(30, 10, {
|
const result = createProgressBar(30, 10, {
|
||||||
pending: 30,
|
pending: 30,
|
||||||
"in-progress": 20,
|
'in-progress': 20,
|
||||||
blocked: 10,
|
blocked: 10,
|
||||||
deferred: 5,
|
deferred: 5,
|
||||||
cancelled: 5,
|
cancelled: 5
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toContain("40%");
|
expect(result).toContain('40%');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("getComplexityWithColor function", () => {
|
describe('getComplexityWithColor function', () => {
|
||||||
test("should return high complexity in red", () => {
|
test('should return high complexity in red', () => {
|
||||||
const result = getComplexityWithColor(8);
|
const result = getComplexityWithColor(8);
|
||||||
expect(result).toMatch(/8/);
|
expect(result).toMatch(/8/);
|
||||||
expect(result).toContain("●");
|
expect(result).toContain('●');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return medium complexity in yellow", () => {
|
test('should return medium complexity in yellow', () => {
|
||||||
const result = getComplexityWithColor(5);
|
const result = getComplexityWithColor(5);
|
||||||
expect(result).toMatch(/5/);
|
expect(result).toMatch(/5/);
|
||||||
expect(result).toContain("●");
|
expect(result).toContain('●');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should return low complexity in green", () => {
|
test('should return low complexity in green', () => {
|
||||||
const result = getComplexityWithColor(3);
|
const result = getComplexityWithColor(3);
|
||||||
expect(result).toMatch(/3/);
|
expect(result).toMatch(/3/);
|
||||||
expect(result).toContain("●");
|
expect(result).toContain('●');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should handle non-numeric inputs", () => {
|
test('should handle non-numeric inputs', () => {
|
||||||
const result = getComplexityWithColor("high");
|
const result = getComplexityWithColor('high');
|
||||||
expect(result).toMatch(/high/);
|
expect(result).toMatch(/high/);
|
||||||
expect(result).toContain("●");
|
expect(result).toContain('●');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user