* Fix Cursor deeplink installation with copy-paste instructions (#723) * detect git worktree * add changeset * add aliases and git flags * add changeset * rename and update test * add store tasks in git functionality * update changeset * fix newline * remove unused import * update command wording * update command option text
582 lines
15 KiB
JavaScript
582 lines
15 KiB
JavaScript
/**
|
|
* Integration tests for manage-gitignore.js module
|
|
* Tests actual file system operations in a temporary directory
|
|
*/
|
|
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import os from 'os';
|
|
import manageGitignoreFile from '../../src/utils/manage-gitignore.js';
|
|
|
|
describe('manage-gitignore.js Integration Tests', () => {
|
|
let tempDir;
|
|
let testGitignorePath;
|
|
|
|
beforeEach(() => {
|
|
// Create a temporary directory for each test
|
|
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitignore-test-'));
|
|
testGitignorePath = path.join(tempDir, '.gitignore');
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Clean up temporary directory after each test
|
|
if (fs.existsSync(tempDir)) {
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
describe('New File Creation', () => {
|
|
const templateContent = `# Logs
|
|
logs
|
|
*.log
|
|
npm-debug.log*
|
|
|
|
# Dependencies
|
|
node_modules/
|
|
jspm_packages/
|
|
|
|
# Environment variables
|
|
.env
|
|
.env.local
|
|
|
|
# Task files
|
|
tasks.json
|
|
tasks/ `;
|
|
|
|
test('should create new .gitignore file with commented task lines (storeTasksInGit = true)', () => {
|
|
const logs = [];
|
|
const mockLog = (level, message) => logs.push({ level, message });
|
|
|
|
manageGitignoreFile(testGitignorePath, templateContent, true, mockLog);
|
|
|
|
// Verify file was created
|
|
expect(fs.existsSync(testGitignorePath)).toBe(true);
|
|
|
|
// Verify content
|
|
const content = fs.readFileSync(testGitignorePath, 'utf8');
|
|
expect(content).toContain('# Logs');
|
|
expect(content).toContain('logs');
|
|
expect(content).toContain('# Dependencies');
|
|
expect(content).toContain('node_modules/');
|
|
expect(content).toContain('# Task files');
|
|
expect(content).toContain('tasks.json');
|
|
expect(content).toContain('tasks/');
|
|
|
|
// Verify task lines are commented (storeTasksInGit = true)
|
|
expect(content).toMatch(
|
|
/# Task files\s*[\r\n]+# tasks\.json\s*[\r\n]+# tasks\/ /
|
|
);
|
|
|
|
// Verify log message
|
|
expect(logs).toContainEqual({
|
|
level: 'success',
|
|
message: expect.stringContaining('Created')
|
|
});
|
|
});
|
|
|
|
test('should create new .gitignore file with uncommented task lines (storeTasksInGit = false)', () => {
|
|
const logs = [];
|
|
const mockLog = (level, message) => logs.push({ level, message });
|
|
|
|
manageGitignoreFile(testGitignorePath, templateContent, false, mockLog);
|
|
|
|
// Verify file was created
|
|
expect(fs.existsSync(testGitignorePath)).toBe(true);
|
|
|
|
// Verify content
|
|
const content = fs.readFileSync(testGitignorePath, 'utf8');
|
|
expect(content).toContain('# Task files');
|
|
|
|
// Verify task lines are uncommented (storeTasksInGit = false)
|
|
expect(content).toMatch(
|
|
/# Task files\s*[\r\n]+tasks\.json\s*[\r\n]+tasks\/ /
|
|
);
|
|
|
|
// Verify log message
|
|
expect(logs).toContainEqual({
|
|
level: 'success',
|
|
message: expect.stringContaining('Created')
|
|
});
|
|
});
|
|
|
|
test('should work without log function', () => {
|
|
expect(() => {
|
|
manageGitignoreFile(testGitignorePath, templateContent, false);
|
|
}).not.toThrow();
|
|
|
|
expect(fs.existsSync(testGitignorePath)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('File Merging', () => {
|
|
const templateContent = `# Logs
|
|
logs
|
|
*.log
|
|
|
|
# Dependencies
|
|
node_modules/
|
|
|
|
# Environment variables
|
|
.env
|
|
|
|
# Task files
|
|
tasks.json
|
|
tasks/ `;
|
|
|
|
test('should merge template with existing file content', () => {
|
|
// Create existing .gitignore file
|
|
const existingContent = `# Existing content
|
|
old-files.txt
|
|
*.backup
|
|
|
|
# Old task files (to be replaced)
|
|
# Task files
|
|
# tasks.json
|
|
# tasks/
|
|
|
|
# More existing content
|
|
cache/`;
|
|
|
|
fs.writeFileSync(testGitignorePath, existingContent);
|
|
|
|
const logs = [];
|
|
const mockLog = (level, message) => logs.push({ level, message });
|
|
|
|
manageGitignoreFile(testGitignorePath, templateContent, false, mockLog);
|
|
|
|
// Verify file still exists
|
|
expect(fs.existsSync(testGitignorePath)).toBe(true);
|
|
|
|
const content = fs.readFileSync(testGitignorePath, 'utf8');
|
|
|
|
// Should retain existing non-task content
|
|
expect(content).toContain('# Existing content');
|
|
expect(content).toContain('old-files.txt');
|
|
expect(content).toContain('*.backup');
|
|
expect(content).toContain('# More existing content');
|
|
expect(content).toContain('cache/');
|
|
|
|
// Should add new template content
|
|
expect(content).toContain('# Logs');
|
|
expect(content).toContain('logs');
|
|
expect(content).toContain('# Dependencies');
|
|
expect(content).toContain('node_modules/');
|
|
expect(content).toContain('# Environment variables');
|
|
expect(content).toContain('.env');
|
|
|
|
// Should replace task section with new preference (storeTasksInGit = false means uncommented)
|
|
expect(content).toMatch(
|
|
/# Task files\s*[\r\n]+tasks\.json\s*[\r\n]+tasks\/ /
|
|
);
|
|
|
|
// Verify log message
|
|
expect(logs).toContainEqual({
|
|
level: 'success',
|
|
message: expect.stringContaining('Updated')
|
|
});
|
|
});
|
|
|
|
test('should handle switching task preferences from commented to uncommented', () => {
|
|
// Create existing file with commented task lines
|
|
const existingContent = `# Existing
|
|
existing.txt
|
|
|
|
# Task files
|
|
# tasks.json
|
|
# tasks/ `;
|
|
|
|
fs.writeFileSync(testGitignorePath, existingContent);
|
|
|
|
// Update with storeTasksInGit = true (commented)
|
|
manageGitignoreFile(testGitignorePath, templateContent, true);
|
|
|
|
const content = fs.readFileSync(testGitignorePath, 'utf8');
|
|
|
|
// Should retain existing content
|
|
expect(content).toContain('# Existing');
|
|
expect(content).toContain('existing.txt');
|
|
|
|
// Should have commented task lines (storeTasksInGit = true)
|
|
expect(content).toMatch(
|
|
/# Task files\s*[\r\n]+# tasks\.json\s*[\r\n]+# tasks\/ /
|
|
);
|
|
});
|
|
|
|
test('should handle switching task preferences from uncommented to commented', () => {
|
|
// Create existing file with uncommented task lines
|
|
const existingContent = `# Existing
|
|
existing.txt
|
|
|
|
# Task files
|
|
tasks.json
|
|
tasks/ `;
|
|
|
|
fs.writeFileSync(testGitignorePath, existingContent);
|
|
|
|
// Update with storeTasksInGit = false (uncommented)
|
|
manageGitignoreFile(testGitignorePath, templateContent, false);
|
|
|
|
const content = fs.readFileSync(testGitignorePath, 'utf8');
|
|
|
|
// Should retain existing content
|
|
expect(content).toContain('# Existing');
|
|
expect(content).toContain('existing.txt');
|
|
|
|
// Should have uncommented task lines (storeTasksInGit = false)
|
|
expect(content).toMatch(
|
|
/# Task files\s*[\r\n]+tasks\.json\s*[\r\n]+tasks\/ /
|
|
);
|
|
});
|
|
|
|
test('should not duplicate existing template content', () => {
|
|
// Create existing file that already has some template content
|
|
const existingContent = `# Logs
|
|
logs
|
|
*.log
|
|
|
|
# Dependencies
|
|
node_modules/
|
|
|
|
# Custom content
|
|
custom.txt
|
|
|
|
# Task files
|
|
# tasks.json
|
|
# tasks/ `;
|
|
|
|
fs.writeFileSync(testGitignorePath, existingContent);
|
|
|
|
manageGitignoreFile(testGitignorePath, templateContent, false);
|
|
|
|
const content = fs.readFileSync(testGitignorePath, 'utf8');
|
|
|
|
// Should not duplicate logs section
|
|
const logsMatches = content.match(/# Logs/g);
|
|
expect(logsMatches).toHaveLength(1);
|
|
|
|
// Should not duplicate dependencies section
|
|
const depsMatches = content.match(/# Dependencies/g);
|
|
expect(depsMatches).toHaveLength(1);
|
|
|
|
// Should retain custom content
|
|
expect(content).toContain('# Custom content');
|
|
expect(content).toContain('custom.txt');
|
|
|
|
// Should add new template content that wasn't present
|
|
expect(content).toContain('# Environment variables');
|
|
expect(content).toContain('.env');
|
|
});
|
|
|
|
test('should handle empty existing file', () => {
|
|
// Create empty file
|
|
fs.writeFileSync(testGitignorePath, '');
|
|
|
|
manageGitignoreFile(testGitignorePath, templateContent, false);
|
|
|
|
expect(fs.existsSync(testGitignorePath)).toBe(true);
|
|
|
|
const content = fs.readFileSync(testGitignorePath, 'utf8');
|
|
expect(content).toContain('# Logs');
|
|
expect(content).toContain('# Task files');
|
|
expect(content).toMatch(
|
|
/# Task files\s*[\r\n]+tasks\.json\s*[\r\n]+tasks\/ /
|
|
);
|
|
});
|
|
|
|
test('should handle file with only whitespace', () => {
|
|
// Create file with only whitespace
|
|
fs.writeFileSync(testGitignorePath, ' \n\n \n');
|
|
|
|
manageGitignoreFile(testGitignorePath, templateContent, true);
|
|
|
|
const content = fs.readFileSync(testGitignorePath, 'utf8');
|
|
expect(content).toContain('# Logs');
|
|
expect(content).toContain('# Task files');
|
|
expect(content).toMatch(
|
|
/# Task files\s*[\r\n]+# tasks\.json\s*[\r\n]+# tasks\/ /
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Complex Task Section Handling', () => {
|
|
test('should remove task section with mixed comments and spacing', () => {
|
|
const existingContent = `# Dependencies
|
|
node_modules/
|
|
|
|
# Task files
|
|
|
|
# tasks.json
|
|
tasks/
|
|
|
|
|
|
# More content
|
|
more.txt`;
|
|
|
|
const templateContent = `# New content
|
|
new.txt
|
|
|
|
# Task files
|
|
tasks.json
|
|
tasks/ `;
|
|
|
|
fs.writeFileSync(testGitignorePath, existingContent);
|
|
|
|
manageGitignoreFile(testGitignorePath, templateContent, false);
|
|
|
|
const content = fs.readFileSync(testGitignorePath, 'utf8');
|
|
|
|
// Should retain non-task content
|
|
expect(content).toContain('# Dependencies');
|
|
expect(content).toContain('node_modules/');
|
|
expect(content).toContain('# More content');
|
|
expect(content).toContain('more.txt');
|
|
|
|
// Should add new content
|
|
expect(content).toContain('# New content');
|
|
expect(content).toContain('new.txt');
|
|
|
|
// Should have clean task section (storeTasksInGit = false means uncommented)
|
|
expect(content).toMatch(
|
|
/# Task files\s*[\r\n]+tasks\.json\s*[\r\n]+tasks\/ /
|
|
);
|
|
});
|
|
|
|
test('should handle multiple task file variations', () => {
|
|
const existingContent = `# Existing
|
|
existing.txt
|
|
|
|
# Task files
|
|
tasks.json
|
|
# tasks.json
|
|
# tasks/
|
|
tasks/
|
|
#tasks.json
|
|
|
|
# More content
|
|
more.txt`;
|
|
|
|
const templateContent = `# Task files
|
|
tasks.json
|
|
tasks/ `;
|
|
|
|
fs.writeFileSync(testGitignorePath, existingContent);
|
|
|
|
manageGitignoreFile(testGitignorePath, templateContent, true);
|
|
|
|
const content = fs.readFileSync(testGitignorePath, 'utf8');
|
|
|
|
// Should retain non-task content
|
|
expect(content).toContain('# Existing');
|
|
expect(content).toContain('existing.txt');
|
|
expect(content).toContain('# More content');
|
|
expect(content).toContain('more.txt');
|
|
|
|
// Should have clean task section with preference applied (storeTasksInGit = true means commented)
|
|
expect(content).toMatch(
|
|
/# Task files\s*[\r\n]+# tasks\.json\s*[\r\n]+# tasks\/ /
|
|
);
|
|
|
|
// Should not have multiple task sections
|
|
const taskFileMatches = content.match(/# Task files/g);
|
|
expect(taskFileMatches).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe('Error Handling', () => {
|
|
test('should handle permission errors gracefully', () => {
|
|
// Create a directory where we would create the file, then remove write permissions
|
|
const readOnlyDir = path.join(tempDir, 'readonly');
|
|
fs.mkdirSync(readOnlyDir);
|
|
fs.chmodSync(readOnlyDir, 0o444); // Read-only
|
|
|
|
const readOnlyGitignorePath = path.join(readOnlyDir, '.gitignore');
|
|
const templateContent = `# Test
|
|
test.txt
|
|
|
|
# Task files
|
|
tasks.json
|
|
tasks/ `;
|
|
|
|
const logs = [];
|
|
const mockLog = (level, message) => logs.push({ level, message });
|
|
|
|
expect(() => {
|
|
manageGitignoreFile(
|
|
readOnlyGitignorePath,
|
|
templateContent,
|
|
false,
|
|
mockLog
|
|
);
|
|
}).toThrow();
|
|
|
|
// Verify error was logged
|
|
expect(logs).toContainEqual({
|
|
level: 'error',
|
|
message: expect.stringContaining('Failed to create')
|
|
});
|
|
|
|
// Restore permissions for cleanup
|
|
fs.chmodSync(readOnlyDir, 0o755);
|
|
});
|
|
|
|
test('should handle read errors on existing files', () => {
|
|
// Create a file then remove read permissions
|
|
fs.writeFileSync(testGitignorePath, 'existing content');
|
|
fs.chmodSync(testGitignorePath, 0o000); // No permissions
|
|
|
|
const templateContent = `# Test
|
|
test.txt
|
|
|
|
# Task files
|
|
tasks.json
|
|
tasks/ `;
|
|
|
|
const logs = [];
|
|
const mockLog = (level, message) => logs.push({ level, message });
|
|
|
|
expect(() => {
|
|
manageGitignoreFile(testGitignorePath, templateContent, false, mockLog);
|
|
}).toThrow();
|
|
|
|
// Verify error was logged
|
|
expect(logs).toContainEqual({
|
|
level: 'error',
|
|
message: expect.stringContaining('Failed to merge content')
|
|
});
|
|
|
|
// Restore permissions for cleanup
|
|
fs.chmodSync(testGitignorePath, 0o644);
|
|
});
|
|
});
|
|
|
|
describe('Real-world Scenarios', () => {
|
|
test('should handle typical Node.js project .gitignore', () => {
|
|
const existingNodeGitignore = `# Logs
|
|
logs
|
|
*.log
|
|
npm-debug.log*
|
|
yarn-debug.log*
|
|
yarn-error.log*
|
|
|
|
# Runtime data
|
|
pids
|
|
*.pid
|
|
*.seed
|
|
*.pid.lock
|
|
|
|
# Dependency directories
|
|
node_modules/
|
|
jspm_packages/
|
|
|
|
# Optional npm cache directory
|
|
.npm
|
|
|
|
# Output of 'npm pack'
|
|
*.tgz
|
|
|
|
# Yarn Integrity file
|
|
.yarn-integrity
|
|
|
|
# dotenv environment variables file
|
|
.env
|
|
|
|
# next.js build output
|
|
.next`;
|
|
|
|
const taskMasterTemplate = `# Logs
|
|
logs
|
|
*.log
|
|
|
|
# Dependencies
|
|
node_modules/
|
|
|
|
# Environment variables
|
|
.env
|
|
|
|
# Build output
|
|
dist/
|
|
build/
|
|
|
|
# Task files
|
|
tasks.json
|
|
tasks/ `;
|
|
|
|
fs.writeFileSync(testGitignorePath, existingNodeGitignore);
|
|
|
|
manageGitignoreFile(testGitignorePath, taskMasterTemplate, false);
|
|
|
|
const content = fs.readFileSync(testGitignorePath, 'utf8');
|
|
|
|
// Should retain existing Node.js specific entries
|
|
expect(content).toContain('npm-debug.log*');
|
|
expect(content).toContain('yarn-debug.log*');
|
|
expect(content).toContain('*.pid');
|
|
expect(content).toContain('jspm_packages/');
|
|
expect(content).toContain('.npm');
|
|
expect(content).toContain('*.tgz');
|
|
expect(content).toContain('.yarn-integrity');
|
|
expect(content).toContain('.next');
|
|
|
|
// Should add new content from template that wasn't present
|
|
expect(content).toContain('dist/');
|
|
expect(content).toContain('build/');
|
|
|
|
// Should add task files section with correct preference (storeTasksInGit = false means uncommented)
|
|
expect(content).toMatch(
|
|
/# Task files\s*[\r\n]+tasks\.json\s*[\r\n]+tasks\/ /
|
|
);
|
|
|
|
// Should not duplicate common entries
|
|
const nodeModulesMatches = content.match(/node_modules\//g);
|
|
expect(nodeModulesMatches).toHaveLength(1);
|
|
|
|
const logsMatches = content.match(/# Logs/g);
|
|
expect(logsMatches).toHaveLength(1);
|
|
});
|
|
|
|
test('should handle project with existing task files in git', () => {
|
|
const existingContent = `# Dependencies
|
|
node_modules/
|
|
|
|
# Logs
|
|
*.log
|
|
|
|
# Current task setup - keeping in git
|
|
# Task files
|
|
tasks.json
|
|
tasks/
|
|
|
|
# Build output
|
|
dist/`;
|
|
|
|
const templateContent = `# New template
|
|
# Dependencies
|
|
node_modules/
|
|
|
|
# Task files
|
|
tasks.json
|
|
tasks/ `;
|
|
|
|
fs.writeFileSync(testGitignorePath, existingContent);
|
|
|
|
// Change preference to exclude tasks from git (storeTasksInGit = false means uncommented/ignored)
|
|
manageGitignoreFile(testGitignorePath, templateContent, false);
|
|
|
|
const content = fs.readFileSync(testGitignorePath, 'utf8');
|
|
|
|
// Should retain existing content
|
|
expect(content).toContain('# Dependencies');
|
|
expect(content).toContain('node_modules/');
|
|
expect(content).toContain('# Logs');
|
|
expect(content).toContain('*.log');
|
|
expect(content).toContain('# Build output');
|
|
expect(content).toContain('dist/');
|
|
|
|
// Should update task preference to uncommented (storeTasksInGit = false)
|
|
expect(content).toMatch(
|
|
/# Task files\s*[\r\n]+tasks\.json\s*[\r\n]+tasks\/ /
|
|
);
|
|
});
|
|
});
|
|
});
|