import { describe, it, expect, beforeEach, afterEach } from 'bun:test'; import { mkdirSync, writeFileSync, existsSync, rmSync } from 'fs'; import { join } from 'path'; import { tmpdir } from 'os'; import { writeContextFile, readContextFile } from '../src/utils/cursor-utils'; describe('Cursor Context Update', () => { let tempDir: string; let workspacePath: string; beforeEach(() => { tempDir = join(tmpdir(), `cursor-context-test-${Date.now()}-${Math.random().toString(36).slice(2)}`); workspacePath = join(tempDir, 'my-project'); mkdirSync(workspacePath, { recursive: true }); }); afterEach(() => { try { rmSync(tempDir, { recursive: true, force: true }); } catch { // Ignore cleanup errors } }); describe('writeContextFile', () => { it('creates .cursor/rules directory structure', () => { writeContextFile(workspacePath, 'test context'); const rulesDir = join(workspacePath, '.cursor', 'rules'); expect(existsSync(rulesDir)).toBe(true); }); it('creates claude-mem-context.mdc file', () => { writeContextFile(workspacePath, 'test context'); const rulesFile = join(workspacePath, '.cursor', 'rules', 'claude-mem-context.mdc'); expect(existsSync(rulesFile)).toBe(true); }); it('includes alwaysApply: true in frontmatter', () => { writeContextFile(workspacePath, 'test context'); const content = readContextFile(workspacePath); expect(content).toContain('alwaysApply: true'); }); it('includes description in frontmatter', () => { writeContextFile(workspacePath, 'test context'); const content = readContextFile(workspacePath); expect(content).toContain('description: "Claude-mem context from past sessions (auto-updated)"'); }); it('includes the provided context in the file body', () => { const testContext = `## Recent Session - Fixed authentication bug - Added new feature`; writeContextFile(workspacePath, testContext); const content = readContextFile(workspacePath); expect(content).toContain('Fixed authentication bug'); expect(content).toContain('Added new feature'); }); it('includes Memory Context header', () => { writeContextFile(workspacePath, 'test'); const content = readContextFile(workspacePath); expect(content).toContain('# Memory Context from Past Sessions'); }); it('includes footer with MCP tools mention', () => { writeContextFile(workspacePath, 'test'); const content = readContextFile(workspacePath); expect(content).toContain("Use claude-mem's MCP search tools for more detailed queries"); }); it('uses atomic write (no temp file left behind)', () => { writeContextFile(workspacePath, 'test context'); const tempFile = join(workspacePath, '.cursor', 'rules', 'claude-mem-context.mdc.tmp'); expect(existsSync(tempFile)).toBe(false); }); it('overwrites existing context file', () => { writeContextFile(workspacePath, 'first context'); writeContextFile(workspacePath, 'second context'); const content = readContextFile(workspacePath); expect(content).not.toContain('first context'); expect(content).toContain('second context'); }); it('handles empty context gracefully', () => { writeContextFile(workspacePath, ''); const content = readContextFile(workspacePath); expect(content).toBeDefined(); expect(content).toContain('alwaysApply: true'); }); it('preserves multi-line context with proper formatting', () => { const multilineContext = `Line 1 Line 2 Line 3 Paragraph 2`; writeContextFile(workspacePath, multilineContext); const content = readContextFile(workspacePath); expect(content).toContain('Line 1\nLine 2\nLine 3'); expect(content).toContain('Paragraph 2'); }); }); describe('MDC format validation', () => { it('has valid YAML frontmatter delimiters', () => { writeContextFile(workspacePath, 'test'); const content = readContextFile(workspacePath)!; const lines = content.split('\n'); expect(lines[0]).toBe('---'); const secondDashIndex = lines.indexOf('---', 1); expect(secondDashIndex).toBeGreaterThan(0); }); it('frontmatter is parseable as YAML', () => { writeContextFile(workspacePath, 'test'); const content = readContextFile(workspacePath)!; const lines = content.split('\n'); const frontmatterEnd = lines.indexOf('---', 1); const frontmatter = lines.slice(1, frontmatterEnd).join('\n'); expect(frontmatter).toMatch(/alwaysApply:\s*true/); expect(frontmatter).toMatch(/description:\s*"/); }); it('content after frontmatter is proper markdown', () => { writeContextFile(workspacePath, 'test'); const content = readContextFile(workspacePath)!; expect(content).toMatch(/^# Memory Context/m); const bodyPart = content.split('---')[2]; expect(bodyPart).toBeDefined(); }); }); describe('edge cases', () => { it('handles special characters in context', () => { const specialContext = '`code` **bold** _italic_ $variable @mention #tag'; writeContextFile(workspacePath, specialContext); const content = readContextFile(workspacePath); expect(content).toContain('`code`'); expect(content).toContain('**bold**'); expect(content).toContain(''); }); it('handles unicode in context', () => { const unicodeContext = 'Emoji: 🚀 Japanese: 日本語 Arabic: العربية'; writeContextFile(workspacePath, unicodeContext); const content = readContextFile(workspacePath); expect(content).toContain('🚀'); expect(content).toContain('日本語'); expect(content).toContain('العربية'); }); it('handles very long context', () => { const longContext = 'x'.repeat(100 * 1024); writeContextFile(workspacePath, longContext); const content = readContextFile(workspacePath); expect(content).toContain(longContext); }); it('works when .cursor directory already exists', () => { mkdirSync(join(workspacePath, '.cursor', 'other'), { recursive: true }); writeFileSync(join(workspacePath, '.cursor', 'other', 'file.txt'), 'existing'); writeContextFile(workspacePath, 'new context'); expect(existsSync(join(workspacePath, '.cursor', 'other', 'file.txt'))).toBe(true); expect(readContextFile(workspacePath)).toContain('new context'); }); }); });