Spaces:
Paused
Paused
| import { jest, expect, describe, it } from '@jest/globals' | |
| import { | |
| splitPatch, | |
| patchStartEndLine, | |
| parsePatch, | |
| checkIfDocumentationOnly, | |
| parseReview, | |
| extractPatternType, | |
| HunkInfo, | |
| } from '../utils/patch-utils' | |
| describe('patch-utils', () => { | |
| describe('splitPatch', () => { | |
| it('should split patch with proper @@ headers into hunks', () => { | |
| const patch = `@@ -10,5 +10,6 @@ | |
| line1 | |
| line2 | |
| -old line | |
| +new line | |
| +added line | |
| @@ -20,3 +20,4 @@ | |
| context | |
| -modified | |
| +added | |
| another` | |
| const result = splitPatch(patch) | |
| expect(result).toHaveLength(2) | |
| expect(result[0]).toContain('line1') | |
| expect(result[1]).toContain('context') | |
| }) | |
| it('should return entire patch when no @@ headers found (new files)', () => { | |
| const newFilePatch = `+function updateSettings(user, newTheme) { | |
| + const updatedUser = user; | |
| + updatedUser.settings.theme = newTheme; | |
| + return updatedUser; | |
| +}` | |
| const result = splitPatch(newFilePatch) | |
| expect(result).toHaveLength(1) | |
| expect(result[0]).toBe(newFilePatch) | |
| }) | |
| it('should return empty array for null/undefined patch', () => { | |
| expect(splitPatch(null)).toEqual([]) | |
| expect(splitPatch(undefined)).toEqual([]) | |
| }) | |
| it('should handle single hunk patch', () => { | |
| const patch = `@@ -1,3 +1,4 @@ | |
| const x = 1; | |
| -old | |
| +new | |
| +added` | |
| const result = splitPatch(patch) | |
| expect(result).toHaveLength(1) | |
| }) | |
| it('should handle patch with only context lines', () => { | |
| const patch = `@@ -5,3 +5,3 @@ | |
| return result; | |
| } | |
| -remove | |
| +add` | |
| const result = splitPatch(patch) | |
| expect(result).toHaveLength(1) | |
| }) | |
| }) | |
| describe('patchStartEndLine', () => { | |
| it('should parse standard @@ header correctly', () => { | |
| const patch = `@@ -10,5 +10,6 @@ | |
| content` | |
| const result = patchStartEndLine(patch) | |
| expect(result).toEqual({ | |
| oldStart: 10, | |
| oldLength: 5, | |
| newStart: 10, | |
| newLength: 6, | |
| oldHunk: { startLine: 10, endLine: 14 }, | |
| newHunk: { startLine: 10, endLine: 15 }, | |
| }) | |
| }) | |
| it('should handle new file without @@ header (all + lines)', () => { | |
| const newFilePatch = `+function test() { | |
| + return true; | |
| +}` | |
| const result = patchStartEndLine(newFilePatch) | |
| expect(result).not.toBeNull() | |
| expect(result?.newStart).toBe(1) | |
| expect(result?.newLength).toBe(3) | |
| expect(result?.oldStart).toBe(0) | |
| expect(result?.oldLength).toBe(0) | |
| expect(result?.oldHunk.startLine).toBe(0) | |
| expect(result?.oldHunk.endLine).toBe(0) | |
| expect(result?.newHunk.startLine).toBe(1) | |
| expect(result?.newHunk.endLine).toBe(3) | |
| }) | |
| it('should handle single line new file', () => { | |
| const singleLinePatch = `+const x = 1;` | |
| const result = patchStartEndLine(singleLinePatch) | |
| expect(result?.newLength).toBe(1) | |
| expect(result?.newHunk.endLine).toBe(1) | |
| }) | |
| it('should return null for patch with no + lines and no header', () => { | |
| const invalidPatch = ` context line | |
| another line` | |
| const result = patchStartEndLine(invalidPatch) | |
| expect(result).toBeNull() | |
| }) | |
| it('should handle diff with whitespace before +', () => { | |
| const patch = `@@ -1,2 +1,3 @@ | |
| line1 | |
| +added line` | |
| const result = patchStartEndLine(patch) | |
| expect(result?.newLength).toBe(3) | |
| expect(result?.oldLength).toBe(2) | |
| }) | |
| }) | |
| describe('parsePatch', () => { | |
| it('should parse standard diff correctly', () => { | |
| const patch = `@@ -1,4 +1,5 @@ | |
| const x = 1; | |
| -old line | |
| +new line | |
| +added line | |
| context line` | |
| const result = parsePatch(patch) | |
| expect(result).not.toBeNull() | |
| expect(result?.oldStart).toBe(1) | |
| expect(result?.newStart).toBe(1) | |
| expect(result?.oldHunk).toContain('old line') | |
| expect(result?.newHunk).toContain('new line') | |
| expect(result?.newHunk).toContain('added line') | |
| }) | |
| it('should handle new file (no old lines)', () => { | |
| const newFilePatch = `+function test() { | |
| + return 1; | |
| +}` | |
| const result = parsePatch(newFilePatch) | |
| expect(result).not.toBeNull() | |
| expect(result?.oldHunk).toBe('') | |
| expect(result?.newHunk).toContain('function test()') | |
| expect(result?.newStart).toBe(1) | |
| }) | |
| it('should handle deleted file (no new lines)', () => { | |
| const deletedPatch = `@@ -1,3 +1,0 @@ | |
| -old line | |
| -old line 2 | |
| -old line 3` | |
| const result = parsePatch(deletedPatch) | |
| expect(result).not.toBeNull() | |
| expect(result?.oldHunk).toContain('old line') | |
| expect(result?.newHunk).toBe('') | |
| }) | |
| it('should return null for invalid patch without header and no + lines', () => { | |
| const invalidPatch = `just some text without diff markers` | |
| const result = parsePatch(invalidPatch) | |
| expect(result).toBeNull() | |
| }) | |
| }) | |
| describe('checkIfDocumentationOnly', () => { | |
| it('should return true for pure comment marker changes (no text content)', () => { | |
| const docOnlyDiff = `-// | |
| +////` | |
| expect(checkIfDocumentationOnly(docOnlyDiff)).toBe(true) | |
| }) | |
| it('should return false for comment text changes', () => { | |
| const codeDiff = `-// Old comment | |
| +// New comment` | |
| expect(checkIfDocumentationOnly(codeDiff)).toBe(false) | |
| }) | |
| it('should return false for code changes', () => { | |
| const codeDiff = `-const x = 1; | |
| +const x = 2;` | |
| expect(checkIfDocumentationOnly(codeDiff)).toBe(false) | |
| }) | |
| it('should return false for Python docstrings', () => { | |
| const pythonDocDiff = `-"""Old doc""" | |
| +"""New doc"""` | |
| expect(checkIfDocumentationOnly(pythonDocDiff)).toBe(false) | |
| }) | |
| }) | |
| describe('parseReview', () => { | |
| it('should parse review with line numbers', () => { | |
| const response = `[10] | major | bug: Missing null check | |
| Null pointer risk here` | |
| const patches = [ | |
| { | |
| newStart: 1, | |
| newLength: 20, | |
| }, | |
| ] | |
| const result = parseReview(response, patches, 'test.js') | |
| expect(result).toHaveLength(1) | |
| expect(result[0].startLine).toBe(10) | |
| expect(result[0].endLine).toBe(10) | |
| expect(result[0].severity).toBe('major') | |
| }) | |
| it('should parse review with line range', () => { | |
| const response = `[10-15] | critical | security: SQL injection risk` | |
| const patches = [ | |
| { | |
| newStart: 1, | |
| newLength: 20, | |
| }, | |
| ] | |
| const result = parseReview(response, patches, 'test.js') | |
| expect(result).toHaveLength(1) | |
| expect(result[0].startLine).toBe(10) | |
| expect(result[0].endLine).toBe(15) | |
| expect(result[0].severity).toBe('critical') | |
| }) | |
| it('should skip reviews outside valid line range', () => { | |
| const response = `[100] | major | bug: Out of range` | |
| const patches = [ | |
| { | |
| newStart: 1, | |
| newLength: 20, | |
| }, | |
| ] | |
| const result = parseReview(response, patches, 'test.js') | |
| expect(result).toHaveLength(0) | |
| }) | |
| it('should extract remedy from code blocks', () => { | |
| const response = `[10] | major | bug: Fix this | |
| \`\`\` | |
| const fixed = value || defaultValue; | |
| \`\`\`` | |
| const patches = [ | |
| { | |
| newStart: 1, | |
| newLength: 20, | |
| }, | |
| ] | |
| const result = parseReview(response, patches, 'test.js') | |
| expect(result).toHaveLength(1) | |
| expect(result[0].remedy).toContain('fixed') | |
| }) | |
| }) | |
| describe('extractPatternType', () => { | |
| it('should extract Security pattern', () => { | |
| expect(extractPatternType('Security issue found')).toBe('security') | |
| }) | |
| it('should extract Bug pattern', () => { | |
| expect(extractPatternType('Bug in loop')).toBe('bug') | |
| }) | |
| it('should extract Performance pattern', () => { | |
| expect(extractPatternType('Performance bottleneck detected')).toBe('performance') | |
| }) | |
| it('should return general for unknown patterns', () => { | |
| expect(extractPatternType('Some random message')).toBe('general') | |
| }) | |
| }) | |
| describe('integration: real-world patch scenarios', () => { | |
| it('should handle the actual e.js patch from PR #52', () => { | |
| const actualPatch = `+function updateSettings(user, newTheme) { | |
| + | |
| + const updatedUser = user; | |
| + | |
| + updatedUser.settings.theme = newTheme; | |
| + | |
| + return updatedUser; | |
| +} | |
| + | |
| + | |
| +const account = { id: 1, settings: { theme: 'dark' } }; | |
| +const newAccount = updateSettings(account, 'light'); | |
| +console.log(account.settings.theme);` | |
| const split = splitPatch(actualPatch) | |
| expect(split).toHaveLength(1) | |
| const hunkInfo = patchStartEndLine(split[0]) | |
| expect(hunkInfo).not.toBeNull() | |
| expect(hunkInfo?.newLength).toBe(13) | |
| const parsed = parsePatch(split[0]) | |
| expect(parsed).not.toBeNull() | |
| expect(parsed?.newHunk).toContain('updateSettings') | |
| }) | |
| it('should handle multi-file diff with multiple hunks', () => { | |
| const multiFileDiff = `diff --git a/calc.py b/calc.py | |
| @@ -1,5 +1,6 @@ | |
| if __name__ == "__main__": | |
| print(f"Addition: {add(10, 5)}") | |
| - print(f"Division: {divide(10, 5)}") | |
| + print(f"Division: {divide(10, 5)}") | |
| + ded | |
| \\ No newline at end of file` | |
| const split = splitPatch(multiFileDiff) | |
| expect(split.length).toBeGreaterThanOrEqual(1) | |
| const hunkInfo = patchStartEndLine(split[0]) | |
| expect(hunkInfo).not.toBeNull() | |
| }) | |
| }) | |
| }) |