feat: Add .taskmaster directory (#619)
This commit is contained in:
@@ -23,15 +23,27 @@ describe('Roo Files Inclusion in Package', () => {
|
||||
|
||||
// Check for Roo directory creation (using more flexible pattern matching)
|
||||
const hasRooDir = initJsContent.includes(
|
||||
"ensureDirectoryExists(path.join(targetDir, '.roo"
|
||||
"ensureDirectoryExists(path.join(targetDir, '.roo'))"
|
||||
);
|
||||
expect(hasRooDir).toBe(true);
|
||||
|
||||
// Check for .roomodes file copying
|
||||
const hasRoomodes = initJsContent.includes("copyTemplateFile('.roomodes'");
|
||||
// Check for .roomodes file copying using hardcoded path
|
||||
const hasRoomodes = initJsContent.includes(
|
||||
"path.join(targetDir, '.roomodes')"
|
||||
);
|
||||
expect(hasRoomodes).toBe(true);
|
||||
|
||||
// Check for mode-specific patterns (using more flexible pattern matching)
|
||||
// Check for local ROO_MODES definition and usage
|
||||
const hasRooModes = initJsContent.includes('ROO_MODES');
|
||||
expect(hasRooModes).toBe(true);
|
||||
|
||||
// Check for local ROO_MODES array definition
|
||||
const hasLocalRooModes = initJsContent.includes(
|
||||
"const ROO_MODES = ['architect', 'ask', 'boomerang', 'code', 'debug', 'test']"
|
||||
);
|
||||
expect(hasLocalRooModes).toBe(true);
|
||||
|
||||
// Check for mode-specific patterns (these will still be present in the local array)
|
||||
const hasArchitect = initJsContent.includes('architect');
|
||||
const hasAsk = initJsContent.includes('ask');
|
||||
const hasBoomerang = initJsContent.includes('boomerang');
|
||||
|
||||
@@ -23,23 +23,21 @@ describe('Roo Initialization Functionality', () => {
|
||||
|
||||
// Check for the line that creates .roo/rules directory
|
||||
const hasRooRulesDir = initJsContent.includes(
|
||||
"ensureDirectoryExists(path.join(targetDir, '.roo', 'rules'))"
|
||||
"ensureDirectoryExists(path.join(targetDir, '.roo/rules'))"
|
||||
);
|
||||
expect(hasRooRulesDir).toBe(true);
|
||||
|
||||
// Check for the for loop that creates mode-specific directories
|
||||
const hasRooModeLoop =
|
||||
initJsContent.includes(
|
||||
"for (const mode of ['architect', 'ask', 'boomerang', 'code', 'debug', 'test'])"
|
||||
) ||
|
||||
(initJsContent.includes('for (const mode of [') &&
|
||||
initJsContent.includes('architect') &&
|
||||
initJsContent.includes('ask') &&
|
||||
initJsContent.includes('boomerang') &&
|
||||
initJsContent.includes('code') &&
|
||||
initJsContent.includes('debug') &&
|
||||
initJsContent.includes('test'));
|
||||
// Check for the for loop that creates mode-specific directories using local ROO_MODES array
|
||||
const hasRooModeLoop = initJsContent.includes(
|
||||
'for (const mode of ROO_MODES)'
|
||||
);
|
||||
expect(hasRooModeLoop).toBe(true);
|
||||
|
||||
// Check for local ROO_MODES definition
|
||||
const hasLocalRooModes = initJsContent.includes(
|
||||
"const ROO_MODES = ['architect', 'ask', 'boomerang', 'code', 'debug', 'test']"
|
||||
);
|
||||
expect(hasLocalRooModes).toBe(true);
|
||||
});
|
||||
|
||||
test('init.js copies Roo files from assets/roocode directory', () => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,62 @@ import path from 'path';
|
||||
import { jest } from '@jest/globals';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
// Mock modules first before any imports
|
||||
jest.mock('fs', () => ({
|
||||
existsSync: jest.fn((filePath) => {
|
||||
// Prevent Jest internal file access
|
||||
if (
|
||||
filePath.includes('jest-message-util') ||
|
||||
filePath.includes('node_modules')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return false; // Default to false for config discovery prevention
|
||||
}),
|
||||
readFileSync: jest.fn(() => '{}'),
|
||||
writeFileSync: jest.fn(),
|
||||
mkdirSync: jest.fn()
|
||||
}));
|
||||
|
||||
jest.mock('path', () => ({
|
||||
join: jest.fn((dir, file) => `${dir}/${file}`),
|
||||
dirname: jest.fn((filePath) => filePath.split('/').slice(0, -1).join('/')),
|
||||
resolve: jest.fn((...paths) => paths.join('/')),
|
||||
basename: jest.fn((filePath) => filePath.split('/').pop())
|
||||
}));
|
||||
|
||||
jest.mock('chalk', () => ({
|
||||
red: jest.fn((text) => text),
|
||||
blue: jest.fn((text) => text),
|
||||
green: jest.fn((text) => text),
|
||||
yellow: jest.fn((text) => text),
|
||||
white: jest.fn((text) => ({
|
||||
bold: jest.fn((text) => text)
|
||||
})),
|
||||
reset: jest.fn((text) => text),
|
||||
dim: jest.fn((text) => text) // Add dim function to prevent chalk errors
|
||||
}));
|
||||
|
||||
// Mock console to prevent Jest internal access
|
||||
const mockConsole = {
|
||||
log: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
error: jest.fn()
|
||||
};
|
||||
global.console = mockConsole;
|
||||
|
||||
// Mock path-utils to prevent config file path discovery and logging
|
||||
jest.mock('../../src/utils/path-utils.js', () => ({
|
||||
__esModule: true,
|
||||
findProjectRoot: jest.fn(() => '/mock/project'),
|
||||
findConfigPath: jest.fn(() => null), // Always return null to prevent config discovery
|
||||
findTasksPath: jest.fn(() => '/mock/tasks.json'),
|
||||
findComplexityReportPath: jest.fn(() => null),
|
||||
resolveTasksOutputPath: jest.fn(() => '/mock/tasks.json'),
|
||||
resolveComplexityReportOutputPath: jest.fn(() => '/mock/report.json')
|
||||
}));
|
||||
|
||||
// --- Read REAL supported-models.json data BEFORE mocks ---
|
||||
const __filename = fileURLToPath(import.meta.url); // Get current file path
|
||||
const __dirname = path.dirname(__filename); // Get current directory
|
||||
@@ -34,9 +90,6 @@ const mockLog = jest.fn();
|
||||
|
||||
// --- Mock Dependencies BEFORE importing the module under test ---
|
||||
|
||||
// Mock the entire 'fs' module
|
||||
jest.mock('fs');
|
||||
|
||||
// Mock the 'utils.js' module using a factory function
|
||||
jest.mock('../../scripts/modules/utils.js', () => ({
|
||||
__esModule: true, // Indicate it's an ES module mock
|
||||
@@ -46,8 +99,6 @@ jest.mock('../../scripts/modules/utils.js', () => ({
|
||||
resolveEnvVariable: jest.fn() // Example if needed
|
||||
}));
|
||||
|
||||
// DO NOT MOCK 'chalk'
|
||||
|
||||
// --- Import the module under test AFTER mocks are defined ---
|
||||
import * as configManager from '../../scripts/modules/config-manager.js';
|
||||
// Import the mocked 'fs' module to allow spying on its functions
|
||||
@@ -55,7 +106,10 @@ import fsMocked from 'fs';
|
||||
|
||||
// --- Test Data (Keep as is, ensure DEFAULT_CONFIG is accurate) ---
|
||||
const MOCK_PROJECT_ROOT = '/mock/project';
|
||||
const MOCK_CONFIG_PATH = path.join(MOCK_PROJECT_ROOT, '.taskmasterconfig');
|
||||
const MOCK_CONFIG_PATH = path.join(
|
||||
MOCK_PROJECT_ROOT,
|
||||
'.taskmaster/config.json'
|
||||
);
|
||||
|
||||
// Updated DEFAULT_CONFIG reflecting the implementation
|
||||
const DEFAULT_CONFIG = {
|
||||
@@ -185,7 +239,15 @@ beforeEach(() => {
|
||||
// Still mock the .taskmasterconfig reads
|
||||
return JSON.stringify(DEFAULT_CONFIG); // Default behavior
|
||||
}
|
||||
// Throw for unexpected reads - helps catch errors
|
||||
// For Jest internal files or other unexpected files, return empty string instead of throwing
|
||||
// This prevents Jest's internal file operations from breaking tests
|
||||
if (
|
||||
filePath.includes('jest-message-util') ||
|
||||
filePath.includes('node_modules')
|
||||
) {
|
||||
return '{}'; // Return empty JSON for Jest internal files
|
||||
}
|
||||
// Throw for truly unexpected reads that should be caught in tests
|
||||
throw new Error(`Unexpected fs.readFileSync call in test: ${filePath}`);
|
||||
});
|
||||
|
||||
@@ -444,7 +506,7 @@ describe('getConfig Tests', () => {
|
||||
// Assert
|
||||
expect(config).toEqual(DEFAULT_CONFIG);
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(`Permission denied. Using default configuration.`)
|
||||
expect.stringContaining('Permission denied. Using default configuration.')
|
||||
);
|
||||
});
|
||||
|
||||
@@ -533,7 +595,7 @@ describe('writeConfig', () => {
|
||||
expect(success).toBe(false);
|
||||
expect(fsWriteFileSyncSpy).toHaveBeenCalled();
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(`Disk full`)
|
||||
expect.stringContaining('Disk full')
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -3,8 +3,62 @@
|
||||
*/
|
||||
|
||||
import { jest } from '@jest/globals';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
// Mock modules first before any imports
|
||||
jest.mock('fs', () => ({
|
||||
existsSync: jest.fn((filePath) => {
|
||||
// Prevent Jest internal file access
|
||||
if (
|
||||
filePath.includes('jest-message-util') ||
|
||||
filePath.includes('node_modules')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return false; // Default to false for config discovery prevention
|
||||
}),
|
||||
readFileSync: jest.fn(() => '{}'),
|
||||
writeFileSync: jest.fn(),
|
||||
mkdirSync: jest.fn()
|
||||
}));
|
||||
|
||||
jest.mock('path', () => ({
|
||||
join: jest.fn((dir, file) => `${dir}/${file}`),
|
||||
dirname: jest.fn((filePath) => filePath.split('/').slice(0, -1).join('/')),
|
||||
resolve: jest.fn((...paths) => paths.join('/')),
|
||||
basename: jest.fn((filePath) => filePath.split('/').pop())
|
||||
}));
|
||||
|
||||
jest.mock('chalk', () => ({
|
||||
red: jest.fn((text) => text),
|
||||
blue: jest.fn((text) => text),
|
||||
green: jest.fn((text) => text),
|
||||
yellow: jest.fn((text) => text),
|
||||
white: jest.fn((text) => ({
|
||||
bold: jest.fn((text) => text)
|
||||
})),
|
||||
reset: jest.fn((text) => text),
|
||||
dim: jest.fn((text) => text) // Add dim function to prevent chalk errors
|
||||
}));
|
||||
|
||||
// Mock console to prevent Jest internal access
|
||||
const mockConsole = {
|
||||
log: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
error: jest.fn()
|
||||
};
|
||||
global.console = mockConsole;
|
||||
|
||||
// Mock path-utils to prevent file system discovery issues
|
||||
jest.mock('../../src/utils/path-utils.js', () => ({
|
||||
__esModule: true,
|
||||
findProjectRoot: jest.fn(() => '/mock/project'),
|
||||
findConfigPath: jest.fn(() => null), // Always return null to prevent config discovery
|
||||
findTasksPath: jest.fn(() => '/mock/tasks.json'),
|
||||
findComplexityReportPath: jest.fn(() => null),
|
||||
resolveTasksOutputPath: jest.fn(() => '/mock/tasks.json'),
|
||||
resolveComplexityReportOutputPath: jest.fn(() => '/mock/report.json')
|
||||
}));
|
||||
|
||||
// Import the actual module to test
|
||||
import {
|
||||
@@ -21,10 +75,16 @@ import {
|
||||
toKebabCase
|
||||
} from '../../scripts/modules/utils.js';
|
||||
|
||||
// Import the mocked modules for use in tests
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
// Mock config-manager to provide config values
|
||||
const mockGetLogLevel = jest.fn(() => 'info'); // Default log level for tests
|
||||
const mockGetDebugFlag = jest.fn(() => false); // Default debug flag for tests
|
||||
jest.mock('../../scripts/modules/config-manager.js', () => ({
|
||||
getLogLevel: mockGetLogLevel
|
||||
getLogLevel: mockGetLogLevel,
|
||||
getDebugFlag: mockGetDebugFlag
|
||||
// Mock other getters if needed by utils.js functions under test
|
||||
}));
|
||||
|
||||
@@ -56,31 +116,11 @@ function testDetectCamelCaseFlags(args) {
|
||||
}
|
||||
|
||||
describe('Utils Module', () => {
|
||||
// Setup fs mocks for each test
|
||||
let fsReadFileSyncSpy;
|
||||
let fsWriteFileSyncSpy;
|
||||
let fsExistsSyncSpy;
|
||||
let pathJoinSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
// Setup fs spy functions for each test
|
||||
fsReadFileSyncSpy = jest.spyOn(fs, 'readFileSync').mockImplementation();
|
||||
fsWriteFileSyncSpy = jest.spyOn(fs, 'writeFileSync').mockImplementation();
|
||||
fsExistsSyncSpy = jest.spyOn(fs, 'existsSync').mockImplementation();
|
||||
pathJoinSpy = jest.spyOn(path, 'join').mockImplementation();
|
||||
|
||||
// Clear all mocks before each test
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore all mocked functions
|
||||
fsReadFileSyncSpy.mockRestore();
|
||||
fsWriteFileSyncSpy.mockRestore();
|
||||
fsExistsSyncSpy.mockRestore();
|
||||
pathJoinSpy.mockRestore();
|
||||
});
|
||||
|
||||
describe('truncate function', () => {
|
||||
test('should return the original string if shorter than maxLength', () => {
|
||||
const result = truncate('Hello', 10);
|
||||
@@ -340,14 +380,16 @@ describe('Utils Module', () => {
|
||||
complexityAnalysis: [{ taskId: 1, complexityScore: 7 }]
|
||||
};
|
||||
|
||||
fsExistsSyncSpy.mockReturnValue(true);
|
||||
fsReadFileSyncSpy.mockReturnValue(JSON.stringify(testReport));
|
||||
pathJoinSpy.mockReturnValue('/path/to/report.json');
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
jest
|
||||
.spyOn(fs, 'readFileSync')
|
||||
.mockReturnValue(JSON.stringify(testReport));
|
||||
jest.spyOn(path, 'join').mockReturnValue('/path/to/report.json');
|
||||
|
||||
const result = readComplexityReport();
|
||||
|
||||
expect(fsExistsSyncSpy).toHaveBeenCalled();
|
||||
expect(fsReadFileSyncSpy).toHaveBeenCalledWith(
|
||||
expect(fs.existsSync).toHaveBeenCalled();
|
||||
expect(fs.readFileSync).toHaveBeenCalledWith(
|
||||
'/path/to/report.json',
|
||||
'utf8'
|
||||
);
|
||||
@@ -355,13 +397,13 @@ describe('Utils Module', () => {
|
||||
});
|
||||
|
||||
test('should handle missing report file', () => {
|
||||
fsExistsSyncSpy.mockReturnValue(false);
|
||||
pathJoinSpy.mockReturnValue('/path/to/report.json');
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(false);
|
||||
jest.spyOn(path, 'join').mockReturnValue('/path/to/report.json');
|
||||
|
||||
const result = readComplexityReport();
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(fsReadFileSyncSpy).not.toHaveBeenCalled();
|
||||
expect(fs.readFileSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should handle custom report path', () => {
|
||||
@@ -370,14 +412,16 @@ describe('Utils Module', () => {
|
||||
complexityAnalysis: [{ taskId: 1, complexityScore: 7 }]
|
||||
};
|
||||
|
||||
fsExistsSyncSpy.mockReturnValue(true);
|
||||
fsReadFileSyncSpy.mockReturnValue(JSON.stringify(testReport));
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
jest
|
||||
.spyOn(fs, 'readFileSync')
|
||||
.mockReturnValue(JSON.stringify(testReport));
|
||||
|
||||
const customPath = '/custom/path/report.json';
|
||||
const result = readComplexityReport(customPath);
|
||||
|
||||
expect(fsExistsSyncSpy).toHaveBeenCalledWith(customPath);
|
||||
expect(fsReadFileSyncSpy).toHaveBeenCalledWith(customPath, 'utf8');
|
||||
expect(fs.existsSync).toHaveBeenCalledWith(customPath);
|
||||
expect(fs.readFileSync).toHaveBeenCalledWith(customPath, 'utf8');
|
||||
expect(result).toEqual(testReport);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user