fix merge conflicts to prep for merge with branch next
- Enhance E2E testing and LLM analysis report and: - Add --analyze-log flag to run_e2e.sh to re-run LLM analysis on existing logs. - Add test:e2e and analyze-log scripts to package.json for easier execution. - Correct display errors and dependency validation output: - Update chalk usage in add-task.js to use bracket notation (chalk[color]) compatible with v5, resolving 'chalk.keyword is not a function' error. - Modify fix-dependencies command output to show red failure box with issue count instead of green success box when validation fails. - Refactor interactive model setup: - Verify inclusion of 'No change' option during interactive model setup flow (task-master models --setup). - Update model definitions: - Add max_tokens field for gpt-4o in supported-models.json. - Remove unused scripts: - Delete prepare-package.js and rule-transformer.test.js. Release candidate
This commit is contained in:
@@ -199,16 +199,35 @@ describe('Commands Module', () => {
|
||||
// Use input option if file argument not provided
|
||||
const inputFile = file || options.input;
|
||||
const defaultPrdPath = 'scripts/prd.txt';
|
||||
const append = options.append || false;
|
||||
const force = options.force || false;
|
||||
const outputPath = options.output || 'tasks/tasks.json';
|
||||
|
||||
// Mock confirmOverwriteIfNeeded function to test overwrite behavior
|
||||
const mockConfirmOverwrite = jest.fn().mockResolvedValue(true);
|
||||
|
||||
// Helper function to check if tasks.json exists and confirm overwrite
|
||||
async function confirmOverwriteIfNeeded() {
|
||||
if (fs.existsSync(outputPath) && !force && !append) {
|
||||
return mockConfirmOverwrite();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// If no input file specified, check for default PRD location
|
||||
if (!inputFile) {
|
||||
if (fs.existsSync(defaultPrdPath)) {
|
||||
console.log(chalk.blue(`Using default PRD file: ${defaultPrdPath}`));
|
||||
const numTasks = parseInt(options.numTasks, 10);
|
||||
const outputPath = options.output;
|
||||
|
||||
// Check if we need to confirm overwrite
|
||||
if (!(await confirmOverwriteIfNeeded())) return;
|
||||
|
||||
console.log(chalk.blue(`Generating ${numTasks} tasks...`));
|
||||
await mockParsePRD(defaultPrdPath, outputPath, numTasks);
|
||||
if (append) {
|
||||
console.log(chalk.blue('Appending to existing tasks...'));
|
||||
}
|
||||
await mockParsePRD(defaultPrdPath, outputPath, numTasks, { append });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -221,12 +240,20 @@ describe('Commands Module', () => {
|
||||
}
|
||||
|
||||
const numTasks = parseInt(options.numTasks, 10);
|
||||
const outputPath = options.output;
|
||||
|
||||
// Check if we need to confirm overwrite
|
||||
if (!(await confirmOverwriteIfNeeded())) return;
|
||||
|
||||
console.log(chalk.blue(`Parsing PRD file: ${inputFile}`));
|
||||
console.log(chalk.blue(`Generating ${numTasks} tasks...`));
|
||||
if (append) {
|
||||
console.log(chalk.blue('Appending to existing tasks...'));
|
||||
}
|
||||
|
||||
await mockParsePRD(inputFile, outputPath, numTasks);
|
||||
await mockParsePRD(inputFile, outputPath, numTasks, { append });
|
||||
|
||||
// Return mock for testing
|
||||
return { mockConfirmOverwrite };
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -252,7 +279,8 @@ describe('Commands Module', () => {
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(
|
||||
'scripts/prd.txt',
|
||||
'tasks/tasks.json',
|
||||
10 // Default value from command definition
|
||||
10, // Default value from command definition
|
||||
{ append: false }
|
||||
);
|
||||
});
|
||||
|
||||
@@ -290,7 +318,8 @@ describe('Commands Module', () => {
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(
|
||||
testFile,
|
||||
'tasks/tasks.json',
|
||||
10
|
||||
10,
|
||||
{ append: false }
|
||||
);
|
||||
expect(mockExistsSync).not.toHaveBeenCalledWith('scripts/prd.txt');
|
||||
});
|
||||
@@ -313,7 +342,8 @@ describe('Commands Module', () => {
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(
|
||||
testFile,
|
||||
'tasks/tasks.json',
|
||||
10
|
||||
10,
|
||||
{ append: false }
|
||||
);
|
||||
expect(mockExistsSync).not.toHaveBeenCalledWith('scripts/prd.txt');
|
||||
});
|
||||
@@ -331,7 +361,126 @@ describe('Commands Module', () => {
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(testFile, outputFile, numTasks);
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(
|
||||
testFile,
|
||||
outputFile,
|
||||
numTasks,
|
||||
{ append: false }
|
||||
);
|
||||
});
|
||||
|
||||
test('should pass append flag to parsePRD when provided', async () => {
|
||||
// Arrange
|
||||
const testFile = 'test/prd.txt';
|
||||
|
||||
// Act - call the handler directly with append flag
|
||||
await parsePrdAction(testFile, {
|
||||
numTasks: '10',
|
||||
output: 'tasks/tasks.json',
|
||||
append: true
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(mockConsoleLog).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Appending to existing tasks')
|
||||
);
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(
|
||||
testFile,
|
||||
'tasks/tasks.json',
|
||||
10,
|
||||
{ append: true }
|
||||
);
|
||||
});
|
||||
|
||||
test('should bypass confirmation when append flag is true and tasks.json exists', async () => {
|
||||
// Arrange
|
||||
const testFile = 'test/prd.txt';
|
||||
const outputFile = 'tasks/tasks.json';
|
||||
|
||||
// Mock that tasks.json exists
|
||||
mockExistsSync.mockImplementation((path) => {
|
||||
if (path === outputFile) return true;
|
||||
if (path === testFile) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
// Act - call the handler with append flag
|
||||
const { mockConfirmOverwrite } =
|
||||
(await parsePrdAction(testFile, {
|
||||
numTasks: '10',
|
||||
output: outputFile,
|
||||
append: true
|
||||
})) || {};
|
||||
|
||||
// Assert - confirm overwrite should not be called with append flag
|
||||
expect(mockConfirmOverwrite).not.toHaveBeenCalled();
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(testFile, outputFile, 10, {
|
||||
append: true
|
||||
});
|
||||
|
||||
// Reset mock implementation
|
||||
mockExistsSync.mockReset();
|
||||
});
|
||||
|
||||
test('should prompt for confirmation when append flag is false and tasks.json exists', async () => {
|
||||
// Arrange
|
||||
const testFile = 'test/prd.txt';
|
||||
const outputFile = 'tasks/tasks.json';
|
||||
|
||||
// Mock that tasks.json exists
|
||||
mockExistsSync.mockImplementation((path) => {
|
||||
if (path === outputFile) return true;
|
||||
if (path === testFile) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
// Act - call the handler without append flag
|
||||
const { mockConfirmOverwrite } =
|
||||
(await parsePrdAction(testFile, {
|
||||
numTasks: '10',
|
||||
output: outputFile
|
||||
// append: false (default)
|
||||
})) || {};
|
||||
|
||||
// Assert - confirm overwrite should be called without append flag
|
||||
expect(mockConfirmOverwrite).toHaveBeenCalled();
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(testFile, outputFile, 10, {
|
||||
append: false
|
||||
});
|
||||
|
||||
// Reset mock implementation
|
||||
mockExistsSync.mockReset();
|
||||
});
|
||||
|
||||
test('should bypass confirmation when force flag is true, regardless of append flag', async () => {
|
||||
// Arrange
|
||||
const testFile = 'test/prd.txt';
|
||||
const outputFile = 'tasks/tasks.json';
|
||||
|
||||
// Mock that tasks.json exists
|
||||
mockExistsSync.mockImplementation((path) => {
|
||||
if (path === outputFile) return true;
|
||||
if (path === testFile) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
// Act - call the handler with force flag
|
||||
const { mockConfirmOverwrite } =
|
||||
(await parsePrdAction(testFile, {
|
||||
numTasks: '10',
|
||||
output: outputFile,
|
||||
force: true,
|
||||
append: false
|
||||
})) || {};
|
||||
|
||||
// Assert - confirm overwrite should not be called with force flag
|
||||
expect(mockConfirmOverwrite).not.toHaveBeenCalled();
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(testFile, outputFile, 10, {
|
||||
append: false
|
||||
});
|
||||
|
||||
// Reset mock implementation
|
||||
mockExistsSync.mockReset();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
182
tests/unit/roo-integration.test.js
Normal file
182
tests/unit/roo-integration.test.js
Normal file
@@ -0,0 +1,182 @@
|
||||
import { jest } from '@jest/globals';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
|
||||
// Mock external modules
|
||||
jest.mock('child_process', () => ({
|
||||
execSync: jest.fn()
|
||||
}));
|
||||
|
||||
// Mock console methods
|
||||
jest.mock('console', () => ({
|
||||
log: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
error: jest.fn(),
|
||||
clear: jest.fn()
|
||||
}));
|
||||
|
||||
describe('Roo Integration', () => {
|
||||
let tempDir;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Create a temporary directory for testing
|
||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'task-master-test-'));
|
||||
|
||||
// Spy on fs methods
|
||||
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
|
||||
jest.spyOn(fs, 'readFileSync').mockImplementation((filePath) => {
|
||||
if (filePath.toString().includes('.roomodes')) {
|
||||
return 'Existing roomodes content';
|
||||
}
|
||||
if (filePath.toString().includes('-rules')) {
|
||||
return 'Existing mode rules content';
|
||||
}
|
||||
return '{}';
|
||||
});
|
||||
jest.spyOn(fs, 'existsSync').mockImplementation(() => false);
|
||||
jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Clean up the temporary directory
|
||||
try {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
} catch (err) {
|
||||
console.error(`Error cleaning up: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Test function that simulates the createProjectStructure behavior for Roo files
|
||||
function mockCreateRooStructure() {
|
||||
// Create main .roo directory
|
||||
fs.mkdirSync(path.join(tempDir, '.roo'), { recursive: true });
|
||||
|
||||
// Create rules directory
|
||||
fs.mkdirSync(path.join(tempDir, '.roo', 'rules'), { recursive: true });
|
||||
|
||||
// Create mode-specific rule directories
|
||||
const rooModes = ['architect', 'ask', 'boomerang', 'code', 'debug', 'test'];
|
||||
for (const mode of rooModes) {
|
||||
fs.mkdirSync(path.join(tempDir, '.roo', `rules-${mode}`), {
|
||||
recursive: true
|
||||
});
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, '.roo', `rules-${mode}`, `${mode}-rules`),
|
||||
`Content for ${mode} rules`
|
||||
);
|
||||
}
|
||||
|
||||
// Create additional directories
|
||||
fs.mkdirSync(path.join(tempDir, '.roo', 'config'), { recursive: true });
|
||||
fs.mkdirSync(path.join(tempDir, '.roo', 'templates'), { recursive: true });
|
||||
fs.mkdirSync(path.join(tempDir, '.roo', 'logs'), { recursive: true });
|
||||
|
||||
// Copy .roomodes file
|
||||
fs.writeFileSync(path.join(tempDir, '.roomodes'), 'Roomodes file content');
|
||||
}
|
||||
|
||||
test('creates all required .roo directories', () => {
|
||||
// Act
|
||||
mockCreateRooStructure();
|
||||
|
||||
// Assert
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.roo'), {
|
||||
recursive: true
|
||||
});
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'rules'),
|
||||
{ recursive: true }
|
||||
);
|
||||
|
||||
// Verify all mode directories are created
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'rules-architect'),
|
||||
{ recursive: true }
|
||||
);
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'rules-ask'),
|
||||
{ recursive: true }
|
||||
);
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'rules-boomerang'),
|
||||
{ recursive: true }
|
||||
);
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'rules-code'),
|
||||
{ recursive: true }
|
||||
);
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'rules-debug'),
|
||||
{ recursive: true }
|
||||
);
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'rules-test'),
|
||||
{ recursive: true }
|
||||
);
|
||||
});
|
||||
|
||||
test('creates rule files for all modes', () => {
|
||||
// Act
|
||||
mockCreateRooStructure();
|
||||
|
||||
// Assert - check all rule files are created
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'rules-architect', 'architect-rules'),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'rules-ask', 'ask-rules'),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'rules-boomerang', 'boomerang-rules'),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'rules-code', 'code-rules'),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'rules-debug', 'debug-rules'),
|
||||
expect.any(String)
|
||||
);
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'rules-test', 'test-rules'),
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
test('creates .roomodes file in project root', () => {
|
||||
// Act
|
||||
mockCreateRooStructure();
|
||||
|
||||
// Assert
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roomodes'),
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
test('creates additional required Roo directories', () => {
|
||||
// Act
|
||||
mockCreateRooStructure();
|
||||
|
||||
// Assert
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'config'),
|
||||
{ recursive: true }
|
||||
);
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'templates'),
|
||||
{ recursive: true }
|
||||
);
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.roo', 'logs'),
|
||||
{ recursive: true }
|
||||
);
|
||||
});
|
||||
});
|
||||
113
tests/unit/rule-transformer.test.js
Normal file
113
tests/unit/rule-transformer.test.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import { expect } from 'chai';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
import { convertCursorRuleToRooRule } from '../modules/rule-transformer.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
describe('Rule Transformer', () => {
|
||||
const testDir = path.join(__dirname, 'temp-test-dir');
|
||||
|
||||
before(() => {
|
||||
// Create test directory
|
||||
if (!fs.existsSync(testDir)) {
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
after(() => {
|
||||
// Clean up test directory
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('should correctly convert basic terms', () => {
|
||||
// Create a test Cursor rule file with basic terms
|
||||
const testCursorRule = path.join(testDir, 'basic-terms.mdc');
|
||||
const testContent = `---
|
||||
description: Test Cursor rule for basic terms
|
||||
globs: **/*
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
This is a Cursor rule that references cursor.so and uses the word Cursor multiple times.
|
||||
Also has references to .mdc files.`;
|
||||
|
||||
fs.writeFileSync(testCursorRule, testContent);
|
||||
|
||||
// Convert it
|
||||
const testRooRule = path.join(testDir, 'basic-terms.md');
|
||||
convertCursorRuleToRooRule(testCursorRule, testRooRule);
|
||||
|
||||
// Read the converted file
|
||||
const convertedContent = fs.readFileSync(testRooRule, 'utf8');
|
||||
|
||||
// Verify transformations
|
||||
expect(convertedContent).to.include('Roo Code');
|
||||
expect(convertedContent).to.include('roocode.com');
|
||||
expect(convertedContent).to.include('.md');
|
||||
expect(convertedContent).to.not.include('cursor.so');
|
||||
expect(convertedContent).to.not.include('Cursor rule');
|
||||
});
|
||||
|
||||
it('should correctly convert tool references', () => {
|
||||
// Create a test Cursor rule file with tool references
|
||||
const testCursorRule = path.join(testDir, 'tool-refs.mdc');
|
||||
const testContent = `---
|
||||
description: Test Cursor rule for tool references
|
||||
globs: **/*
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
- Use the search tool to find code
|
||||
- The edit_file tool lets you modify files
|
||||
- run_command executes terminal commands
|
||||
- use_mcp connects to external services`;
|
||||
|
||||
fs.writeFileSync(testCursorRule, testContent);
|
||||
|
||||
// Convert it
|
||||
const testRooRule = path.join(testDir, 'tool-refs.md');
|
||||
convertCursorRuleToRooRule(testCursorRule, testRooRule);
|
||||
|
||||
// Read the converted file
|
||||
const convertedContent = fs.readFileSync(testRooRule, 'utf8');
|
||||
|
||||
// Verify transformations
|
||||
expect(convertedContent).to.include('search_files tool');
|
||||
expect(convertedContent).to.include('apply_diff tool');
|
||||
expect(convertedContent).to.include('execute_command');
|
||||
expect(convertedContent).to.include('use_mcp_tool');
|
||||
});
|
||||
|
||||
it('should correctly update file references', () => {
|
||||
// Create a test Cursor rule file with file references
|
||||
const testCursorRule = path.join(testDir, 'file-refs.mdc');
|
||||
const testContent = `---
|
||||
description: Test Cursor rule for file references
|
||||
globs: **/*
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and
|
||||
[taskmaster.mdc](mdc:.cursor/rules/taskmaster.mdc).`;
|
||||
|
||||
fs.writeFileSync(testCursorRule, testContent);
|
||||
|
||||
// Convert it
|
||||
const testRooRule = path.join(testDir, 'file-refs.md');
|
||||
convertCursorRuleToRooRule(testCursorRule, testRooRule);
|
||||
|
||||
// Read the converted file
|
||||
const convertedContent = fs.readFileSync(testRooRule, 'utf8');
|
||||
|
||||
// Verify transformations
|
||||
expect(convertedContent).to.include('(mdc:.roo/rules/dev_workflow.md)');
|
||||
expect(convertedContent).to.include('(mdc:.roo/rules/taskmaster.md)');
|
||||
expect(convertedContent).to.not.include('(mdc:.cursor/rules/');
|
||||
});
|
||||
});
|
||||
@@ -134,33 +134,59 @@ jest.mock('../../scripts/modules/task-manager.js', () => {
|
||||
});
|
||||
|
||||
// Create a simplified version of parsePRD for testing
|
||||
const testParsePRD = async (prdPath, outputPath, numTasks) => {
|
||||
const testParsePRD = async (prdPath, outputPath, numTasks, options = {}) => {
|
||||
const { append = false } = options;
|
||||
try {
|
||||
// Handle existing tasks when append flag is true
|
||||
let existingTasks = { tasks: [] };
|
||||
let lastTaskId = 0;
|
||||
|
||||
// Check if the output file already exists
|
||||
if (mockExistsSync(outputPath)) {
|
||||
const confirmOverwrite = await mockPromptYesNo(
|
||||
`Warning: ${outputPath} already exists. Overwrite?`,
|
||||
false
|
||||
);
|
||||
if (append) {
|
||||
// Simulate reading existing tasks.json
|
||||
existingTasks = {
|
||||
tasks: [
|
||||
{ id: 1, title: 'Existing Task 1', status: 'done' },
|
||||
{ id: 2, title: 'Existing Task 2', status: 'pending' }
|
||||
]
|
||||
};
|
||||
lastTaskId = 2; // Highest existing ID
|
||||
} else {
|
||||
const confirmOverwrite = await mockPromptYesNo(
|
||||
`Warning: ${outputPath} already exists. Overwrite?`,
|
||||
false
|
||||
);
|
||||
|
||||
if (!confirmOverwrite) {
|
||||
console.log(`Operation cancelled. ${outputPath} was not modified.`);
|
||||
return null;
|
||||
if (!confirmOverwrite) {
|
||||
console.log(`Operation cancelled. ${outputPath} was not modified.`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const prdContent = mockReadFileSync(prdPath, 'utf8');
|
||||
const tasks = await mockCallClaude(prdContent, prdPath, numTasks);
|
||||
// Modify mockCallClaude to accept lastTaskId parameter
|
||||
let newTasks = await mockCallClaude(prdContent, prdPath, numTasks);
|
||||
|
||||
// Merge tasks if appending
|
||||
const tasksData = append
|
||||
? {
|
||||
...existingTasks,
|
||||
tasks: [...existingTasks.tasks, ...newTasks.tasks]
|
||||
}
|
||||
: newTasks;
|
||||
|
||||
const dir = mockDirname(outputPath);
|
||||
|
||||
if (!mockExistsSync(dir)) {
|
||||
mockMkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
mockWriteJSON(outputPath, tasks);
|
||||
mockWriteJSON(outputPath, tasksData);
|
||||
await mockGenerateTaskFiles(outputPath, dir);
|
||||
|
||||
return tasks;
|
||||
return tasksData;
|
||||
} catch (error) {
|
||||
console.error(`Error parsing PRD: ${error.message}`);
|
||||
process.exit(1);
|
||||
@@ -628,6 +654,27 @@ describe('Task Manager Module', () => {
|
||||
// Mock the sample PRD content
|
||||
const samplePRDContent = '# Sample PRD for Testing';
|
||||
|
||||
// Mock existing tasks for append test
|
||||
const existingTasks = {
|
||||
tasks: [
|
||||
{ id: 1, title: 'Existing Task 1', status: 'done' },
|
||||
{ id: 2, title: 'Existing Task 2', status: 'pending' }
|
||||
]
|
||||
};
|
||||
|
||||
// Mock new tasks with continuing IDs for append test
|
||||
const newTasksWithContinuedIds = {
|
||||
tasks: [
|
||||
{ id: 3, title: 'New Task 3' },
|
||||
{ id: 4, title: 'New Task 4' }
|
||||
]
|
||||
};
|
||||
|
||||
// Mock merged tasks for append test
|
||||
const mergedTasks = {
|
||||
tasks: [...existingTasks.tasks, ...newTasksWithContinuedIds.tasks]
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
@@ -811,6 +858,66 @@ describe('Task Manager Module', () => {
|
||||
sampleClaudeResponse
|
||||
);
|
||||
});
|
||||
|
||||
test('should append new tasks when append option is true', async () => {
|
||||
// Setup mocks to simulate tasks.json already exists
|
||||
mockExistsSync.mockImplementation((path) => {
|
||||
if (path === 'tasks/tasks.json') return true; // Output file exists
|
||||
if (path === 'tasks') return true; // Directory exists
|
||||
return false;
|
||||
});
|
||||
|
||||
// Mock for reading existing tasks
|
||||
mockReadJSON.mockReturnValue(existingTasks);
|
||||
// mockReadJSON = jest.fn().mockReturnValue(existingTasks);
|
||||
|
||||
// Mock callClaude to return new tasks with continuing IDs
|
||||
mockCallClaude.mockResolvedValueOnce(newTasksWithContinuedIds);
|
||||
|
||||
// Call the function with append option
|
||||
const result = await testParsePRD(
|
||||
'path/to/prd.txt',
|
||||
'tasks/tasks.json',
|
||||
2,
|
||||
{ append: true }
|
||||
);
|
||||
|
||||
// Verify prompt was NOT called (no confirmation needed for append)
|
||||
expect(mockPromptYesNo).not.toHaveBeenCalled();
|
||||
|
||||
// Verify the file was written with merged tasks
|
||||
expect(mockWriteJSON).toHaveBeenCalledWith(
|
||||
'tasks/tasks.json',
|
||||
expect.objectContaining({
|
||||
tasks: expect.arrayContaining([
|
||||
expect.objectContaining({ id: 1 }),
|
||||
expect.objectContaining({ id: 2 }),
|
||||
expect.objectContaining({ id: 3 }),
|
||||
expect.objectContaining({ id: 4 })
|
||||
])
|
||||
})
|
||||
);
|
||||
|
||||
// Verify the result contains merged tasks
|
||||
expect(result.tasks.length).toBe(4);
|
||||
});
|
||||
|
||||
test('should skip prompt and not overwrite when append is true', async () => {
|
||||
// Setup mocks to simulate tasks.json already exists
|
||||
mockExistsSync.mockImplementation((path) => {
|
||||
if (path === 'tasks/tasks.json') return true; // Output file exists
|
||||
if (path === 'tasks') return true; // Directory exists
|
||||
return false;
|
||||
});
|
||||
|
||||
// Call the function with append option
|
||||
await testParsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
|
||||
append: true
|
||||
});
|
||||
|
||||
// Verify prompt was NOT called with append flag
|
||||
expect(mockPromptYesNo).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('updateTasks function', () => {
|
||||
|
||||
Reference in New Issue
Block a user