feat: Add Zed editor rule profile with agent rules and MCP config (#974)
* zed profile * add changeset * update changeset
This commit is contained in:
committed by
Ralph Khreish
parent
6c5e0f97f8
commit
b0e09c76ed
@@ -46,6 +46,12 @@ describe('MCP Configuration Validation', () => {
|
||||
expectedDir: '.windsurf',
|
||||
expectedConfigName: 'mcp.json',
|
||||
expectedPath: '.windsurf/mcp.json'
|
||||
},
|
||||
zed: {
|
||||
shouldHaveMcp: true,
|
||||
expectedDir: '.zed',
|
||||
expectedConfigName: 'settings.json',
|
||||
expectedPath: '.zed/settings.json'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
212
tests/unit/profiles/rule-transformer-zed.test.js
Normal file
212
tests/unit/profiles/rule-transformer-zed.test.js
Normal file
@@ -0,0 +1,212 @@
|
||||
import { jest } from '@jest/globals';
|
||||
|
||||
// Mock fs module before importing anything that uses it
|
||||
jest.mock('fs', () => ({
|
||||
readFileSync: jest.fn(),
|
||||
writeFileSync: jest.fn(),
|
||||
existsSync: jest.fn(),
|
||||
mkdirSync: jest.fn()
|
||||
}));
|
||||
|
||||
// Import modules after mocking
|
||||
import fs from 'fs';
|
||||
import { convertRuleToProfileRule } from '../../../src/utils/rule-transformer.js';
|
||||
import { zedProfile } from '../../../src/profiles/zed.js';
|
||||
|
||||
describe('Zed Rule Transformer', () => {
|
||||
// Set up spies on the mocked modules
|
||||
const mockReadFileSync = jest.spyOn(fs, 'readFileSync');
|
||||
const mockWriteFileSync = jest.spyOn(fs, 'writeFileSync');
|
||||
const mockExistsSync = jest.spyOn(fs, 'existsSync');
|
||||
const mockMkdirSync = jest.spyOn(fs, 'mkdirSync');
|
||||
const mockConsoleError = jest
|
||||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
// Setup default mocks
|
||||
mockReadFileSync.mockReturnValue('');
|
||||
mockWriteFileSync.mockImplementation(() => {});
|
||||
mockExistsSync.mockReturnValue(true);
|
||||
mockMkdirSync.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should correctly convert basic terms', () => {
|
||||
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.`;
|
||||
|
||||
// Mock file read to return our test content
|
||||
mockReadFileSync.mockReturnValue(testContent);
|
||||
|
||||
// Mock file system operations
|
||||
mockExistsSync.mockReturnValue(true);
|
||||
|
||||
// Call the function
|
||||
const result = convertRuleToProfileRule(
|
||||
'test-source.mdc',
|
||||
'test-target.md',
|
||||
zedProfile
|
||||
);
|
||||
|
||||
// Verify the result
|
||||
expect(result).toBe(true);
|
||||
expect(mockWriteFileSync).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Get the transformed content
|
||||
const transformedContent = mockWriteFileSync.mock.calls[0][1];
|
||||
|
||||
// Verify Cursor -> Zed transformations
|
||||
expect(transformedContent).toContain('zed.dev');
|
||||
expect(transformedContent).toContain('Zed');
|
||||
expect(transformedContent).not.toContain('cursor.so');
|
||||
expect(transformedContent).not.toContain('Cursor');
|
||||
expect(transformedContent).toContain('.md');
|
||||
expect(transformedContent).not.toContain('.mdc');
|
||||
});
|
||||
|
||||
it('should handle URL transformations', () => {
|
||||
const testContent = `Visit https://cursor.so/docs for more information.
|
||||
Also check out cursor.so and www.cursor.so for updates.`;
|
||||
|
||||
mockReadFileSync.mockReturnValue(testContent);
|
||||
mockExistsSync.mockReturnValue(true);
|
||||
|
||||
const result = convertRuleToProfileRule(
|
||||
'test-source.mdc',
|
||||
'test-target.md',
|
||||
zedProfile
|
||||
);
|
||||
|
||||
expect(result).toBe(true);
|
||||
const transformedContent = mockWriteFileSync.mock.calls[0][1];
|
||||
|
||||
// Verify URL transformations
|
||||
expect(transformedContent).toContain('https://zed.dev');
|
||||
expect(transformedContent).toContain('zed.dev');
|
||||
expect(transformedContent).not.toContain('cursor.so');
|
||||
});
|
||||
|
||||
it('should handle file extension transformations', () => {
|
||||
const testContent = `This rule references file.mdc and another.mdc file.
|
||||
Use the .mdc extension for all rule files.`;
|
||||
|
||||
mockReadFileSync.mockReturnValue(testContent);
|
||||
mockExistsSync.mockReturnValue(true);
|
||||
|
||||
const result = convertRuleToProfileRule(
|
||||
'test-source.mdc',
|
||||
'test-target.md',
|
||||
zedProfile
|
||||
);
|
||||
|
||||
expect(result).toBe(true);
|
||||
const transformedContent = mockWriteFileSync.mock.calls[0][1];
|
||||
|
||||
// Verify file extension transformations
|
||||
expect(transformedContent).toContain('file.md');
|
||||
expect(transformedContent).toContain('another.md');
|
||||
expect(transformedContent).toContain('.md extension');
|
||||
expect(transformedContent).not.toContain('.mdc');
|
||||
});
|
||||
|
||||
it('should handle case variations', () => {
|
||||
const testContent = `CURSOR, Cursor, cursor should all be transformed.`;
|
||||
|
||||
mockReadFileSync.mockReturnValue(testContent);
|
||||
mockExistsSync.mockReturnValue(true);
|
||||
|
||||
const result = convertRuleToProfileRule(
|
||||
'test-source.mdc',
|
||||
'test-target.md',
|
||||
zedProfile
|
||||
);
|
||||
|
||||
expect(result).toBe(true);
|
||||
const transformedContent = mockWriteFileSync.mock.calls[0][1];
|
||||
|
||||
// Verify case transformations
|
||||
// Due to regex order, the case-insensitive rule runs first:
|
||||
// CURSOR -> Zed (because it starts with 'C'), Cursor -> Zed, cursor -> zed
|
||||
expect(transformedContent).toContain('Zed');
|
||||
expect(transformedContent).toContain('zed');
|
||||
expect(transformedContent).not.toContain('CURSOR');
|
||||
expect(transformedContent).not.toContain('Cursor');
|
||||
expect(transformedContent).not.toContain('cursor');
|
||||
});
|
||||
|
||||
it('should create target directory if it does not exist', () => {
|
||||
const testContent = 'Test content';
|
||||
mockReadFileSync.mockReturnValue(testContent);
|
||||
mockExistsSync.mockReturnValue(false);
|
||||
|
||||
const result = convertRuleToProfileRule(
|
||||
'test-source.mdc',
|
||||
'nested/path/test-target.md',
|
||||
zedProfile
|
||||
);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockMkdirSync).toHaveBeenCalledWith('nested/path', {
|
||||
recursive: true
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle file system errors gracefully', () => {
|
||||
mockReadFileSync.mockImplementation(() => {
|
||||
throw new Error('File not found');
|
||||
});
|
||||
|
||||
const result = convertRuleToProfileRule(
|
||||
'test-source.mdc',
|
||||
'test-target.md',
|
||||
zedProfile
|
||||
);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(mockConsoleError).toHaveBeenCalledWith(
|
||||
'Error converting rule file: File not found'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle write errors gracefully', () => {
|
||||
mockReadFileSync.mockReturnValue('Test content');
|
||||
mockWriteFileSync.mockImplementation(() => {
|
||||
throw new Error('Write permission denied');
|
||||
});
|
||||
|
||||
const result = convertRuleToProfileRule(
|
||||
'test-source.mdc',
|
||||
'test-target.md',
|
||||
zedProfile
|
||||
);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(mockConsoleError).toHaveBeenCalledWith(
|
||||
'Error converting rule file: Write permission denied'
|
||||
);
|
||||
});
|
||||
|
||||
it('should verify profile configuration', () => {
|
||||
expect(zedProfile.profileName).toBe('zed');
|
||||
expect(zedProfile.displayName).toBe('Zed');
|
||||
expect(zedProfile.profileDir).toBe('.zed');
|
||||
expect(zedProfile.mcpConfig).toBe(true);
|
||||
expect(zedProfile.mcpConfigName).toBe('settings.json');
|
||||
expect(zedProfile.mcpConfigPath).toBe('.zed/settings.json');
|
||||
expect(zedProfile.includeDefaultRules).toBe(false);
|
||||
expect(zedProfile.fileMap).toEqual({
|
||||
'AGENTS.md': '.rules'
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -22,7 +22,8 @@ describe('Rule Transformer - General', () => {
|
||||
'roo',
|
||||
'trae',
|
||||
'vscode',
|
||||
'windsurf'
|
||||
'windsurf',
|
||||
'zed'
|
||||
];
|
||||
expectedProfiles.forEach((profile) => {
|
||||
expect(RULE_PROFILES).toContain(profile);
|
||||
@@ -229,6 +230,11 @@ describe('Rule Transformer - General', () => {
|
||||
mcpConfig: true,
|
||||
mcpConfigName: 'mcp.json',
|
||||
expectedPath: '.windsurf/mcp.json'
|
||||
},
|
||||
zed: {
|
||||
mcpConfig: true,
|
||||
mcpConfigName: 'settings.json',
|
||||
expectedPath: '.zed/settings.json'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
99
tests/unit/profiles/zed-integration.test.js
Normal file
99
tests/unit/profiles/zed-integration.test.js
Normal file
@@ -0,0 +1,99 @@
|
||||
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('Zed 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('settings.json')) {
|
||||
return JSON.stringify({ context_servers: {} }, null, 2);
|
||||
}
|
||||
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 Zed files
|
||||
function mockCreateZedStructure() {
|
||||
// Create main .zed directory
|
||||
fs.mkdirSync(path.join(tempDir, '.zed'), { recursive: true });
|
||||
|
||||
// Create MCP config file (settings.json)
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, '.zed', 'settings.json'),
|
||||
JSON.stringify({ context_servers: {} }, null, 2)
|
||||
);
|
||||
|
||||
// Create AGENTS.md in project root
|
||||
fs.writeFileSync(
|
||||
path.join(tempDir, 'AGENTS.md'),
|
||||
'# Task Master Instructions\n\nThis is the Task Master agents file.'
|
||||
);
|
||||
}
|
||||
|
||||
test('creates all required .zed directories', () => {
|
||||
// Act
|
||||
mockCreateZedStructure();
|
||||
|
||||
// Assert
|
||||
expect(fs.mkdirSync).toHaveBeenCalledWith(path.join(tempDir, '.zed'), {
|
||||
recursive: true
|
||||
});
|
||||
});
|
||||
|
||||
test('creates Zed settings.json with context_servers format', () => {
|
||||
// Act
|
||||
mockCreateZedStructure();
|
||||
|
||||
// Assert
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, '.zed', 'settings.json'),
|
||||
JSON.stringify({ context_servers: {} }, null, 2)
|
||||
);
|
||||
});
|
||||
|
||||
test('creates AGENTS.md in project root', () => {
|
||||
// Act
|
||||
mockCreateZedStructure();
|
||||
|
||||
// Assert
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
path.join(tempDir, 'AGENTS.md'),
|
||||
'# Task Master Instructions\n\nThis is the Task Master agents file.'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user