Skip to content

Commit 482a6ff

Browse files
committed
add testing for TextDocumentServiceHandler
1 parent b6af245 commit 482a6ff

File tree

2 files changed

+233
-8
lines changed

2 files changed

+233
-8
lines changed

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/textdocument/TextDocumentServiceHandler.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ class TextDocumentServiceHandler(
5454
}
5555

5656
override fun beforeDocumentSaving(document: Document) {
57-
AmazonQLspService.executeIfRunning(project) {
57+
AmazonQLspService.executeIfRunning(project) { languageServer ->
5858
val file = FileDocumentManager.getInstance().getFile(document) ?: return@executeIfRunning
5959
file.toNioPath().toUri().toString().takeIf { it.isNotEmpty() }?.let { uri ->
60-
it.textDocumentService.didSave(
60+
languageServer.textDocumentService.didSave(
6161
DidSaveTextDocumentParams().apply {
6262
textDocument = TextDocumentIdentifier().apply {
6363
this.uri = uri
@@ -70,12 +70,12 @@ class TextDocumentServiceHandler(
7070
}
7171

7272
override fun after(events: MutableList<out VFileEvent>) {
73-
AmazonQLspService.executeIfRunning(project) {
73+
AmazonQLspService.executeIfRunning(project) { languageServer ->
7474
pluginAwareExecuteOnPooledThread {
7575
events.filterIsInstance<VFileContentChangeEvent>().forEach { event ->
7676
val document = FileDocumentManager.getInstance().getCachedDocument(event.file) ?: return@forEach
7777
event.file.toNioPath().toUri().toString().takeIf { it.isNotEmpty() }?.let { uri ->
78-
it.textDocumentService.didChange(
78+
languageServer.textDocumentService.didChange(
7979
DidChangeTextDocumentParams().apply {
8080
textDocument = VersionedTextDocumentIdentifier().apply {
8181
this.uri = uri
@@ -98,9 +98,9 @@ class TextDocumentServiceHandler(
9898
source: FileEditorManager,
9999
file: VirtualFile,
100100
) {
101-
AmazonQLspService.executeIfRunning(project) {
101+
AmazonQLspService.executeIfRunning(project) { languageServer ->
102102
file.toNioPath().toUri().toString().takeIf { it.isNotEmpty() }?.let { uri ->
103-
it.textDocumentService.didOpen(
103+
languageServer.textDocumentService.didOpen(
104104
DidOpenTextDocumentParams().apply {
105105
textDocument = TextDocumentItem().apply {
106106
this.uri = uri
@@ -116,9 +116,9 @@ class TextDocumentServiceHandler(
116116
source: FileEditorManager,
117117
file: VirtualFile,
118118
) {
119-
AmazonQLspService.executeIfRunning(project) {
119+
AmazonQLspService.executeIfRunning(project) { languageServer ->
120120
file.toNioPath().toUri().toString().takeIf { it.isNotEmpty() }?.let { uri ->
121-
it.textDocumentService.didClose(
121+
languageServer.textDocumentService.didClose(
122122
DidCloseTextDocumentParams().apply {
123123
textDocument = TextDocumentIdentifier().apply {
124124
this.uri = uri
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.amazonq.lsp.textdocument
5+
6+
import com.intellij.openapi.Disposable
7+
import com.intellij.openapi.application.Application
8+
import com.intellij.openapi.application.ApplicationManager
9+
import com.intellij.openapi.components.serviceIfCreated
10+
import com.intellij.openapi.editor.Document
11+
import com.intellij.openapi.fileEditor.FileDocumentManager
12+
import com.intellij.openapi.project.Project
13+
import com.intellij.openapi.vfs.VirtualFile
14+
import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent
15+
import com.intellij.util.messages.MessageBus
16+
import com.intellij.util.messages.MessageBusConnection
17+
import io.mockk.coEvery
18+
import io.mockk.every
19+
import io.mockk.just
20+
import io.mockk.mockk
21+
import io.mockk.mockkStatic
22+
import io.mockk.runs
23+
import io.mockk.slot
24+
import io.mockk.verify
25+
import kotlinx.coroutines.test.runTest
26+
import org.eclipse.lsp4j.DidChangeTextDocumentParams
27+
import org.eclipse.lsp4j.DidCloseTextDocumentParams
28+
import org.eclipse.lsp4j.DidOpenTextDocumentParams
29+
import org.eclipse.lsp4j.DidSaveTextDocumentParams
30+
import org.eclipse.lsp4j.services.TextDocumentService
31+
import org.junit.Assert.assertEquals
32+
import org.junit.Before
33+
import org.junit.Test
34+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer
35+
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
36+
import java.net.URI
37+
import java.nio.file.Path
38+
import java.util.concurrent.Callable
39+
import java.util.concurrent.CompletableFuture
40+
41+
class TextDocumentServiceHandlerTest {
42+
private lateinit var project: Project
43+
private lateinit var mockLanguageServer: AmazonQLanguageServer
44+
private lateinit var mockTextDocumentService: TextDocumentService
45+
private lateinit var sut: TextDocumentServiceHandler
46+
private lateinit var mockApplication: Application
47+
48+
@Before
49+
fun setup() {
50+
project = mockk<Project>()
51+
mockTextDocumentService = mockk<TextDocumentService>()
52+
mockLanguageServer = mockk<AmazonQLanguageServer>()
53+
54+
mockApplication = mockk<Application>()
55+
mockkStatic(ApplicationManager::class)
56+
every { ApplicationManager.getApplication() } returns mockApplication
57+
every { mockApplication.executeOnPooledThread(any<Callable<*>>()) } answers {
58+
CompletableFuture.completedFuture(firstArg<Callable<*>>().call())
59+
}
60+
61+
// Mock the LSP service
62+
val mockLspService = mockk<AmazonQLspService>()
63+
64+
// Mock the service methods on Project
65+
every { project.getService(AmazonQLspService::class.java) } returns mockLspService
66+
every { project.serviceIfCreated<AmazonQLspService>() } returns mockLspService
67+
68+
// Mock the LSP service's executeSync method as a suspend function
69+
coEvery {
70+
mockLspService.executeSync(any())
71+
} coAnswers {
72+
val func = firstArg<suspend (AmazonQLanguageServer) -> Unit>()
73+
func.invoke(mockLanguageServer)
74+
}
75+
76+
// Mock workspace service
77+
every { mockLanguageServer.textDocumentService } returns mockTextDocumentService
78+
every { mockTextDocumentService.didChange(any()) } returns Unit
79+
every { mockTextDocumentService.didSave(any()) } returns Unit
80+
every { mockTextDocumentService.didOpen(any()) } returns Unit
81+
every { mockTextDocumentService.didClose(any()) } returns Unit
82+
83+
// Mock message bus
84+
val messageBus = mockk<MessageBus>()
85+
every { project.messageBus } returns messageBus
86+
val mockConnection = mockk<MessageBusConnection>()
87+
every { messageBus.connect(any<Disposable>()) } returns mockConnection
88+
every { mockConnection.subscribe(any(), any()) } just runs
89+
90+
sut = TextDocumentServiceHandler(project, mockk())
91+
}
92+
93+
@Test
94+
fun `didSave runs on beforeDocumentSaving`() = runTest {
95+
// Create test document and file
96+
val uri = URI.create("file:///test/path/file.txt")
97+
val document = mockk<Document> {
98+
every { text } returns "test content"
99+
}
100+
101+
val path = mockk<Path> {
102+
every { toUri() } returns uri
103+
}
104+
105+
val file = mockk<VirtualFile> {
106+
every { this@mockk.path } returns uri.path
107+
every { toNioPath() } returns path
108+
}
109+
110+
// Mock FileDocumentManager
111+
val fileDocumentManager = mockk<FileDocumentManager> {
112+
every { getFile(document) } returns file
113+
}
114+
115+
// Replace the FileDocumentManager instance
116+
mockkStatic(FileDocumentManager::class) {
117+
every { FileDocumentManager.getInstance() } returns fileDocumentManager
118+
119+
// Call the handler method
120+
sut.beforeDocumentSaving(document)
121+
122+
// Verify the correct LSP method was called with matching parameters
123+
val paramsSlot = slot<DidSaveTextDocumentParams>()
124+
verify { mockTextDocumentService.didSave(capture(paramsSlot)) }
125+
126+
with(paramsSlot.captured) {
127+
assertEquals(uri.toString(), textDocument.uri)
128+
assertEquals("test content", text)
129+
}
130+
}
131+
}
132+
133+
@Test
134+
fun `didOpen runs on fileOpened`() = runTest {
135+
// Create test file
136+
val uri = URI.create("file:///test/path/file.txt")
137+
val content = "test content"
138+
val inputStream = content.byteInputStream()
139+
140+
val path = mockk<Path> {
141+
every { toUri() } returns uri
142+
}
143+
144+
val file = mockk<VirtualFile> {
145+
every { this@mockk.path } returns uri.path
146+
every { toNioPath() } returns path
147+
every { this@mockk.inputStream } returns inputStream
148+
}
149+
150+
// Call the handler method
151+
sut.fileOpened(mockk(), file)
152+
153+
// Verify the correct LSP method was called with matching parameters
154+
val paramsSlot = slot<DidOpenTextDocumentParams>()
155+
verify { mockTextDocumentService.didOpen(capture(paramsSlot)) }
156+
157+
with(paramsSlot.captured.textDocument) {
158+
assertEquals(uri.toString(), this.uri)
159+
assertEquals(content, text)
160+
}
161+
}
162+
163+
@Test
164+
fun `didClose runs on fileClosed`() = runTest {
165+
val uri = URI.create("file:///test/path/file.txt")
166+
val path = mockk<Path> {
167+
every { toUri() } returns uri
168+
}
169+
val file = mockk<VirtualFile> {
170+
every { this@mockk.path } returns uri.path
171+
every { toNioPath() } returns path
172+
}
173+
174+
sut.fileClosed(mockk(), file)
175+
176+
val paramsSlot = slot<DidCloseTextDocumentParams>()
177+
verify { mockTextDocumentService.didClose(capture(paramsSlot)) }
178+
179+
assertEquals(uri.toString(), paramsSlot.captured.textDocument.uri)
180+
}
181+
182+
@Test
183+
fun `didChange runs on content change events`() = runTest {
184+
val uri = URI.create("file:///test/path/file.txt")
185+
val document = mockk<Document> {
186+
every { text } returns "changed content"
187+
every { modificationStamp } returns 123L
188+
}
189+
190+
val path = mockk<Path> {
191+
every { toUri() } returns uri
192+
}
193+
194+
val file = mockk<VirtualFile> {
195+
every { this@mockk.path } returns uri.path
196+
every { toNioPath() } returns path
197+
}
198+
199+
val changeEvent = mockk<VFileContentChangeEvent> {
200+
every { this@mockk.file } returns file
201+
}
202+
203+
// Mock FileDocumentManager
204+
val fileDocumentManager = mockk<FileDocumentManager> {
205+
every { getCachedDocument(file) } returns document
206+
}
207+
208+
mockkStatic(FileDocumentManager::class) {
209+
every { FileDocumentManager.getInstance() } returns fileDocumentManager
210+
211+
// Call the handler method
212+
sut.after(mutableListOf(changeEvent))
213+
}
214+
215+
// Verify the correct LSP method was called with matching parameters
216+
val paramsSlot = slot<DidChangeTextDocumentParams>()
217+
verify { mockTextDocumentService.didChange(capture(paramsSlot)) }
218+
219+
with(paramsSlot.captured) {
220+
assertEquals(uri.toString(), textDocument.uri)
221+
assertEquals(123, textDocument.version)
222+
assertEquals("changed content", contentChanges[0].text)
223+
}
224+
}
225+
}

0 commit comments

Comments
 (0)