@@ -18,6 +18,7 @@ import io.mockk.coEvery
1818import io.mockk.every
1919import io.mockk.just
2020import io.mockk.mockk
21+ import io.mockk.mockkObject
2122import io.mockk.mockkStatic
2223import io.mockk.runs
2324import io.mockk.slot
@@ -26,13 +27,16 @@ import kotlinx.coroutines.test.runTest
2627import org.eclipse.lsp4j.CreateFilesParams
2728import org.eclipse.lsp4j.DeleteFilesParams
2829import org.eclipse.lsp4j.DidChangeWatchedFilesParams
30+ import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams
2931import org.eclipse.lsp4j.FileChangeType
32+ import org.eclipse.lsp4j.WorkspaceFolder
3033import org.eclipse.lsp4j.services.WorkspaceService
3134import org.junit.Assert.assertEquals
3235import org.junit.Before
3336import org.junit.Test
3437import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLanguageServer
3538import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
39+ import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil
3640import java.net.URI
3741import java.nio.file.Path
3842import java.util.concurrent.Callable
@@ -78,6 +82,7 @@ class WorkspaceServiceHandlerTest {
7882 every { mockWorkspaceService.didCreateFiles(any()) } returns Unit
7983 every { mockWorkspaceService.didDeleteFiles(any()) } returns Unit
8084 every { mockWorkspaceService.didChangeWatchedFiles(any()) } returns Unit
85+ every { mockWorkspaceService.didChangeWorkspaceFolders(any()) } returns Unit
8186
8287 // Mock message bus
8388 val messageBus = mockk<MessageBus >()
@@ -152,6 +157,162 @@ class WorkspaceServiceHandlerTest {
152157 verify(exactly = 0 ) { mockWorkspaceService.didChangeWatchedFiles(any()) }
153158 }
154159
160+ @Test
161+ fun `rootsChanged does not notify when no changes` () = runTest {
162+ // Arrange
163+ mockkObject(WorkspaceFolderUtil )
164+ val folders = listOf (
165+ WorkspaceFolder ().apply {
166+ name = " folder1"
167+ uri = " file:///path/to/folder1"
168+ }
169+ )
170+ every { WorkspaceFolderUtil .createWorkspaceFolders(any()) } returns folders
171+
172+ // Act
173+ sut.beforeRootsChange(mockk())
174+ sut.rootsChanged(mockk())
175+
176+ // Assert
177+ verify(exactly = 0 ) { mockWorkspaceService.didChangeWorkspaceFolders(any()) }
178+ }
179+
180+ // rootsChanged handles
181+ @Test
182+ fun `rootsChanged handles init` () = runTest {
183+ // Arrange
184+ mockkObject(WorkspaceFolderUtil )
185+ val oldFolders = emptyList<WorkspaceFolder >()
186+ val newFolders = listOf (
187+ WorkspaceFolder ().apply {
188+ name = " folder1"
189+ uri = " file:///path/to/folder1"
190+ }
191+ )
192+
193+ // Act
194+ every { WorkspaceFolderUtil .createWorkspaceFolders(project) } returns oldFolders
195+ sut.beforeRootsChange(mockk())
196+ every { WorkspaceFolderUtil .createWorkspaceFolders(project) } returns newFolders
197+ sut.rootsChanged(mockk())
198+
199+ // Assert
200+ val paramsSlot = slot<DidChangeWorkspaceFoldersParams >()
201+ verify(exactly = 1 ) { mockWorkspaceService.didChangeWorkspaceFolders(capture(paramsSlot)) }
202+ assertEquals(1 , paramsSlot.captured.event.added.size)
203+ assertEquals(" folder1" , paramsSlot.captured.event.added[0 ].name)
204+ }
205+
206+ // rootsChanged handles additional files added to root
207+ @Test
208+ fun `rootsChanged handles additional files added to root` () = runTest {
209+ // Arrange
210+ mockkObject(WorkspaceFolderUtil )
211+ val oldFolders = listOf (
212+ WorkspaceFolder ().apply {
213+ name = " folder1"
214+ uri = " file:///path/to/folder1"
215+ }
216+ )
217+ val newFolders = listOf (
218+ WorkspaceFolder ().apply {
219+ name = " folder1"
220+ uri = " file:///path/to/folder1"
221+ },
222+ WorkspaceFolder ().apply {
223+ name = " folder2"
224+ uri = " file:///path/to/folder2"
225+ }
226+ )
227+
228+ // Act
229+ every { WorkspaceFolderUtil .createWorkspaceFolders(project) } returns oldFolders
230+ sut.beforeRootsChange(mockk())
231+ every { WorkspaceFolderUtil .createWorkspaceFolders(project) } returns newFolders
232+ sut.rootsChanged(mockk())
233+
234+ // Assert
235+ val paramsSlot = slot<DidChangeWorkspaceFoldersParams >()
236+ verify(exactly = 1 ) { mockWorkspaceService.didChangeWorkspaceFolders(capture(paramsSlot)) }
237+ assertEquals(1 , paramsSlot.captured.event.added.size)
238+ assertEquals(" folder2" , paramsSlot.captured.event.added[0 ].name)
239+ }
240+
241+ // rootsChanged handles removal of files from root
242+ @Test
243+ fun `rootsChanged handles removal of files from root` () = runTest {
244+ // Arrange
245+ mockkObject(WorkspaceFolderUtil )
246+ val oldFolders = listOf (
247+ WorkspaceFolder ().apply {
248+ name = " folder1"
249+ uri = " file:///path/to/folder1"
250+ },
251+ WorkspaceFolder ().apply {
252+ name = " folder2"
253+ uri = " file:///path/to/folder2"
254+ }
255+ )
256+ val newFolders = listOf (
257+ WorkspaceFolder ().apply {
258+ name = " folder1"
259+ uri = " file:///path/to/folder1"
260+ }
261+ )
262+
263+ // Act
264+ every { WorkspaceFolderUtil .createWorkspaceFolders(project) } returns oldFolders
265+ sut.beforeRootsChange(mockk())
266+ every { WorkspaceFolderUtil .createWorkspaceFolders(project) } returns newFolders
267+ sut.rootsChanged(mockk())
268+
269+ // Assert
270+ val paramsSlot = slot<DidChangeWorkspaceFoldersParams >()
271+ verify(exactly = 1 ) { mockWorkspaceService.didChangeWorkspaceFolders(capture(paramsSlot)) }
272+ assertEquals(1 , paramsSlot.captured.event.removed.size)
273+ assertEquals(" folder2" , paramsSlot.captured.event.removed[0 ].name)
274+ }
275+
276+ @Test
277+ fun `rootsChanged handles multiple simultaneous additions and removals` () = runTest {
278+ // Arrange
279+ mockkObject(WorkspaceFolderUtil )
280+ val oldFolders = listOf (
281+ WorkspaceFolder ().apply {
282+ name = " folder1"
283+ uri = " file:///path/to/folder1"
284+ },
285+ WorkspaceFolder ().apply {
286+ name = " folder2"
287+ uri = " file:///path/to/folder2"
288+ }
289+ )
290+ val newFolders = listOf (
291+ WorkspaceFolder ().apply {
292+ name = " folder1"
293+ uri = " file:///path/to/folder1"
294+ },
295+ WorkspaceFolder ().apply {
296+ name = " folder3"
297+ uri = " file:///path/to/folder3"
298+ }
299+ )
300+
301+ // Act
302+ every { WorkspaceFolderUtil .createWorkspaceFolders(project) } returns oldFolders
303+ sut.beforeRootsChange(mockk())
304+ every { WorkspaceFolderUtil .createWorkspaceFolders(project) } returns newFolders
305+ sut.rootsChanged(mockk())
306+
307+ // Assert
308+ val paramsSlot = slot<DidChangeWorkspaceFoldersParams >()
309+ verify(exactly = 1 ) { mockWorkspaceService.didChangeWorkspaceFolders(capture(paramsSlot)) }
310+ assertEquals(1 , paramsSlot.captured.event.added.size)
311+ assertEquals(1 , paramsSlot.captured.event.removed.size)
312+ assertEquals(" folder3" , paramsSlot.captured.event.added[0 ].name)
313+ assertEquals(" folder2" , paramsSlot.captured.event.removed[0 ].name)
314+ }
315+
155316 private fun createMockVFileEvent (uri : URI , type : FileChangeType = FileChangeType .Changed ): VFileEvent {
156317 val virtualFile = mockk<VirtualFile >()
157318 val nioPath = mockk<Path >()
0 commit comments