Skip to content

Commit bb5d506

Browse files
committed
refactor: extract editor utilities into EditorUtils module and add tests
1 parent 2e56149 commit bb5d506

File tree

2 files changed

+116
-62
lines changed

2 files changed

+116
-62
lines changed

src/core/__tests__/CodeActionProvider.test.ts

Lines changed: 41 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as vscode from "vscode"
22
import { CodeActionProvider, ACTION_NAMES } from "../CodeActionProvider"
3+
import { EditorUtils } from "../EditorUtils"
34

45
// Mock VSCode API
56
jest.mock("vscode", () => ({
@@ -16,13 +17,6 @@ jest.mock("vscode", () => ({
1617
start: { line: startLine, character: startChar },
1718
end: { line: endLine, character: endChar },
1819
})),
19-
Position: jest.fn().mockImplementation((line, character) => ({
20-
line,
21-
character,
22-
})),
23-
workspace: {
24-
getWorkspaceFolder: jest.fn(),
25-
},
2620
DiagnosticSeverity: {
2721
Error: 0,
2822
Warning: 1,
@@ -31,6 +25,16 @@ jest.mock("vscode", () => ({
3125
},
3226
}))
3327

28+
// Mock EditorUtils
29+
jest.mock("../EditorUtils", () => ({
30+
EditorUtils: {
31+
getEffectiveRange: jest.fn(),
32+
getFilePath: jest.fn(),
33+
hasIntersectingRange: jest.fn(),
34+
createDiagnosticData: jest.fn(),
35+
},
36+
}))
37+
3438
describe("CodeActionProvider", () => {
3539
let provider: CodeActionProvider
3640
let mockDocument: any
@@ -55,68 +59,32 @@ describe("CodeActionProvider", () => {
5559
mockContext = {
5660
diagnostics: [],
5761
}
58-
})
59-
60-
describe("getEffectiveRange", () => {
61-
it("should return selected text when available", () => {
62-
mockDocument.getText.mockReturnValue("selected text")
63-
64-
const result = (provider as any).getEffectiveRange(mockDocument, mockRange)
65-
66-
expect(result).toEqual({
67-
range: mockRange,
68-
text: "selected text",
69-
})
70-
})
71-
72-
it("should return null for empty line", () => {
73-
mockDocument.getText.mockReturnValue("")
74-
mockDocument.lineAt.mockReturnValue({ text: "", lineNumber: 0 })
75-
76-
const result = (provider as any).getEffectiveRange(mockDocument, mockRange)
77-
78-
expect(result).toBeNull()
79-
})
80-
})
81-
82-
describe("getFilePath", () => {
83-
it("should return relative path when in workspace", () => {
84-
const mockWorkspaceFolder = {
85-
uri: { fsPath: "/test" },
86-
}
87-
;(vscode.workspace.getWorkspaceFolder as jest.Mock).mockReturnValue(mockWorkspaceFolder)
88-
89-
const result = (provider as any).getFilePath(mockDocument)
90-
91-
expect(result).toBe("file.ts")
92-
})
93-
94-
it("should return absolute path when not in workspace", () => {
95-
;(vscode.workspace.getWorkspaceFolder as jest.Mock).mockReturnValue(null)
9662

97-
const result = (provider as any).getFilePath(mockDocument)
98-
99-
expect(result).toBe("/test/file.ts")
63+
// Setup default EditorUtils mocks
64+
;(EditorUtils.getEffectiveRange as jest.Mock).mockReturnValue({
65+
range: mockRange,
66+
text: "test code",
10067
})
68+
;(EditorUtils.getFilePath as jest.Mock).mockReturnValue("/test/file.ts")
69+
;(EditorUtils.hasIntersectingRange as jest.Mock).mockReturnValue(true)
70+
;(EditorUtils.createDiagnosticData as jest.Mock).mockImplementation((d) => d)
10171
})
10272

10373
describe("provideCodeActions", () => {
104-
beforeEach(() => {
105-
mockDocument.getText.mockReturnValue("test code")
106-
mockDocument.lineAt.mockReturnValue({ text: "test code", lineNumber: 0 })
107-
})
108-
109-
it("should provide explain and improve actions by default", () => {
74+
it("should provide explain, improve, fix logic, and add to context actions by default", () => {
11075
const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext)
11176

112-
expect(actions).toHaveLength(4)
77+
expect(actions).toHaveLength(7) // 2 explain + 2 fix logic + 2 improve + 1 add to context
11378
expect((actions as any)[0].title).toBe(`${ACTION_NAMES.EXPLAIN} in New Task`)
11479
expect((actions as any)[1].title).toBe(`${ACTION_NAMES.EXPLAIN} in Current Task`)
115-
expect((actions as any)[2].title).toBe(`${ACTION_NAMES.IMPROVE} in New Task`)
116-
expect((actions as any)[3].title).toBe(`${ACTION_NAMES.IMPROVE} in Current Task`)
80+
expect((actions as any)[2].title).toBe(`${ACTION_NAMES.FIX_LOGIC} in New Task`)
81+
expect((actions as any)[3].title).toBe(`${ACTION_NAMES.FIX_LOGIC} in Current Task`)
82+
expect((actions as any)[4].title).toBe(`${ACTION_NAMES.IMPROVE} in New Task`)
83+
expect((actions as any)[5].title).toBe(`${ACTION_NAMES.IMPROVE} in Current Task`)
84+
expect((actions as any)[6].title).toBe(ACTION_NAMES.ADD_TO_CONTEXT)
11785
})
11886

119-
it("should provide fix action when diagnostics exist", () => {
87+
it("should provide fix action instead of fix logic when diagnostics exist", () => {
12088
mockContext.diagnostics = [
12189
{
12290
message: "test error",
@@ -127,22 +95,33 @@ describe("CodeActionProvider", () => {
12795

12896
const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext)
12997

130-
expect(actions).toHaveLength(6)
98+
expect(actions).toHaveLength(7) // 2 explain + 2 fix + 2 improve + 1 add to context
13199
expect((actions as any).some((a: any) => a.title === `${ACTION_NAMES.FIX} in New Task`)).toBe(true)
132100
expect((actions as any).some((a: any) => a.title === `${ACTION_NAMES.FIX} in Current Task`)).toBe(true)
101+
expect((actions as any).some((a: any) => a.title === `${ACTION_NAMES.FIX_LOGIC} in New Task`)).toBe(false)
102+
expect((actions as any).some((a: any) => a.title === `${ACTION_NAMES.FIX_LOGIC} in Current Task`)).toBe(
103+
false,
104+
)
105+
})
106+
107+
it("should return empty array when no effective range", () => {
108+
;(EditorUtils.getEffectiveRange as jest.Mock).mockReturnValue(null)
109+
110+
const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext)
111+
112+
expect(actions).toEqual([])
133113
})
134114

135115
it("should handle errors gracefully", () => {
136116
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => {})
137-
mockDocument.getText.mockImplementation(() => {
117+
;(EditorUtils.getEffectiveRange as jest.Mock).mockImplementation(() => {
138118
throw new Error("Test error")
139119
})
140-
mockDocument.lineAt.mockReturnValue({ text: "test", lineNumber: 0 })
141120

142121
const actions = provider.provideCodeActions(mockDocument, mockRange, mockContext)
143122

144123
expect(actions).toEqual([])
145-
expect(consoleErrorSpy).toHaveBeenCalledWith("Error getting effective range:", expect.any(Error))
124+
expect(consoleErrorSpy).toHaveBeenCalledWith("Error providing code actions:", expect.any(Error))
146125

147126
consoleErrorSpy.mockRestore()
148127
})
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import * as vscode from "vscode"
2+
import { EditorUtils } from "../EditorUtils"
3+
4+
// Mock VSCode API
5+
jest.mock("vscode", () => ({
6+
Range: jest.fn().mockImplementation((startLine, startChar, endLine, endChar) => ({
7+
start: { line: startLine, character: startChar },
8+
end: { line: endLine, character: endChar },
9+
})),
10+
Position: jest.fn().mockImplementation((line, character) => ({
11+
line,
12+
character,
13+
})),
14+
workspace: {
15+
getWorkspaceFolder: jest.fn(),
16+
},
17+
}))
18+
19+
describe("EditorUtils", () => {
20+
let mockDocument: any
21+
22+
beforeEach(() => {
23+
mockDocument = {
24+
getText: jest.fn(),
25+
lineAt: jest.fn(),
26+
lineCount: 10,
27+
uri: { fsPath: "/test/file.ts" },
28+
}
29+
})
30+
31+
describe("getEffectiveRange", () => {
32+
it("should return selected text when available", () => {
33+
const mockRange = new vscode.Range(0, 0, 0, 10)
34+
mockDocument.getText.mockReturnValue("selected text")
35+
36+
const result = EditorUtils.getEffectiveRange(mockDocument, mockRange)
37+
38+
expect(result).toEqual({
39+
range: mockRange,
40+
text: "selected text",
41+
})
42+
})
43+
44+
it("should return null for empty line", () => {
45+
const mockRange = new vscode.Range(0, 0, 0, 10)
46+
mockDocument.getText.mockReturnValue("")
47+
mockDocument.lineAt.mockReturnValue({ text: "", lineNumber: 0 })
48+
49+
const result = EditorUtils.getEffectiveRange(mockDocument, mockRange)
50+
51+
expect(result).toBeNull()
52+
})
53+
})
54+
55+
describe("getFilePath", () => {
56+
it("should return relative path when in workspace", () => {
57+
const mockWorkspaceFolder = {
58+
uri: { fsPath: "/test" },
59+
}
60+
;(vscode.workspace.getWorkspaceFolder as jest.Mock).mockReturnValue(mockWorkspaceFolder)
61+
62+
const result = EditorUtils.getFilePath(mockDocument)
63+
64+
expect(result).toBe("file.ts")
65+
})
66+
67+
it("should return absolute path when not in workspace", () => {
68+
;(vscode.workspace.getWorkspaceFolder as jest.Mock).mockReturnValue(null)
69+
70+
const result = EditorUtils.getFilePath(mockDocument)
71+
72+
expect(result).toBe("/test/file.ts")
73+
})
74+
})
75+
})

0 commit comments

Comments
 (0)