66import * as vscode from 'vscode'
77import * as sinon from 'sinon'
88import assert from 'assert'
9- import { EditDecorationManager } from '../../../../../src/app/inline/EditRendering/displayImage'
9+ import { EditDecorationManager , displaySvgDecoration } from '../../../../../src/app/inline/EditRendering/displayImage'
10+ import { EditSuggestionState } from '../../../../../src/app/inline/editSuggestionState'
11+
12+ // Shared helper function to create common stubs
13+ function createCommonStubs ( sandbox : sinon . SinonSandbox ) {
14+ const documentStub = {
15+ getText : sandbox . stub ( ) . returns ( 'Original code content' ) ,
16+ uri : vscode . Uri . file ( '/test/file.ts' ) ,
17+ lineAt : sandbox . stub ( ) . returns ( {
18+ text : 'Line text content' ,
19+ range : new vscode . Range ( 0 , 0 , 0 , 18 ) ,
20+ rangeIncludingLineBreak : new vscode . Range ( 0 , 0 , 0 , 19 ) ,
21+ firstNonWhitespaceCharacterIndex : 0 ,
22+ isEmptyOrWhitespace : false ,
23+ } ) ,
24+ } as unknown as sinon . SinonStubbedInstance < vscode . TextDocument >
25+
26+ const editorStub = {
27+ document : documentStub ,
28+ setDecorations : sandbox . stub ( ) ,
29+ } as unknown as sinon . SinonStubbedInstance < vscode . TextEditor >
30+
31+ return { documentStub, editorStub }
32+ }
1033
1134describe ( 'EditDecorationManager' , function ( ) {
1235 let sandbox : sinon . SinonSandbox
@@ -25,23 +48,13 @@ describe('EditDecorationManager', function () {
2548 dispose : sandbox . stub ( ) ,
2649 } as unknown as sinon . SinonStubbedInstance < vscode . TextEditorDecorationType >
2750
28- documentStub = {
29- getText : sandbox . stub ( ) . returns ( 'Original code content' ) ,
30- lineCount : 5 ,
31- lineAt : sandbox . stub ( ) . returns ( {
32- text : 'Line text content' ,
33- range : new vscode . Range ( 0 , 0 , 0 , 18 ) ,
34- rangeIncludingLineBreak : new vscode . Range ( 0 , 0 , 0 , 19 ) ,
35- firstNonWhitespaceCharacterIndex : 0 ,
36- isEmptyOrWhitespace : false ,
37- } ) ,
38- } as unknown as sinon . SinonStubbedInstance < vscode . TextDocument >
39-
40- editorStub = {
41- document : documentStub ,
42- setDecorations : sandbox . stub ( ) ,
43- edit : sandbox . stub ( ) . resolves ( true ) ,
44- } as unknown as sinon . SinonStubbedInstance < vscode . TextEditor >
51+ const commonStubs = createCommonStubs ( sandbox )
52+ documentStub = commonStubs . documentStub
53+ editorStub = commonStubs . editorStub
54+
55+ // Add additional properties needed for this test suite - extend the stub objects
56+ Object . assign ( documentStub , { lineCount : 5 } )
57+ Object . assign ( editorStub , { edit : sandbox . stub ( ) . resolves ( true ) } )
4558
4659 windowStub = sandbox . stub ( vscode . window )
4760 windowStub . createTextEditorDecorationType . returns ( decorationTypeStub as any )
@@ -174,3 +187,124 @@ describe('EditDecorationManager', function () {
174187 sinon . assert . calledWith ( editorStub . setDecorations . secondCall , manager [ 'removedCodeDecorationType' ] , [ ] )
175188 } )
176189} )
190+
191+ describe ( 'displaySvgDecoration cursor distance auto-reject' , function ( ) {
192+ let sandbox : sinon . SinonSandbox
193+ let editorStub : sinon . SinonStubbedInstance < vscode . TextEditor >
194+ let windowStub : sinon . SinonStub
195+ let commandsStub : sinon . SinonStub
196+ let editSuggestionStateStub : sinon . SinonStub
197+ let onDidChangeTextEditorSelectionStub : sinon . SinonStub
198+ let selectionChangeListener : ( e : vscode . TextEditorSelectionChangeEvent ) => void
199+
200+ // Helper function to setup displaySvgDecoration
201+ async function setupDisplaySvgDecoration ( startLine : number ) {
202+ return await displaySvgDecoration (
203+ editorStub as unknown as vscode . TextEditor ,
204+ vscode . Uri . parse ( 'data:image/svg+xml;base64,test' ) ,
205+ startLine ,
206+ 'new code' ,
207+ [ ] ,
208+ { } as any ,
209+ { } as any ,
210+ { itemId : 'test' , insertText : 'patch' } as any
211+ )
212+ }
213+
214+ // Helper function to create selection change event
215+ function createSelectionChangeEvent ( line : number ) : vscode . TextEditorSelectionChangeEvent {
216+ const position = new vscode . Position ( line , 0 )
217+ const selection = new vscode . Selection ( position , position )
218+ return {
219+ textEditor : editorStub ,
220+ selections : [ selection ] ,
221+ kind : vscode . TextEditorSelectionChangeKind . Mouse ,
222+ } as vscode . TextEditorSelectionChangeEvent
223+ }
224+
225+ beforeEach ( function ( ) {
226+ sandbox = sinon . createSandbox ( )
227+
228+ const commonStubs = createCommonStubs ( sandbox )
229+ editorStub = commonStubs . editorStub
230+
231+ // Mock vscode.window.onDidChangeTextEditorSelection
232+ onDidChangeTextEditorSelectionStub = sandbox . stub ( )
233+ onDidChangeTextEditorSelectionStub . returns ( { dispose : sandbox . stub ( ) } )
234+ windowStub = sandbox . stub ( vscode . window , 'onDidChangeTextEditorSelection' )
235+ windowStub . callsFake ( ( callback ) => {
236+ selectionChangeListener = callback
237+ return { dispose : sandbox . stub ( ) }
238+ } )
239+
240+ // Mock vscode.commands.executeCommand
241+ commandsStub = sandbox . stub ( vscode . commands , 'executeCommand' )
242+
243+ // Mock EditSuggestionState
244+ editSuggestionStateStub = sandbox . stub ( EditSuggestionState , 'isEditSuggestionActive' )
245+ editSuggestionStateStub . returns ( true )
246+
247+ // Mock other required dependencies
248+ sandbox . stub ( vscode . workspace , 'onDidChangeTextDocument' ) . returns ( { dispose : sandbox . stub ( ) } )
249+ } )
250+
251+ afterEach ( function ( ) {
252+ sandbox . restore ( )
253+ } )
254+
255+ it ( 'should not reject when cursor moves less than 25 lines away' , async function ( ) {
256+ const startLine = 50
257+ await setupDisplaySvgDecoration ( startLine )
258+
259+ selectionChangeListener ( createSelectionChangeEvent ( startLine + 24 ) )
260+
261+ sinon . assert . notCalled ( commandsStub )
262+ } )
263+
264+ it ( 'should not reject when cursor moves exactly 25 lines away' , async function ( ) {
265+ const startLine = 50
266+ await setupDisplaySvgDecoration ( startLine )
267+
268+ selectionChangeListener ( createSelectionChangeEvent ( startLine + 25 ) )
269+
270+ sinon . assert . notCalled ( commandsStub )
271+ } )
272+
273+ it ( 'should reject when cursor moves more than 25 lines away' , async function ( ) {
274+ const startLine = 50
275+ await setupDisplaySvgDecoration ( startLine )
276+
277+ selectionChangeListener ( createSelectionChangeEvent ( startLine + 26 ) )
278+
279+ sinon . assert . calledOnceWithExactly ( commandsStub , 'aws.amazonq.inline.rejectEdit' )
280+ } )
281+
282+ it ( 'should reject when cursor moves more than 25 lines before the edit' , async function ( ) {
283+ const startLine = 50
284+ await setupDisplaySvgDecoration ( startLine )
285+
286+ selectionChangeListener ( createSelectionChangeEvent ( startLine - 26 ) )
287+
288+ sinon . assert . calledOnceWithExactly ( commandsStub , 'aws.amazonq.inline.rejectEdit' )
289+ } )
290+
291+ it ( 'should not reject when edit is near beginning of file and cursor cannot move far enough' , async function ( ) {
292+ const startLine = 10
293+ await setupDisplaySvgDecoration ( startLine )
294+
295+ selectionChangeListener ( createSelectionChangeEvent ( 0 ) )
296+
297+ sinon . assert . notCalled ( commandsStub )
298+ } )
299+
300+ it ( 'should not reject when edit suggestion is not active' , async function ( ) {
301+ editSuggestionStateStub . returns ( false )
302+
303+ const startLine = 50
304+ await setupDisplaySvgDecoration ( startLine )
305+
306+ selectionChangeListener ( createSelectionChangeEvent ( startLine + 100 ) )
307+
308+ sinon . assert . notCalled ( commandsStub )
309+ } )
310+ } )
0 commit comments