diff --git a/tests/integration/cursor-init-functionality.test.js b/tests/integration/cursor-init-functionality.test.js new file mode 100644 index 00000000..f97bb33c --- /dev/null +++ b/tests/integration/cursor-init-functionality.test.js @@ -0,0 +1,33 @@ +import fs from 'fs'; +import path from 'path'; + +describe('Cursor Profile Initialization Functionality', () => { + let cursorProfileContent; + + beforeAll(() => { + const cursorJsPath = path.join(process.cwd(), 'scripts', 'profiles', 'cursor.js'); + cursorProfileContent = fs.readFileSync(cursorJsPath, 'utf8'); + }); + + test('cursor.js exports correct brandName and rulesDir', () => { + expect(cursorProfileContent).toContain("const brandName = 'Cursor'"); + expect(cursorProfileContent).toContain("const rulesDir = '.cursor/rules'"); + }); + + test('cursor.js preserves .mdc filenames in fileMap', () => { + expect(cursorProfileContent).toContain('fileMap = {'); + // Should NOT contain any .md mapping + expect(cursorProfileContent).not.toMatch(/\.md'/); + }); + + test('cursor.js contains tool naming logic and global replacements', () => { + expect(cursorProfileContent).toContain('edit_file'); + expect(cursorProfileContent).toContain('search tool'); + expect(cursorProfileContent).not.toContain('apply_diff'); + expect(cursorProfileContent).not.toContain('search_files tool'); + }); + + test('cursor.js contains correct documentation URL logic', () => { + expect(cursorProfileContent).toContain('docs.cursor.com'); + }); +}); diff --git a/tests/integration/rules-files-inclusion.test.js b/tests/integration/rules-files-inclusion.test.js new file mode 100644 index 00000000..e6e8b220 --- /dev/null +++ b/tests/integration/rules-files-inclusion.test.js @@ -0,0 +1,42 @@ +import fs from 'fs'; +import path from 'path'; + +describe('Rules Files Inclusion in Package', () => { + test('package.json includes assets/** in the "files" array for rules files', () => { + const packageJsonPath = path.join(process.cwd(), 'package.json'); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + expect(packageJson.files).toContain('assets/**'); + }); + + test('all rules files exist in assets/rules directory', () => { + const rulesDir = path.join(process.cwd(), 'assets', 'rules'); + const expectedFiles = [ + 'ai_providers.mdc', + 'ai_services.mdc', + 'architecture.mdc', + 'changeset.mdc', + 'commands.mdc', + 'cursor_rules.mdc', + 'dependencies.mdc', + 'dev_workflow.mdc', + 'glossary.mdc', + 'mcp.mdc', + 'new_features.mdc', + 'self_improve.mdc', + 'taskmaster.mdc', + 'tasks.mdc', + 'tests.mdc', + 'ui.mdc', + 'utilities.mdc', + ]; + for (const file of expectedFiles) { + expect(fs.existsSync(path.join(rulesDir, file))).toBe(true); + } + }); + + test('assets/rules directory is not empty', () => { + const rulesDir = path.join(process.cwd(), 'assets', 'rules'); + const files = fs.readdirSync(rulesDir).filter(f => !f.startsWith('.')); + expect(files.length).toBeGreaterThan(0); + }); +}); diff --git a/tests/integration/windsurf-init-functionality.test.js b/tests/integration/windsurf-init-functionality.test.js new file mode 100644 index 00000000..70171a02 --- /dev/null +++ b/tests/integration/windsurf-init-functionality.test.js @@ -0,0 +1,36 @@ +import fs from 'fs'; +import path from 'path'; + +describe('Windsurf Profile Initialization Functionality', () => { + let windsurfProfileContent; + + beforeAll(() => { + const windsurfJsPath = path.join(process.cwd(), 'scripts', 'profiles', 'windsurf.js'); + windsurfProfileContent = fs.readFileSync(windsurfJsPath, 'utf8'); + }); + + test('windsurf.js exports correct brandName and rulesDir', () => { + expect(windsurfProfileContent).toContain("const brandName = 'Windsurf'"); + expect(windsurfProfileContent).toContain("const rulesDir = '.windsurf/rules'"); + }); + + test('windsurf.js contains fileMap for .mdc to .md mapping', () => { + expect(windsurfProfileContent).toContain("fileMap = {"); + expect(windsurfProfileContent).toContain(".mdc'"); + expect(windsurfProfileContent).toContain(".md'"); + }); + + test('windsurf.js contains tool renaming and extension logic', () => { + expect(windsurfProfileContent).toContain("edit_file"); + expect(windsurfProfileContent).toContain("apply_diff"); + expect(windsurfProfileContent).toContain("search tool"); + expect(windsurfProfileContent).toContain("search_files tool"); + expect(windsurfProfileContent).toContain(".mdc"); + expect(windsurfProfileContent).toContain(".md"); + }); + + test('windsurf.js contains correct documentation URL transformation', () => { + expect(windsurfProfileContent).toContain('docs.cursor.com'); + expect(windsurfProfileContent).toContain('docs.windsurf.com'); + }); +}); diff --git a/tests/unit/cursor-integration.test.js b/tests/unit/cursor-integration.test.js new file mode 100644 index 00000000..46f65701 --- /dev/null +++ b/tests/unit/cursor-integration.test.js @@ -0,0 +1,90 @@ +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('Cursor 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('.cursormodes')) { + return 'Existing cursormodes 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 Cursor files + function mockCreateCursorStructure() { + // Create main .cursor directory + fs.mkdirSync(path.join(tempDir, '.cursor'), { recursive: true }); + + // Create rules directory + fs.mkdirSync(path.join(tempDir, '.cursor', 'rules'), { recursive: true }); + + // Create mode-specific rule directories + const cursorModes = ['architect', 'ask', 'boomerang', 'code', 'debug', 'test']; + for (const mode of cursorModes) { + fs.mkdirSync(path.join(tempDir, '.cursor', `rules-${mode}`), { + recursive: true + }); + fs.writeFileSync( + path.join(tempDir, '.cursor', `rules-${mode}`, `${mode}-rules`), + `Content for ${mode} rules` + ); + } + + // Copy .cursormodes file + fs.writeFileSync(path.join(tempDir, '.cursormodes'), 'Cursormodes file content'); + } + + test('creates all required .cursor directories', () => { + // Act + mockCreateCursorStructure(); + + // Assert + expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.cursor'), { + recursive: true + }); + expect(fs.mkdirSync).toHaveBeenCalledWith( + path.join(tempDir, '.cursor', 'rules'), + { recursive: true } + ); + }); +}); diff --git a/tests/unit/rule-transformer-cursor.test.js b/tests/unit/rule-transformer-cursor.test.js new file mode 100644 index 00000000..de8eb6e5 --- /dev/null +++ b/tests/unit/rule-transformer-cursor.test.js @@ -0,0 +1,119 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; +import { convertRuleToBrandRule, convertAllRulesToBrandRules } from '../../scripts/modules/rule-transformer.js'; +import * as cursorProfile from '../../scripts/profiles/cursor.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +describe('Cursor Rule Transformer', () => { + const testDir = path.join(__dirname, 'temp-test-dir'); + + beforeAll(() => { + // Create test directory + if (!fs.existsSync(testDir)) { + fs.mkdirSync(testDir, { recursive: true }); + } + }); + + afterAll(() => { + // 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 testCursorOut = path.join(testDir, 'basic-terms.mdc'); + convertRuleToBrandRule(testCursorRule, testCursorOut, cursorProfile); + + // Read the converted file + const convertedContent = fs.readFileSync(testCursorOut, 'utf8'); + + // Verify transformations (should preserve Cursor branding and references) + expect(convertedContent).toContain('Cursor rule'); + expect(convertedContent).toContain('cursor.so'); + expect(convertedContent).toContain('.mdc'); + expect(convertedContent).not.toContain('roocode.com'); + expect(convertedContent).not.toContain('windsurf.com'); + }); + + 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 testCursorOut = path.join(testDir, 'tool-refs.mdc'); + convertRuleToBrandRule(testCursorRule, testCursorOut, cursorProfile); + + // Read the converted file + const convertedContent = fs.readFileSync(testCursorOut, 'utf8'); + + // Verify transformations (should preserve Cursor tool references) + expect(convertedContent).toContain('search tool'); + expect(convertedContent).toContain('edit_file tool'); + expect(convertedContent).toContain('run_command'); + expect(convertedContent).toContain('use_mcp'); + expect(convertedContent).not.toContain('apply_diff'); + expect(convertedContent).not.toContain('search_files'); + }); + + 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 testCursorOut = path.join(testDir, 'file-refs.mdc'); + convertRuleToBrandRule(testCursorRule, testCursorOut, cursorProfile); + + // Read the converted file + const convertedContent = fs.readFileSync(testCursorOut, 'utf8'); + + // Verify transformations (should preserve Cursor file references) + expect(convertedContent).toContain('(mdc:.cursor/rules/dev_workflow.mdc)'); + expect(convertedContent).toContain('(mdc:.cursor/rules/taskmaster.mdc)'); + expect(convertedContent).not.toContain('(mdc:.roo/rules/'); + expect(convertedContent).not.toContain('(mdc:.windsurf/rules/'); + }); + + + +}); diff --git a/tests/unit/rule-transformer.test.js b/tests/unit/rule-transformer-roo.test.js similarity index 79% rename from tests/unit/rule-transformer.test.js rename to tests/unit/rule-transformer-roo.test.js index 810f0372..bd9bf212 100644 --- a/tests/unit/rule-transformer.test.js +++ b/tests/unit/rule-transformer-roo.test.js @@ -2,13 +2,13 @@ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; -import { convertRuleToBrandRule } from '../../scripts/modules/rule-transformer.js'; +import { convertRuleToBrandRule, convertAllRulesToBrandRules } from '../../scripts/modules/rule-transformer.js'; import * as rooProfile from '../../scripts/profiles/roo.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); -describe('Rule Transformer', () => { +describe('Roo Rule Transformer', () => { const testDir = path.join(__dirname, 'temp-test-dir'); beforeAll(() => { @@ -110,4 +110,18 @@ This references [dev_workflow.mdc](mdc:.cursor/rules/dev_workflow.mdc) and expect(convertedContent).toContain('(mdc:.roo/rules/taskmaster.md)'); expect(convertedContent).not.toContain('(mdc:.cursor/rules/'); }); + + it('should run post-processing when converting all rules for Roo', () => { + // Simulate a rules directory with a .mdc file + const assetsRulesDir = path.join(testDir, 'assets', 'rules'); + fs.mkdirSync(assetsRulesDir, { recursive: true }); + const assetRule = path.join(assetsRulesDir, 'dev_workflow.mdc'); + fs.writeFileSync(assetRule, 'dummy'); + // Should create .roo/rules and call post-processing + convertAllRulesToBrandRules(testDir, rooProfile); + // Check for post-processing artifacts, e.g., rules-* folders or extra files + const rooDir = path.join(testDir, '.roo'); + const found = fs.readdirSync(rooDir).some(f => f.startsWith('rules-')); + expect(found).toBe(true); // There should be at least one rules-* folder + }); }); diff --git a/tests/unit/rule-transformer-windsurf.test.js b/tests/unit/rule-transformer-windsurf.test.js new file mode 100644 index 00000000..0e30b5da --- /dev/null +++ b/tests/unit/rule-transformer-windsurf.test.js @@ -0,0 +1,115 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; +import { convertRuleToBrandRule } from '../../scripts/modules/rule-transformer.js'; +import * as windsurfProfile from '../../scripts/profiles/windsurf.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +describe('Windsurf Rule Transformer', () => { + const testDir = path.join(__dirname, 'temp-test-dir'); + + beforeAll(() => { + // Create test directory + if (!fs.existsSync(testDir)) { + fs.mkdirSync(testDir, { recursive: true }); + } + }); + + afterAll(() => { + // 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 testWindsurfRule = path.join(testDir, 'basic-terms.md'); + convertRuleToBrandRule(testCursorRule, testWindsurfRule, windsurfProfile); + + // Read the converted file + const convertedContent = fs.readFileSync(testWindsurfRule, 'utf8'); + + // Verify transformations + expect(convertedContent).toContain('Windsurf'); + expect(convertedContent).toContain('windsurf.com'); + expect(convertedContent).toContain('.md'); + expect(convertedContent).not.toContain('cursor.so'); + expect(convertedContent).not.toContain('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 testWindsurfRule = path.join(testDir, 'tool-refs.md'); + convertRuleToBrandRule(testCursorRule, testWindsurfRule, windsurfProfile); + + // Read the converted file + const convertedContent = fs.readFileSync(testWindsurfRule, 'utf8'); + + // Verify transformations + expect(convertedContent).toContain('search_files tool'); + expect(convertedContent).toContain('apply_diff tool'); + expect(convertedContent).toContain('execute_command'); + expect(convertedContent).toContain('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 testWindsurfRule = path.join(testDir, 'file-refs.md'); + convertRuleToBrandRule(testCursorRule, testWindsurfRule, windsurfProfile); + + // Read the converted file + const convertedContent = fs.readFileSync(testWindsurfRule, 'utf8'); + + // Verify transformations + expect(convertedContent).toContain('(mdc:.windsurf/rules/dev_workflow.md)'); + expect(convertedContent).toContain('(mdc:.windsurf/rules/taskmaster.md)'); + expect(convertedContent).not.toContain('(mdc:.cursor/rules/'); + }); + + +}); diff --git a/tests/unit/windsurf-integration.test.js b/tests/unit/windsurf-integration.test.js new file mode 100644 index 00000000..3d8b581c --- /dev/null +++ b/tests/unit/windsurf-integration.test.js @@ -0,0 +1,90 @@ +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('Windsurf 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('.windsurfmodes')) { + return 'Existing windsurfmodes 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 Windsurf files + function mockCreateWindsurfStructure() { + // Create main .windsurf directory + fs.mkdirSync(path.join(tempDir, '.windsurf'), { recursive: true }); + + // Create rules directory + fs.mkdirSync(path.join(tempDir, '.windsurf', 'rules'), { recursive: true }); + + // Create mode-specific rule directories + const windsurfModes = ['architect', 'ask', 'boomerang', 'code', 'debug', 'test']; + for (const mode of windsurfModes) { + fs.mkdirSync(path.join(tempDir, '.windsurf', `rules-${mode}`), { + recursive: true + }); + fs.writeFileSync( + path.join(tempDir, '.windsurf', `rules-${mode}`, `${mode}-rules`), + `Content for ${mode} rules` + ); + } + + // Copy .windsurfmodes file + fs.writeFileSync(path.join(tempDir, '.windsurfmodes'), 'Windsurfmodes file content'); + } + + test('creates all required .windsurf directories', () => { + // Act + mockCreateWindsurfStructure(); + + // Assert + expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.windsurf'), { + recursive: true + }); + expect(fs.mkdirSync).toHaveBeenCalledWith( + path.join(tempDir, '.windsurf', 'rules'), + { recursive: true } + ); + }); +});