Add snapshot tests for system prompts to ensure modifications are intentional

This commit is contained in:
Matt Rubens
2025-01-09 22:51:29 -05:00
parent bcebba8dd8
commit f5dfa8a486
2 changed files with 2236 additions and 91 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,112 +1,203 @@
import { SYSTEM_PROMPT, addCustomInstructions } from '../system'
import { McpHub } from '../../../services/mcp/McpHub'
import { McpServer } from '../../../shared/mcp'
import { ClineProvider } from '../../../core/webview/ClineProvider'
import { SearchReplaceDiffStrategy } from '../../../core/diff/strategies/search-replace'
import fs from 'fs/promises' import fs from 'fs/promises'
import path from 'path'
import os from 'os' import os from 'os'
import { addCustomInstructions } from '../system' // Import path utils to get access to toPosix string extension
import '../../../utils/path'
// Mock external dependencies // Mock environment-specific values for consistent tests
jest.mock('os-name', () => () => 'macOS')
jest.mock('default-shell', () => '/bin/zsh')
jest.mock('os', () => ({ jest.mock('os', () => ({
homedir: () => '/Users/test', ...jest.requireActual('os'),
...jest.requireActual('os') homedir: () => '/home/user'
})) }))
describe('system.ts', () => { jest.mock('default-shell', () => '/bin/bash')
let tempDir: string
beforeEach(async () => { jest.mock('os-name', () => () => 'Linux')
// Create a temporary directory for test files
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'cline-test-')) // Mock fs.readFile to return empty mcpServers config and mock .clinerules
jest.mock('fs/promises', () => ({
...jest.requireActual('fs/promises'),
readFile: jest.fn().mockImplementation(async (path: string) => {
if (path.endsWith('mcpSettings.json')) {
return '{"mcpServers": {}}'
}
if (path.endsWith('.clinerules')) {
return '# Test Rules\n1. First rule\n2. Second rule'
}
return ''
}),
writeFile: jest.fn().mockResolvedValue(undefined)
}))
// Create a minimal mock of ClineProvider
const mockProvider = {
ensureMcpServersDirectoryExists: async () => '/mock/mcp/path',
ensureSettingsDirectoryExists: async () => '/mock/settings/path',
postMessageToWebview: async () => {},
context: {
extension: {
packageJSON: {
version: '1.0.0'
}
}
}
} as unknown as ClineProvider
// Instead of extending McpHub, create a mock that implements just what we need
const createMockMcpHub = (): McpHub => ({
getServers: () => [],
getMcpServersPath: async () => '/mock/mcp/path',
getMcpSettingsFilePath: async () => '/mock/settings/path',
dispose: async () => {},
// Add other required public methods with no-op implementations
restartConnection: async () => {},
readResource: async () => ({ contents: [] }),
callTool: async () => ({ content: [] }),
toggleServerDisabled: async () => {},
toggleToolAlwaysAllow: async () => {},
isConnecting: false,
connections: []
} as unknown as McpHub)
describe('SYSTEM_PROMPT', () => {
let mockMcpHub: McpHub
beforeEach(() => {
jest.clearAllMocks()
}) })
afterEach(async () => { afterEach(async () => {
// Clean up temporary directory after each test // Clean up any McpHub instances
await fs.rm(tempDir, { recursive: true, force: true }) if (mockMcpHub) {
await mockMcpHub.dispose()
}
}) })
describe('addCustomInstructions', () => { it('should maintain consistent system prompt', async () => {
it('should include content from .clinerules and .cursorrules if present', async () => { const prompt = await SYSTEM_PROMPT(
// Create test rule files '/test/path',
await fs.writeFile(path.join(tempDir, '.clinerules'), 'Always write tests\nUse TypeScript') false, // supportsComputerUse
await fs.writeFile(path.join(tempDir, '.cursorrules'), 'Format code before committing') undefined, // mcpHub
undefined, // diffStrategy
undefined // browserViewportSize
)
const customInstructions = 'Base instructions' expect(prompt).toMatchSnapshot()
const result = await addCustomInstructions(customInstructions, tempDir) })
// Verify all instructions are included it('should include browser actions when supportsComputerUse is true', async () => {
expect(result).toContain('Base instructions') const prompt = await SYSTEM_PROMPT(
expect(result).toContain('Always write tests') '/test/path',
expect(result).toContain('Use TypeScript') true,
expect(result).toContain('Format code before committing') undefined,
expect(result).toContain('Rules from .clinerules:') undefined,
expect(result).toContain('Rules from .cursorrules:') '1280x800'
}) )
it('should handle missing rule files gracefully', async () => { expect(prompt).toMatchSnapshot()
const customInstructions = 'Base instructions' })
const result = await addCustomInstructions(customInstructions, tempDir)
// Should only contain base instructions it('should include MCP server info when mcpHub is provided', async () => {
expect(result).toContain('Base instructions') mockMcpHub = createMockMcpHub()
expect(result).not.toContain('Rules from')
})
it('should handle empty rule files', async () => { const prompt = await SYSTEM_PROMPT(
// Create empty rule files '/test/path',
await fs.writeFile(path.join(tempDir, '.clinerules'), '') false,
await fs.writeFile(path.join(tempDir, '.cursorrules'), '') mockMcpHub
)
const customInstructions = 'Base instructions' expect(prompt).toMatchSnapshot()
const result = await addCustomInstructions(customInstructions, tempDir) })
// Should only contain base instructions it('should explicitly handle undefined mcpHub', async () => {
expect(result).toContain('Base instructions') const prompt = await SYSTEM_PROMPT(
expect(result).not.toContain('Rules from') '/test/path',
}) false,
undefined, // explicitly undefined mcpHub
undefined,
undefined
)
it('should handle whitespace-only rule files', async () => { expect(prompt).toMatchSnapshot()
// Create rule files with only whitespace })
await fs.writeFile(path.join(tempDir, '.clinerules'), ' \n \t ')
await fs.writeFile(path.join(tempDir, '.cursorrules'), ' \n ')
const customInstructions = 'Base instructions' it('should handle different browser viewport sizes', async () => {
const result = await addCustomInstructions(customInstructions, tempDir) const prompt = await SYSTEM_PROMPT(
'/test/path',
true,
undefined,
undefined,
'900x600' // different viewport size
)
// Should only contain base instructions expect(prompt).toMatchSnapshot()
expect(result).toContain('Base instructions') })
expect(result).not.toContain('Rules from')
})
it('should handle one rule file present and one missing', async () => { it('should include diff strategy tool description', async () => {
// Create only .clinerules const prompt = await SYSTEM_PROMPT(
await fs.writeFile(path.join(tempDir, '.clinerules'), 'Always write tests') '/test/path',
false,
undefined,
new SearchReplaceDiffStrategy(), // Use actual diff strategy from the codebase
undefined
)
const customInstructions = 'Base instructions' expect(prompt).toMatchSnapshot()
const result = await addCustomInstructions(customInstructions, tempDir) })
// Should contain base instructions and .clinerules content afterAll(() => {
expect(result).toContain('Base instructions') jest.restoreAllMocks()
expect(result).toContain('Always write tests') })
expect(result).toContain('Rules from .clinerules:') })
expect(result).not.toContain('Rules from .cursorrules:')
}) describe('addCustomInstructions', () => {
beforeEach(() => {
it('should handle empty custom instructions with rule files', async () => { jest.clearAllMocks()
await fs.writeFile(path.join(tempDir, '.clinerules'), 'Always write tests') })
await fs.writeFile(path.join(tempDir, '.cursorrules'), 'Format code before committing')
it('should include preferred language when provided', async () => {
const result = await addCustomInstructions('', tempDir) const result = await addCustomInstructions(
'',
// Should contain rule file content even with empty custom instructions '/test/path',
expect(result).toContain('Always write tests') 'Spanish'
expect(result).toContain('Format code before committing') )
expect(result).toContain('Rules from .clinerules:')
expect(result).toContain('Rules from .cursorrules:') expect(result).toMatchSnapshot()
}) })
it('should return empty string when no instructions or rules exist', async () => { it('should include custom instructions when provided', async () => {
const result = await addCustomInstructions('', tempDir) const result = await addCustomInstructions(
expect(result).toBe('') 'Custom test instructions',
}) '/test/path'
)
expect(result).toMatchSnapshot()
})
it('should include rules from .clinerules', async () => {
const result = await addCustomInstructions(
'',
'/test/path'
)
expect(result).toMatchSnapshot()
})
it('should combine all custom instructions', async () => {
const result = await addCustomInstructions(
'Custom test instructions',
'/test/path',
'French'
)
expect(result).toMatchSnapshot()
})
afterAll(() => {
jest.restoreAllMocks()
}) })
}) })