feat: Enhance Task Master CLI with Testing Framework, Perplexity AI Integration, and Refactored Core Logic
This commit introduces significant enhancements and refactoring to the Task Master CLI, focusing on improved testing, integration with Perplexity AI for research-backed task updates, and core logic refactoring for better maintainability and functionality.
**Testing Infrastructure Setup:**
- Implemented Jest as the primary testing framework, setting up a comprehensive testing environment.
- Added new test scripts to including , , and for streamlined testing workflows.
- Integrated necessary devDependencies for testing, such as , , , , and , to support unit, integration, and end-to-end testing.
**Dependency Updates:**
- Updated and to reflect the latest dependency versions, ensuring project stability and access to the newest features and security patches.
- Upgraded to version 0.9.16 and usage: openai [-h] [-v] [-b API_BASE] [-k API_KEY] [-p PROXY [PROXY ...]]
[-o ORGANIZATION] [-t {openai,azure}]
[--api-version API_VERSION] [--azure-endpoint AZURE_ENDPOINT]
[--azure-ad-token AZURE_AD_TOKEN] [-V]
{api,tools,migrate,grit} ...
positional arguments:
{api,tools,migrate,grit}
api Direct API calls
tools Client side tools for convenience
options:
-h, --help show this help message and exit
-v, --verbose Set verbosity.
-b, --api-base API_BASE
What API base url to use.
-k, --api-key API_KEY
What API key to use.
-p, --proxy PROXY [PROXY ...]
What proxy to use.
-o, --organization ORGANIZATION
Which organization to run as (will use your default
organization if not specified)
-t, --api-type {openai,azure}
The backend API to call, must be `openai` or `azure`
--api-version API_VERSION
The Azure API version, e.g.
'https://learn.microsoft.com/en-us/azure/ai-
services/openai/reference#rest-api-versioning'
--azure-endpoint AZURE_ENDPOINT
The Azure endpoint, e.g.
'https://endpoint.openai.azure.com'
--azure-ad-token AZURE_AD_TOKEN
A token from Azure Active Directory,
https://www.microsoft.com/en-
us/security/business/identity-access/microsoft-entra-
id
-V, --version show program's version number and exit to 4.89.0.
- Added dependency (version 2.3.0) and updated related dependencies to their latest versions.
**Perplexity AI Integration for Research-Backed Updates:**
- Introduced an option to leverage Perplexity AI for task updates, enabling research-backed enhancements to task details.
- Implemented logic to initialize a Perplexity AI client if the environment variable is available.
- Modified the function to accept a parameter, allowing dynamic selection between Perplexity AI and Claude AI for task updates based on API key availability and user preference.
- Enhanced to handle responses from Perplexity AI and update tasks accordingly, including improved error handling and logging for robust operation.
**Core Logic Refactoring and Improvements:**
- Refactored the function to utilize task IDs instead of dependency IDs, ensuring consistency and clarity in dependency management.
- Implemented a new function to rigorously check for both circular dependencies and self-dependencies within tasks, improving task relationship integrity.
- Enhanced UI elements in :
- Refactored to incorporate icons for different task statuses and utilize a object for color mapping, improving visual representation of task status.
- Updated to display colored complexity scores with emojis, providing a more intuitive and visually appealing representation of task complexity.
- Refactored the task data structure creation and validation process:
- Updated the JSON Schema for to reflect a more streamlined and efficient task structure.
- Implemented Task Model Classes for better data modeling and type safety.
- Improved File System Operations for task data management.
- Developed robust Validation Functions and an Error Handling System to ensure data integrity and application stability.
**Testing Guidelines Implementation:**
- Implemented guidelines for writing testable code when developing new features, promoting a test-driven development approach.
- Added testing requirements and best practices for unit, integration, and edge case testing to ensure comprehensive test coverage.
- Updated the development workflow to mandate writing tests before proceeding with configuration and documentation updates, reinforcing the importance of testing throughout the development lifecycle.
This commit collectively enhances the Task Master CLI's reliability, functionality, and developer experience through improved testing practices, AI-powered research capabilities, and a more robust and maintainable codebase.
This commit is contained in:
152
.cursor/rules/architecture.mdc
Normal file
152
.cursor/rules/architecture.mdc
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
description: Describes the high-level architecture of the Task Master CLI application.
|
||||
globs: scripts/modules/*.js
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Application Architecture Overview
|
||||
|
||||
- **Modular Structure**: The Task Master CLI is built using a modular architecture, with distinct modules responsible for different aspects of the application. This promotes separation of concerns, maintainability, and testability.
|
||||
|
||||
- **Main Modules and Responsibilities**:
|
||||
|
||||
- **[`commands.js`](mdc:scripts/modules/commands.js): Command Handling**
|
||||
- **Purpose**: Defines and registers all CLI commands using Commander.js.
|
||||
- **Responsibilities**:
|
||||
- Parses command-line arguments and options.
|
||||
- Invokes appropriate functions from other modules to execute commands.
|
||||
- Handles user input and output related to command execution.
|
||||
- Implements input validation and error handling for CLI commands.
|
||||
- **Key Components**:
|
||||
- `programInstance` (Commander.js `Command` instance): Manages command definitions.
|
||||
- `registerCommands(programInstance)`: Function to register all application commands.
|
||||
- Command action handlers: Functions executed when a specific command is invoked.
|
||||
|
||||
- **[`task-manager.js`](mdc:scripts/modules/task-manager.js): Task Data Management**
|
||||
- **Purpose**: Manages task data, including loading, saving, creating, updating, deleting, and querying tasks.
|
||||
- **Responsibilities**:
|
||||
- Reads and writes task data to `tasks.json` file.
|
||||
- Implements functions for task CRUD operations (Create, Read, Update, Delete).
|
||||
- Handles task parsing from PRD documents using AI.
|
||||
- Manages task expansion and subtask generation.
|
||||
- Updates task statuses and properties.
|
||||
- Implements task listing and display logic.
|
||||
- Performs task complexity analysis using AI.
|
||||
- **Key Functions**:
|
||||
- `readTasks(tasksPath)` / `writeTasks(tasksPath, tasksData)`: Load and save task data.
|
||||
- `parsePRD(prdFilePath, outputPath, numTasks)`: Parses PRD document to create tasks.
|
||||
- `expandTask(taskId, numSubtasks, useResearch, prompt, force)`: Expands a task into subtasks.
|
||||
- `setTaskStatus(tasksPath, taskIdInput, newStatus)`: Updates task status.
|
||||
- `listTasks(tasksPath, statusFilter, withSubtasks)`: Lists tasks with filtering and subtask display options.
|
||||
- `analyzeComplexity(tasksPath, reportPath, useResearch, thresholdScore)`: Analyzes task complexity.
|
||||
|
||||
- **[`dependency-manager.js`](mdc:scripts/modules/dependency-manager.js): Dependency Management**
|
||||
- **Purpose**: Manages task dependencies, including adding, removing, validating, and fixing dependency relationships.
|
||||
- **Responsibilities**:
|
||||
- Adds and removes task dependencies.
|
||||
- Validates dependency relationships to prevent circular dependencies and invalid references.
|
||||
- Fixes invalid dependencies by removing non-existent or self-referential dependencies.
|
||||
- Provides functions to check for circular dependencies.
|
||||
- **Key Functions**:
|
||||
- `addDependency(tasksPath, taskId, dependencyId)`: Adds a dependency between tasks.
|
||||
- `removeDependency(tasksPath, taskId, dependencyId)`: Removes a dependency.
|
||||
- `validateDependencies(tasksPath)`: Validates task dependencies.
|
||||
- `fixDependencies(tasksPath)`: Fixes invalid task dependencies.
|
||||
- `isCircularDependency(tasks, taskId, dependencyChain)`: Detects circular dependencies.
|
||||
|
||||
- **[`ui.js`](mdc:scripts/modules/ui.js): User Interface Components**
|
||||
- **Purpose**: Handles all user interface elements, including displaying information, formatting output, and providing user feedback.
|
||||
- **Responsibilities**:
|
||||
- Displays task lists, task details, and command outputs in a formatted way.
|
||||
- Uses `chalk` for colored output and `boxen` for boxed messages.
|
||||
- Implements table display using `cli-table3`.
|
||||
- Shows loading indicators using `ora`.
|
||||
- Provides helper functions for status formatting, dependency display, and progress reporting.
|
||||
- Suggests next actions to the user after command execution.
|
||||
- **Key Functions**:
|
||||
- `displayTaskList(tasks, statusFilter, withSubtasks)`: Displays a list of tasks in a table.
|
||||
- `displayTaskDetails(task)`: Displays detailed information for a single task.
|
||||
- `displayComplexityReport(reportPath)`: Displays the task complexity report.
|
||||
- `startLoadingIndicator(message)` / `stopLoadingIndicator(indicator)`: Manages loading indicators.
|
||||
- `getStatusWithColor(status)`: Returns status string with color formatting.
|
||||
- `formatDependenciesWithStatus(dependencies, allTasks, inTable)`: Formats dependency list with status indicators.
|
||||
|
||||
- **[`ai-services.js`](mdc:scripts/modules/ai-services.js) (Conceptual): AI Integration**
|
||||
- **Purpose**: Abstracts interactions with AI models (like Anthropic Claude and Perplexity AI) for various features. *Note: This module might be implicitly implemented within `task-manager.js` and `utils.js` or could be explicitly created for better organization as the project evolves.*
|
||||
- **Responsibilities**:
|
||||
- Handles API calls to AI services.
|
||||
- Manages prompts and parameters for AI requests.
|
||||
- Parses AI responses and extracts relevant information.
|
||||
- Implements logic for task complexity analysis, task expansion, and PRD parsing using AI.
|
||||
- **Potential Functions**:
|
||||
- `getAIResponse(prompt, model, maxTokens, temperature)`: Generic function to interact with AI model.
|
||||
- `analyzeTaskComplexityWithAI(taskDescription)`: Sends task description to AI for complexity analysis.
|
||||
- `expandTaskWithAI(taskDescription, numSubtasks, researchContext)`: Generates subtasks using AI.
|
||||
- `parsePRDWithAI(prdContent)`: Extracts tasks from PRD content using AI.
|
||||
|
||||
- **[`utils.js`](mdc:scripts/modules/utils.js): Utility Functions and Configuration**
|
||||
- **Purpose**: Provides reusable utility functions and global configuration settings used across the application.
|
||||
- **Responsibilities**:
|
||||
- Manages global configuration settings loaded from environment variables and defaults.
|
||||
- Implements logging utility with different log levels and output formatting.
|
||||
- Provides file system operation utilities (read/write JSON files).
|
||||
- Includes string manipulation utilities (e.g., `truncate`, `sanitizePrompt`).
|
||||
- Offers task-specific utility functions (e.g., `formatTaskId`, `findTaskById`, `taskExists`).
|
||||
- Implements graph algorithms like cycle detection for dependency management.
|
||||
- **Key Components**:
|
||||
- `CONFIG`: Global configuration object.
|
||||
- `log(level, ...args)`: Logging function.
|
||||
- `readJSON(filepath)` / `writeJSON(filepath, data)`: File I/O utilities for JSON files.
|
||||
- `truncate(text, maxLength)`: String truncation utility.
|
||||
- `formatTaskId(id)` / `findTaskById(tasks, taskId)`: Task ID and search utilities.
|
||||
- `findCycles(subtaskId, dependencyMap)`: Cycle detection algorithm.
|
||||
|
||||
- **Data Flow and Module Dependencies**:
|
||||
|
||||
- **Commands Initiate Actions**: User commands entered via the CLI (handled by [`commands.js`](mdc:scripts/modules/commands.js)) are the entry points for most operations.
|
||||
- **Command Handlers Delegate to Managers**: Command handlers in [`commands.js`](mdc:scripts/modules/commands.js) call functions in [`task-manager.js`](mdc:scripts/modules/task-manager.js) and [`dependency-manager.js`](mdc:scripts/modules/dependency-manager.js) to perform core task and dependency management logic.
|
||||
- **UI for Presentation**: [`ui.js`](mdc:scripts/modules/ui.js) is used by command handlers and task/dependency managers to display information to the user. UI functions primarily consume data and format it for output, without modifying core application state.
|
||||
- **Utilities for Common Tasks**: [`utils.js`](mdc:scripts/modules/utils.js) provides helper functions used by all other modules for configuration, logging, file operations, and common data manipulations.
|
||||
- **AI Services Integration**: AI functionalities (complexity analysis, task expansion, PRD parsing) are invoked from [`task-manager.js`](mdc:scripts/modules/task-manager.js) and potentially [`commands.js`](mdc:scripts/modules/commands.js), likely using functions that would reside in a dedicated `ai-services.js` module or be integrated within `utils.js` or `task-manager.js`.
|
||||
|
||||
- **Testing Architecture**:
|
||||
|
||||
- **Test Organization Structure**:
|
||||
- **Unit Tests**: Located in `tests/unit/`, reflect the module structure with one test file per module
|
||||
- **Integration Tests**: Located in `tests/integration/`, test interactions between modules
|
||||
- **End-to-End Tests**: Located in `tests/e2e/`, test complete workflows from a user perspective
|
||||
- **Test Fixtures**: Located in `tests/fixtures/`, provide reusable test data
|
||||
|
||||
- **Module Design for Testability**:
|
||||
- **Explicit Dependencies**: Functions accept their dependencies as parameters rather than using globals
|
||||
- **Functional Style**: Pure functions with minimal side effects make testing deterministic
|
||||
- **Separate Logic from I/O**: Core business logic is separated from file system operations
|
||||
- **Clear Module Interfaces**: Each module has well-defined exports that can be mocked in tests
|
||||
- **Callback Isolation**: Callbacks are defined as separate functions for easier testing
|
||||
- **Stateless Design**: Modules avoid maintaining internal state where possible
|
||||
|
||||
- **Mock Integration Patterns**:
|
||||
- **External Libraries**: Libraries like `fs`, `commander`, and `@anthropic-ai/sdk` are mocked at module level
|
||||
- **Internal Modules**: Application modules are mocked with appropriate spy functions
|
||||
- **Testing Function Callbacks**: Callbacks are extracted from mock call arguments and tested in isolation
|
||||
- **UI Elements**: Output functions from `ui.js` are mocked to verify display calls
|
||||
|
||||
- **Testing Flow**:
|
||||
- Module dependencies are mocked (following Jest's hoisting behavior)
|
||||
- Test modules are imported after mocks are established
|
||||
- Spy functions are set up on module methods
|
||||
- Tests call the functions under test and verify behavior
|
||||
- Mocks are reset between test cases to maintain isolation
|
||||
|
||||
- **Benefits of this Architecture**:
|
||||
|
||||
- **Maintainability**: Modules are self-contained and focused, making it easier to understand, modify, and debug specific features.
|
||||
- **Testability**: Each module can be tested in isolation (unit testing), and interactions between modules can be tested (integration testing).
|
||||
- **Mocking Support**: The clear dependency boundaries make mocking straightforward
|
||||
- **Test Isolation**: Each component can be tested without affecting others
|
||||
- **Callback Testing**: Function callbacks can be extracted and tested independently
|
||||
- **Reusability**: Utility functions and UI components can be reused across different parts of the application.
|
||||
- **Scalability**: New features can be added as new modules or by extending existing ones without significantly impacting other parts of the application.
|
||||
- **Clarity**: The modular structure provides a clear separation of concerns, making the codebase easier to navigate and understand for developers.
|
||||
|
||||
This architectural overview should help AI models understand the structure and organization of the Task Master CLI codebase, enabling them to more effectively assist with code generation, modification, and understanding.
|
||||
@@ -27,8 +27,9 @@ The standard pattern for adding a feature follows this workflow:
|
||||
1. **Core Logic**: Implement the business logic in the appropriate module
|
||||
2. **UI Components**: Add any display functions to [`ui.js`](mdc:scripts/modules/ui.js)
|
||||
3. **Command Integration**: Add the CLI command to [`commands.js`](mdc:scripts/modules/commands.js)
|
||||
4. **Configuration**: Update any configuration in [`utils.js`](mdc:scripts/modules/utils.js) if needed
|
||||
5. **Documentation**: Update help text and documentation in [dev_workflow.mdc](mdc:scripts/modules/dev_workflow.mdc)
|
||||
4. **Testing**: Write tests for all components of the feature (following [`tests.mdc`](mdc:.cursor/rules/tests.mdc))
|
||||
5. **Configuration**: Update any configuration in [`utils.js`](mdc:scripts/modules/utils.js) if needed
|
||||
6. **Documentation**: Update help text and documentation in [dev_workflow.mdc](mdc:scripts/modules/dev_workflow.mdc)
|
||||
|
||||
```javascript
|
||||
// 1. CORE LOGIC: Add function to appropriate module (example in task-manager.js)
|
||||
@@ -167,26 +168,125 @@ function formatDuration(ms) {
|
||||
}
|
||||
```
|
||||
|
||||
## Testing New Features
|
||||
## Writing Testable Code
|
||||
|
||||
Before submitting a new feature:
|
||||
When implementing new features, follow these guidelines to ensure your code is testable:
|
||||
|
||||
1. Verify export/import structure with:
|
||||
```bash
|
||||
grep -A15 "export {" scripts/modules/*.js
|
||||
grep -A15 "import {" scripts/modules/*.js | grep -v "^--$"
|
||||
- **Dependency Injection**
|
||||
- Design functions to accept dependencies as parameters
|
||||
- Avoid hard-coded dependencies that are difficult to mock
|
||||
```javascript
|
||||
// ✅ DO: Accept dependencies as parameters
|
||||
function processTask(task, fileSystem, logger) {
|
||||
fileSystem.writeFile('task.json', JSON.stringify(task));
|
||||
logger.info('Task processed');
|
||||
}
|
||||
|
||||
// ❌ DON'T: Use hard-coded dependencies
|
||||
function processTask(task) {
|
||||
fs.writeFile('task.json', JSON.stringify(task));
|
||||
console.log('Task processed');
|
||||
}
|
||||
```
|
||||
|
||||
- **Separate Logic from Side Effects**
|
||||
- Keep pure logic separate from I/O operations or UI rendering
|
||||
- This allows testing the logic without mocking complex dependencies
|
||||
```javascript
|
||||
// ✅ DO: Separate logic from side effects
|
||||
function calculateTaskPriority(task, dependencies) {
|
||||
// Pure logic that returns a value
|
||||
return computedPriority;
|
||||
}
|
||||
|
||||
function displayTaskPriority(task, dependencies) {
|
||||
const priority = calculateTaskPriority(task, dependencies);
|
||||
console.log(`Task priority: ${priority}`);
|
||||
}
|
||||
```
|
||||
|
||||
- **Callback Functions and Testing**
|
||||
- When using callbacks (like in Commander.js commands), define them separately
|
||||
- This allows testing the callback logic independently
|
||||
```javascript
|
||||
// ✅ DO: Define callbacks separately for testing
|
||||
function getVersionString() {
|
||||
// Logic to determine version
|
||||
return version;
|
||||
}
|
||||
|
||||
// In setupCLI
|
||||
programInstance.version(getVersionString);
|
||||
|
||||
// In tests
|
||||
test('getVersionString returns correct version', () => {
|
||||
expect(getVersionString()).toBe('1.5.0');
|
||||
});
|
||||
```
|
||||
|
||||
- **UI Output Testing**
|
||||
- For UI components, focus on testing conditional logic rather than exact output
|
||||
- Use string pattern matching (like `expect(result).toContain('text')`)
|
||||
- Pay attention to emojis and formatting which can make exact string matching difficult
|
||||
```javascript
|
||||
// ✅ DO: Test the essence of the output, not exact formatting
|
||||
test('statusFormatter shows done status correctly', () => {
|
||||
const result = formatStatus('done');
|
||||
expect(result).toContain('done');
|
||||
expect(result).toContain('✅');
|
||||
});
|
||||
```
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
Every new feature **must** include comprehensive tests following the guidelines in [`tests.mdc`](mdc:.cursor/rules/tests.mdc). Testing should include:
|
||||
|
||||
1. **Unit Tests**: Test individual functions and components in isolation
|
||||
```javascript
|
||||
// Example unit test for a new utility function
|
||||
describe('newFeatureUtil', () => {
|
||||
test('should perform expected operation with valid input', () => {
|
||||
expect(newFeatureUtil('valid input')).toBe('expected result');
|
||||
});
|
||||
|
||||
test('should handle edge cases appropriately', () => {
|
||||
expect(newFeatureUtil('')).toBeNull();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
2. Test the feature with valid input:
|
||||
```bash
|
||||
task-master your-command --option1=value
|
||||
2. **Integration Tests**: Verify the feature works correctly with other components
|
||||
```javascript
|
||||
// Example integration test for a new command
|
||||
describe('newCommand integration', () => {
|
||||
test('should call the correct service functions with parsed arguments', () => {
|
||||
const mockService = jest.fn().mockResolvedValue('success');
|
||||
// Set up test with mocked dependencies
|
||||
// Call the command handler
|
||||
// Verify service was called with expected arguments
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
3. Test the feature with edge cases:
|
||||
```bash
|
||||
task-master your-command --option1=""
|
||||
task-master your-command # without required options
|
||||
```
|
||||
3. **Edge Cases**: Test boundary conditions and error handling
|
||||
- Invalid inputs
|
||||
- Missing dependencies
|
||||
- File system errors
|
||||
- API failures
|
||||
|
||||
4. **Test Coverage**: Aim for at least 80% coverage for all new code
|
||||
|
||||
5. **Jest Mocking Best Practices**
|
||||
- Follow the mock-first-then-import pattern as described in [`tests.mdc`](mdc:.cursor/rules/tests.mdc)
|
||||
- Use jest.spyOn() to create spy functions for testing
|
||||
- Clear mocks between tests to prevent interference
|
||||
- See the Jest Module Mocking Best Practices section in [`tests.mdc`](mdc:.cursor/rules/tests.mdc) for details
|
||||
|
||||
When submitting a new feature, always run the full test suite to ensure nothing was broken:
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
## Documentation Requirements
|
||||
|
||||
|
||||
285
.cursor/rules/tests.mdc
Normal file
285
.cursor/rules/tests.mdc
Normal file
@@ -0,0 +1,285 @@
|
||||
---
|
||||
description: Guidelines for implementing and maintaining tests for Task Master CLI
|
||||
globs: "**/*.test.js,tests/**/*"
|
||||
---
|
||||
|
||||
# Testing Guidelines for Task Master CLI
|
||||
|
||||
## Test Organization Structure
|
||||
|
||||
- **Unit Tests**
|
||||
- Located in `tests/unit/`
|
||||
- Test individual functions and utilities in isolation
|
||||
- Mock all external dependencies
|
||||
- Keep tests small, focused, and fast
|
||||
- Example naming: `utils.test.js`, `task-manager.test.js`
|
||||
|
||||
- **Integration Tests**
|
||||
- Located in `tests/integration/`
|
||||
- Test interactions between modules
|
||||
- Focus on component interfaces rather than implementation details
|
||||
- Use more realistic but still controlled test environments
|
||||
- Example naming: `task-workflow.test.js`, `command-integration.test.js`
|
||||
|
||||
- **End-to-End Tests**
|
||||
- Located in `tests/e2e/`
|
||||
- Test complete workflows from a user perspective
|
||||
- Focus on CLI commands as they would be used by users
|
||||
- Example naming: `create-task.e2e.test.js`, `expand-task.e2e.test.js`
|
||||
|
||||
- **Test Fixtures**
|
||||
- Located in `tests/fixtures/`
|
||||
- Provide reusable test data
|
||||
- Keep fixtures small and representative
|
||||
- Export fixtures as named exports for reuse
|
||||
|
||||
## Test File Organization
|
||||
|
||||
```javascript
|
||||
// 1. Imports
|
||||
import { jest } from '@jest/globals';
|
||||
|
||||
// 2. Mock setup (MUST come before importing the modules under test)
|
||||
jest.mock('fs');
|
||||
jest.mock('@anthropic-ai/sdk');
|
||||
jest.mock('../../scripts/modules/utils.js', () => ({
|
||||
CONFIG: {
|
||||
projectVersion: '1.5.0'
|
||||
},
|
||||
log: jest.fn()
|
||||
}));
|
||||
|
||||
// 3. Import modules AFTER all mocks are defined
|
||||
import { functionToTest } from '../../scripts/modules/module-name.js';
|
||||
import { testFixture } from '../fixtures/fixture-name.js';
|
||||
import fs from 'fs';
|
||||
|
||||
// 4. Set up spies on mocked modules (if needed)
|
||||
const mockReadFileSync = jest.spyOn(fs, 'readFileSync');
|
||||
|
||||
// 5. Test suite with descriptive name
|
||||
describe('Feature or Function Name', () => {
|
||||
// 6. Setup and teardown (if needed)
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
// Additional setup code
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Cleanup code
|
||||
});
|
||||
|
||||
// 7. Grouped tests for related functionality
|
||||
describe('specific functionality', () => {
|
||||
// 8. Individual test cases with clear descriptions
|
||||
test('should behave in expected way when given specific input', () => {
|
||||
// Arrange - set up test data
|
||||
const input = testFixture.sampleInput;
|
||||
mockReadFileSync.mockReturnValue('mocked content');
|
||||
|
||||
// Act - call the function being tested
|
||||
const result = functionToTest(input);
|
||||
|
||||
// Assert - verify the result
|
||||
expect(result).toBe(expectedOutput);
|
||||
expect(mockReadFileSync).toHaveBeenCalledWith(expect.stringContaining('path'));
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Jest Module Mocking Best Practices
|
||||
|
||||
- **Mock Hoisting Behavior**
|
||||
- Jest hoists `jest.mock()` calls to the top of the file, even above imports
|
||||
- Always declare mocks before importing the modules being tested
|
||||
- Use the factory pattern for complex mocks that need access to other variables
|
||||
|
||||
```javascript
|
||||
// ✅ DO: Place mocks before imports
|
||||
jest.mock('commander');
|
||||
import { program } from 'commander';
|
||||
|
||||
// ❌ DON'T: Define variables and then try to use them in mocks
|
||||
const mockFn = jest.fn();
|
||||
jest.mock('module', () => ({
|
||||
func: mockFn // This won't work due to hoisting!
|
||||
}));
|
||||
```
|
||||
|
||||
- **Mocking Modules with Function References**
|
||||
- Use `jest.spyOn()` after imports to create spies on mock functions
|
||||
- Reference these spies in test assertions
|
||||
|
||||
```javascript
|
||||
// Mock the module first
|
||||
jest.mock('fs');
|
||||
|
||||
// Import the mocked module
|
||||
import fs from 'fs';
|
||||
|
||||
// Create spies on the mock functions
|
||||
const mockExistsSync = jest.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
|
||||
test('should call existsSync', () => {
|
||||
// Call function that uses fs.existsSync
|
||||
const result = functionUnderTest();
|
||||
|
||||
// Verify the mock was called correctly
|
||||
expect(mockExistsSync).toHaveBeenCalled();
|
||||
});
|
||||
```
|
||||
|
||||
- **Testing Functions with Callbacks**
|
||||
- Get the callback from your mock's call arguments
|
||||
- Execute it directly with test inputs
|
||||
- Verify the results match expectations
|
||||
|
||||
```javascript
|
||||
jest.mock('commander');
|
||||
import { program } from 'commander';
|
||||
import { setupCLI } from '../../scripts/modules/commands.js';
|
||||
|
||||
const mockVersion = jest.spyOn(program, 'version').mockReturnValue(program);
|
||||
|
||||
test('version callback should return correct version', () => {
|
||||
// Call the function that registers the callback
|
||||
setupCLI();
|
||||
|
||||
// Extract the callback function
|
||||
const versionCallback = mockVersion.mock.calls[0][0];
|
||||
expect(typeof versionCallback).toBe('function');
|
||||
|
||||
// Execute the callback and verify results
|
||||
const result = versionCallback();
|
||||
expect(result).toBe('1.5.0');
|
||||
});
|
||||
```
|
||||
|
||||
## Mocking Guidelines
|
||||
|
||||
- **File System Operations**
|
||||
```javascript
|
||||
import mockFs from 'mock-fs';
|
||||
|
||||
beforeEach(() => {
|
||||
mockFs({
|
||||
'tasks': {
|
||||
'tasks.json': JSON.stringify({
|
||||
meta: { projectName: 'Test Project' },
|
||||
tasks: []
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
```
|
||||
|
||||
- **API Calls (Anthropic/Claude)**
|
||||
```javascript
|
||||
import { Anthropic } from '@anthropic-ai/sdk';
|
||||
|
||||
jest.mock('@anthropic-ai/sdk');
|
||||
|
||||
beforeEach(() => {
|
||||
Anthropic.mockImplementation(() => ({
|
||||
messages: {
|
||||
create: jest.fn().mockResolvedValue({
|
||||
content: [{ text: 'Mocked response' }]
|
||||
})
|
||||
}
|
||||
}));
|
||||
});
|
||||
```
|
||||
|
||||
- **Environment Variables**
|
||||
```javascript
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
process.env = { ...originalEnv };
|
||||
process.env.MODEL = 'test-model';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
```
|
||||
|
||||
## Testing Common Components
|
||||
|
||||
- **CLI Commands**
|
||||
- Mock the action handlers and verify they're called with correct arguments
|
||||
- Test command registration and option parsing
|
||||
- Use `commander` test utilities or custom mocks
|
||||
|
||||
- **Task Operations**
|
||||
- Use sample task fixtures for consistent test data
|
||||
- Mock file system operations
|
||||
- Test both success and error paths
|
||||
|
||||
- **UI Functions**
|
||||
- Mock console output and verify correct formatting
|
||||
- Test conditional output logic
|
||||
- When testing strings with emojis or formatting, use `toContain()` or `toMatch()` rather than exact `toBe()` comparisons
|
||||
|
||||
## Test Quality Guidelines
|
||||
|
||||
- ✅ **DO**: Write tests before implementing features (TDD approach when possible)
|
||||
- ✅ **DO**: Test edge cases and error conditions, not just happy paths
|
||||
- ✅ **DO**: Keep tests independent and isolated from each other
|
||||
- ✅ **DO**: Use descriptive test names that explain the expected behavior
|
||||
- ✅ **DO**: Maintain test fixtures separate from test logic
|
||||
- ✅ **DO**: Aim for 80%+ code coverage, with critical paths at 100%
|
||||
- ✅ **DO**: Follow the mock-first-then-import pattern for all Jest mocks
|
||||
|
||||
- ❌ **DON'T**: Test implementation details that might change
|
||||
- ❌ **DON'T**: Write brittle tests that depend on specific output formatting
|
||||
- ❌ **DON'T**: Skip testing error handling and validation
|
||||
- ❌ **DON'T**: Duplicate test fixtures across multiple test files
|
||||
- ❌ **DON'T**: Write tests that depend on execution order
|
||||
- ❌ **DON'T**: Define mock variables before `jest.mock()` calls (they won't be accessible due to hoisting)
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
npm test
|
||||
|
||||
# Run tests in watch mode
|
||||
npm run test:watch
|
||||
|
||||
# Run tests with coverage reporting
|
||||
npm run test:coverage
|
||||
|
||||
# Run a specific test file
|
||||
npm test -- tests/unit/specific-file.test.js
|
||||
|
||||
# Run tests matching a pattern
|
||||
npm test -- -t "pattern to match"
|
||||
```
|
||||
|
||||
## Troubleshooting Test Issues
|
||||
|
||||
- **Mock Functions Not Called**
|
||||
- Ensure mocks are defined before imports (Jest hoists `jest.mock()` calls)
|
||||
- Check that you're referencing the correct mock instance
|
||||
- Verify the import paths match exactly
|
||||
|
||||
- **Unexpected Mock Behavior**
|
||||
- Clear mocks between tests with `jest.clearAllMocks()` in `beforeEach`
|
||||
- Check mock implementation for conditional behavior
|
||||
- Ensure mock return values are correctly configured for each test
|
||||
|
||||
- **Tests Affecting Each Other**
|
||||
- Isolate tests by properly mocking shared resources
|
||||
- Reset state in `beforeEach` and `afterEach` hooks
|
||||
- Avoid global state modifications
|
||||
|
||||
See [tests/README.md](mdc:tests/README.md) for more details on the testing approach.
|
||||
|
||||
Refer to [jest.config.js](mdc:jest.config.js) for Jest configuration options.
|
||||
Reference in New Issue
Block a user