File: IntelliSense\SignatureHelpControllerTests.vb
Web Access
Project: src\src\EditorFeatures\Test2\Microsoft.CodeAnalysis.EditorFeatures2.UnitTests.vbproj (Microsoft.CodeAnalysis.EditorFeatures2.UnitTests)
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.
 
Imports System.Runtime.CompilerServices
Imports System.Threading
Imports Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense
Imports Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.SignatureHelp
Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities
Imports Microsoft.CodeAnalysis.Shared.TestHooks
Imports Microsoft.CodeAnalysis.SignatureHelp
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.VisualStudio.Commanding
Imports Microsoft.VisualStudio.Language.Intellisense
Imports Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
Imports Microsoft.VisualStudio.Text
Imports Microsoft.VisualStudio.Text.Editor
Imports Microsoft.VisualStudio.Text.Editor.Commanding.Commands
Imports Microsoft.VisualStudio.Text.Projection
Imports Moq
 
Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
 
    <[UseExportProvider]>
    Public Class SignatureHelpControllerTests
        <WpfFact>
        Public Async Function InvokeSignatureHelpWithoutDocumentShouldNotStartNewSession() As Task
            Dim emptyProvider = New Mock(Of IDocumentProvider)(MockBehavior.Strict)
            emptyProvider.Setup(Function(p) p.GetDocument(It.IsAny(Of ITextSnapshot), It.IsAny(Of CancellationToken))).Returns(DirectCast(Nothing, Document))
            Dim controller As Controller = Await CreateController(CreateWorkspace(), documentProvider:=emptyProvider)
 
            GetMocks(controller).PresenterSession.Setup(Sub(p) p.Dismiss())
 
            Await controller.WaitForModelComputation_ForTestingPurposesOnlyAsync()
 
            Assert.Equal(0, GetMocks(controller).Provider.GetItemsCount)
        End Function
 
        <WpfFact>
        Public Async Function InvokeSignatureHelpWithDocumentShouldStartNewSession() As Task
            Dim controller = Await CreateController(CreateWorkspace())
 
            GetMocks(controller).Presenter.Verify(Function(p) p.CreateSession(It.IsAny(Of ITextView), It.IsAny(Of ITextBuffer), It.IsAny(Of ISignatureHelpSession)), Times.Once)
        End Function
 
        <WpfFact>
        Public Async Function EmptyModelShouldStopSession() As Task
            Dim presenterSession = New Mock(Of ISignatureHelpPresenterSession)(MockBehavior.Strict)
            presenterSession.Setup(Sub(p) p.Dismiss())
 
            Dim controller = Await CreateController(CreateWorkspace(), presenterSession:=presenterSession, items:={}, waitForPresentation:=True)
 
            GetMocks(controller).PresenterSession.Verify(Sub(p) p.Dismiss(), Times.Once)
        End Function
 
        <WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/985007")>
        Public Async Function UpKeyShouldNotCrashWhenSessionIsDismissed() As Task
            Dim options = New MemberDisplayOptions()
 
            ' Create a provider that will return an empty state when queried the second time
            Dim slowProvider = New Mock(Of ISignatureHelpProvider)(MockBehavior.Strict)
            slowProvider.Setup(Function(p) p.IsTriggerCharacter(" "c)).Returns(True)
            slowProvider.Setup(Function(p) p.IsRetriggerCharacter(" "c)).Returns(True)
            slowProvider.Setup(Function(p) p.GetItemsAsync(It.IsAny(Of Document), It.IsAny(Of Integer), It.IsAny(Of SignatureHelpTriggerInfo), options, It.IsAny(Of CancellationToken))) _
                .Returns(Task.FromResult(New SignatureHelpItems(CreateItems(2), TextSpan.FromBounds(0, 0), selectedItem:=0, semanticParameterIndex:=0, syntacticArgumentCount:=0, argumentName:=Nothing)))
            Dim controller = Await CreateController(CreateWorkspace(), provider:=slowProvider.Object, waitForPresentation:=True)
 
            ' Now force an update to the model that will result in stopping the session
            slowProvider.Setup(Function(p) p.GetItemsAsync(It.IsAny(Of Document), It.IsAny(Of Integer), It.IsAny(Of SignatureHelpTriggerInfo), options, It.IsAny(Of CancellationToken))) _
                .Returns(Task.FromResult(Of SignatureHelpItems)(Nothing))
 
            DirectCast(controller, IChainedCommandHandler(Of TypeCharCommandArgs)).ExecuteCommand(
                New TypeCharCommandArgs(CreateMock(Of ITextView), CreateMock(Of ITextBuffer), " "c),
                Sub() GetMocks(controller).Buffer.Insert(0, " "), TestCommandExecutionContext.Create())
        End Function
 
        <WpfFact>
        Public Async Function CaretMoveWithActiveSessionShouldRecomputeModel() As Task
            Dim controller = Await CreateController(CreateWorkspace(), waitForPresentation:=True)
 
            Mock.Get(GetMocks(controller).View.Object.Caret).Raise(Sub(c) AddHandler c.PositionChanged, Nothing, New CaretPositionChangedEventArgs(Nothing, Nothing, Nothing))
            Await controller.WaitForModelComputation_ForTestingPurposesOnlyAsync()
 
            ' GetItemsAsync is called once initially, and then once as a result of handling the PositionChanged event
            Assert.Equal(2, GetMocks(controller).Provider.GetItemsCount)
        End Function
 
        <WpfFact>
        Public Async Function RetriggerActiveSessionOnClosingBrace() As Task
            Dim controller = Await CreateController(CreateWorkspace(), waitForPresentation:=True)
 
            DirectCast(controller, IChainedCommandHandler(Of TypeCharCommandArgs)).ExecuteCommand(
                New TypeCharCommandArgs(CreateMock(Of ITextView), CreateMock(Of ITextBuffer), ")"c),
                Sub() GetMocks(controller).Buffer.Insert(0, ")"), TestCommandExecutionContext.Create())
            Await controller.WaitForModelComputation_ForTestingPurposesOnlyAsync()
 
            ' GetItemsAsync is called once initially, and then once as a result of handling the typechar command
            Assert.Equal(2, GetMocks(controller).Provider.GetItemsCount)
        End Function
 
        <WpfFact, WorkItem("http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/959116")>
        Public Async Function TypingNonTriggerCharacterShouldNotRequestDocument() As Task
            Dim controller = Await CreateController(CreateWorkspace(), triggerSession:=False)
 
            DirectCast(controller, IChainedCommandHandler(Of TypeCharCommandArgs)).ExecuteCommand(
                New TypeCharCommandArgs(CreateMock(Of ITextView), CreateMock(Of ITextBuffer), "a"c),
                Sub() GetMocks(controller).Buffer.Insert(0, "a"), TestCommandExecutionContext.Create())
 
            GetMocks(controller).DocumentProvider.Verify(Function(p) p.GetDocument(It.IsAny(Of ITextSnapshot), It.IsAny(Of CancellationToken)), Times.Never)
        End Function
 
        Private Shared ReadOnly s_controllerMocksMap As New ConditionalWeakTable(Of Controller, ControllerMocks)
        Private Shared Function GetMocks(controller As Controller) As ControllerMocks
            Dim result As ControllerMocks = Nothing
            Roslyn.Utilities.Contract.ThrowIfFalse(s_controllerMocksMap.TryGetValue(controller, result))
            Return result
        End Function
 
        Private Shared Function CreateWorkspace() As TestWorkspace
            Return TestWorkspace.CreateWorkspace(
                <Workspace>
                    <Project Language="C#">
                        <Document>
                        </Document>
                    </Project>
                </Workspace>, composition:=EditorTestCompositions.EditorFeatures)
        End Function
 
        Private Shared Async Function CreateController(
                workspace As TestWorkspace,
                Optional documentProvider As Mock(Of IDocumentProvider) = Nothing,
                Optional presenterSession As Mock(Of ISignatureHelpPresenterSession) = Nothing,
                Optional items As IList(Of SignatureHelpItem) = Nothing,
                Optional provider As ISignatureHelpProvider = Nothing,
                Optional waitForPresentation As Boolean = False,
                Optional triggerSession As Boolean = True) As Task(Of Controller)
            Dim document = workspace.CurrentSolution.GetDocument(workspace.Documents.Single().Id)
 
            Dim threadingContext = workspace.GetService(Of IThreadingContext)
            Dim bufferFactory As ITextBufferFactoryService = workspace.GetService(Of ITextBufferFactoryService)
            Dim buffer = bufferFactory.CreateTextBuffer()
            Dim view = CreateMockTextView(buffer)
            Dim asyncListener = AsynchronousOperationListenerProvider.NullListener
            If documentProvider Is Nothing Then
                documentProvider = New Mock(Of IDocumentProvider)(MockBehavior.Strict)
                documentProvider.Setup(Function(p) p.GetDocument(It.IsAny(Of ITextSnapshot), It.IsAny(Of CancellationToken))).Returns(document)
            End If
 
            If provider Is Nothing Then
                items = If(items, CreateItems(1))
                provider = New MockSignatureHelpProvider(items)
            End If
 
            Dim presenter = New Mock(Of IIntelliSensePresenter(Of ISignatureHelpPresenterSession, ISignatureHelpSession))(MockBehavior.Strict) With {.DefaultValue = DefaultValue.Mock}
            presenterSession = If(presenterSession, New Mock(Of ISignatureHelpPresenterSession)(MockBehavior.Strict) With {.DefaultValue = DefaultValue.Mock})
            presenter.Setup(Function(p) p.CreateSession(It.IsAny(Of ITextView), It.IsAny(Of ITextBuffer), It.IsAny(Of ISignatureHelpSession))).Returns(presenterSession.Object)
            presenterSession.Setup(Sub(p) p.PresentItems(It.IsAny(Of ITrackingSpan), It.IsAny(Of IList(Of SignatureHelpItem)), It.IsAny(Of SignatureHelpItem), It.IsAny(Of Integer?))) _
                .Callback(Sub() presenterSession.SetupGet(Function(p) p.EditorSessionIsActive).Returns(True))
 
            Dim mockCompletionBroker = New Mock(Of IAsyncCompletionBroker)(MockBehavior.Strict)
            mockCompletionBroker.Setup(Function(b) b.GetSession(It.IsAny(Of ITextView))).Returns(DirectCast(Nothing, IAsyncCompletionSession))
 
            Dim controller = New Controller(
                workspace.GlobalOptions,
                threadingContext,
                view.Object,
                buffer,
                presenter.Object,
                asyncListener,
                documentProvider.Object,
                {provider},
                mockCompletionBroker.Object)
 
            s_controllerMocksMap.Add(controller, New ControllerMocks(
                      view,
                      buffer,
                      presenter,
                      presenterSession,
                      asyncListener,
                      documentProvider,
                      TryCast(provider, MockSignatureHelpProvider)))
 
            If triggerSession Then
                DirectCast(controller, IChainedCommandHandler(Of InvokeSignatureHelpCommandArgs)).ExecuteCommand(
                    New InvokeSignatureHelpCommandArgs(view.Object, buffer), Nothing, TestCommandExecutionContext.Create())
                If waitForPresentation Then
                    Await controller.WaitForModelComputation_ForTestingPurposesOnlyAsync()
                End If
            End If
 
            Return controller
        End Function
 
        Private Shared Function CreateItems(count As Integer) As IList(Of SignatureHelpItem)
            Return Enumerable.Range(0, count).Select(Function(i) New SignatureHelpItem(isVariadic:=False, documentationFactory:=Nothing, prefixParts:=New List(Of TaggedText), separatorParts:={}, suffixParts:={}, parameters:={}, descriptionParts:={})).ToList()
        End Function
 
        Friend Class MockSignatureHelpProvider
            Implements ISignatureHelpProvider
 
            Private ReadOnly _items As IList(Of SignatureHelpItem)
 
            Public Sub New(items As IList(Of SignatureHelpItem))
                Me._items = items
            End Sub
 
            Public Property GetItemsCount As Integer
 
            Public Function GetItemsAsync(document As Document, position As Integer, triggerInfo As SignatureHelpTriggerInfo, options As MemberDisplayOptions, cancellationToken As CancellationToken) As Task(Of SignatureHelpItems) Implements ISignatureHelpProvider.GetItemsAsync
                GetItemsCount += 1
                Return Task.FromResult(If(_items.Any(),
                                       New SignatureHelpItems(_items, TextSpan.FromBounds(position, position), selectedItem:=0, semanticParameterIndex:=0, syntacticArgumentCount:=0, argumentName:=Nothing),
                                       Nothing))
            End Function
 
            Public Function IsTriggerCharacter(ch As Char) As Boolean Implements ISignatureHelpProvider.IsTriggerCharacter
                Return ch = "("c
            End Function
 
            Public Function IsRetriggerCharacter(ch As Char) As Boolean Implements ISignatureHelpProvider.IsRetriggerCharacter
                Return ch = ")"c
            End Function
        End Class
 
        Private Shared Function CreateMockTextView(buffer As ITextBuffer) As Mock(Of ITextView)
            Dim caret = New Mock(Of ITextCaret)(MockBehavior.Strict)
            caret.Setup(Function(c) c.Position).Returns(Function() New CaretPosition(New VirtualSnapshotPoint(buffer.CurrentSnapshot, buffer.CurrentSnapshot.Length), CreateMock(Of IMappingPoint), PositionAffinity.Predecessor))
            Dim view = New Mock(Of ITextView)(MockBehavior.Strict) With {.DefaultValue = DefaultValue.Mock}
            view.Setup(Function(v) v.Caret).Returns(caret.Object)
            view.Setup(Function(v) v.TextBuffer).Returns(buffer)
            view.Setup(Function(v) v.TextSnapshot).Returns(buffer.CurrentSnapshot)
            Dim bufferGraph = New Mock(Of IBufferGraph)(MockBehavior.Strict)
            view.Setup(Function(v) v.BufferGraph).Returns(bufferGraph.Object)
            Return view
        End Function
 
        Private Shared Function CreateMock(Of T As Class)() As T
            Dim mock = New Mock(Of T)(MockBehavior.Strict)
            Return mock.Object
        End Function
 
        Private Class ControllerMocks
            Public ReadOnly View As Mock(Of ITextView)
            Public ReadOnly Buffer As ITextBuffer
            Public ReadOnly Presenter As Mock(Of IIntelliSensePresenter(Of ISignatureHelpPresenterSession, ISignatureHelpSession))
            Public ReadOnly PresenterSession As Mock(Of ISignatureHelpPresenterSession)
            Public ReadOnly AsyncListener As IAsynchronousOperationListener
            Public ReadOnly DocumentProvider As Mock(Of IDocumentProvider)
            Public ReadOnly Provider As MockSignatureHelpProvider
 
            Public Sub New(view As Mock(Of ITextView),
                           buffer As ITextBuffer,
                           presenter As Mock(Of IIntelliSensePresenter(Of ISignatureHelpPresenterSession, ISignatureHelpSession)),
                           presenterSession As Mock(Of ISignatureHelpPresenterSession),
                           asyncListener As IAsynchronousOperationListener,
                           documentProvider As Mock(Of IDocumentProvider),
                           provider As MockSignatureHelpProvider)
                Me.View = view
                Me.Buffer = buffer
                Me.Presenter = presenter
                Me.PresenterSession = presenterSession
                Me.AsyncListener = asyncListener
                Me.DocumentProvider = documentProvider
                Me.Provider = provider
            End Sub
        End Class
    End Class
End Namespace