Sometime in the past I made a blogpost on how I made a resharper plugin.
First warning. Running your tests will be slow at best. This is because the sdk creates the whole visual studio solution in memory when you run your tests.
You can find all the code on github.
The resharper SDK makes it “easy” for you to test. They have automated a lot and you just have to do some simple things and all the magic will be done for you.
Alas, documentation is not so brilliant. So most of this I had to found out by myself or ask Matt Ellis.
The contextaction I made has two methods Execute and IsAvailable. And I found out that you have two sort of tests for that. The Execute tests and the Availability tests.
Firt you create a resharper testproject.
Lets start with the Availability tests.
First of all you start by making a class that inherits from VBContextActionAvailabilityTestBase or CSharpContextActionAvailabilityTestBase. Then you create your testdata.
For this you need to create a folder. test with a folder data in it. Do this in your testproject and make all the files you add to that have a build action of none and copy to output directory to copy always. That way you can have all your files there without them being compiled.
Now we create our first file.
This is Class1.vb
vbnet
Public Class Cla{off}ss1
{off}
End Class
The key thing to see here is the {off} placeholder. The test will look for the {on} and {off} placeholder. It will then check to see if your ContextAction is available in those places. When it finds an off the IsAvailable method should return false. When IsAvailable is true it should find an on in that place.
So the above test should pass. because I don’t want to have the contextaction when filename and type are the same or in any other place.
Our second testfile called Calss2.vb looks like this.
vbnet
Public Class Cla{on}ss1
{off}
End Class
In this case the filename and typename do not match and thus IsAvailable should return true and show the contextaction.
I added a few more files to test as you can see on github.
And then I need to tell it which files it should test.
```vbnet Imports ClassNameToFileNameQuickfix Imports JetBrains.ReSharper.Intentions.Extensibility Imports JetBrains.ReSharper.Feature.Services.VB.Bulbs Imports NUnit.Framework Imports JetBrains.ReSharper.Intentions.VB.Tests.ContextActions
Public Class VBAvailabiltyTests Inherits VBContextActionAvailabilityTestBase
<Test()>
Public Sub AvailabilityTestClass1vb()
DoTestFiles("Class1.vb")
End Sub
<Test()>
Public Sub AvailabilityTestClass2vb()
DoTestFiles("Class2.vb")
End Sub
Protected Overrides ReadOnly Property ExtraPath() As String
Get
Return "TestsAvailability"
End Get
End Property
Protected Overrides ReadOnly Property RelativeTestDataPath() As String
Get
Return "TestsAvailability"
End Get
End Property
Protected Overrides Function CreateContextAction(ByVal dataProvider As IVBContextActionDataProvider) As IContextAction
Return New ClassNameToFileNameQuickFixActionForVB(dataProvider)
End Function
End Class``` you do this by simply calling dotestfiles and then giving it the testclassname. YOu will also have to set the extrapath if you created folder in test/data.
And in CreateContextAction you instantiate the contextaction you want to test.
So now on to the executetests.
Here you have to creat two testfiles. One with the code before the action is executed and one with the desired result. This desired result file has the same name as the before file with an extension of .gold.
So I created a Class2.vb file.
```vbnet Public Class Cla{caret}ss1
End Class``` In this case you use a {caret} placeholder to tell the testcode where to invoke the contextaction.
And then we have a Class2.vb.gold file.
```vbnet Public Class Cla{caret}ss2
End Class``` As you can see we want to change the typename from Class1 to Class2.
And here is the code that will check the above.
```vbnet Imports ClassNameToFileNameQuickfix Imports JetBrains.ReSharper.Intentions.Extensibility Imports JetBrains.ReSharper.Feature.Services.VB.Bulbs Imports NUnit.Framework Imports JetBrains.ReSharper.Intentions.VB.Tests.ContextActions
Public Class VBExecuteTests Inherits VBContextActionExecuteTestBase
<Test()>
Public Sub ExecuteTestClass2Vb()
DoTestFiles("Class2.vb")
End Sub
Protected Overrides ReadOnly Property ExtraPath() As String
Get
Return "TestsExecute"
End Get
End Property
Protected Overrides ReadOnly Property RelativeTestDataPath() As String
Get
Return "TestsExecute"
End Get
End Property
Protected Overrides Function CreateContextAction(ByVal dataProvider As IVBContextActionDataProvider) As IContextAction
Return New ClassNameToFileNameQuickFixActionForVB(dataProvider)
End Function
End Class``` The biggest difference with the availability tests is that you now inherit from VBContextActionExecuteTestBase or CSharpContextActionExecuteTestBase.
After creating all these tests I noticed I had more than a few tests that were failing.
This was my code at the time.
```vbnet Imports JetBrains.ReSharper.Feature.Services.Bulbs Imports JetBrains.ReSharper.Intentions.Extensibility Imports JetBrains.ProjectModel Imports JetBrains.Application.Progress Imports JetBrains.TextControl Imports JetBrains.Util Imports JetBrains.ReSharper.Psi.Tree Imports JetBrains.ReSharper.Psi.VB.Tree
Namespace ClassNameToFileNameQuickfix Public Class ClassNameToFileNameQuickFixActionBase Inherits ContextActionBase
Private _provider As IContextActionDataProvider
Public Sub New(dataProvider As IContextActionDataProvider)
_provider = dataProvider
End Sub
Public Overrides ReadOnly Property Text As String
Get
Return "Rename class to match filename"
End Get
End Property
Protected Overrides Function ExecutePsiTransaction(ByVal solution As ISolution, ByVal progress As IProgressIndicator) As System.Action(Of ITextControl)
Dim literal = _provider.GetSelectedElement(Of JetBrains.ReSharper.Psi.Tree.ITypeDeclaration)(True, True)
If (literal IsNot Nothing) Then
literal.SetName(literal.GetSourceFile().Name.Substring(0, literal.GetSourceFile().Name.Length - 3))
End If
Return Nothing
End Function
Public Overrides Function IsAvailable(ByVal cache As IUserDataHolder) As Boolean
Dim literal = _provider.GetSelectedElement(Of JetBrains.ReSharper.Psi.Tree.ITypeDeclaration)(True, True)
If (literal IsNot Nothing AndAlso Not literal.DeclaredName = literal.GetSourceFile().Name.Substring(0, literal.GetSourceFile().Name.Length - 3)) Then
Return True
End If
Return False
End Function
End Class
End NameSpace``` And it was Avaibility test of Class2 that was failing.
With this error message.
Incorrect state at offset 25.
Supposed to be False:off:, but was True
Public Class Class1
|HERE|
End Class
Expected: False
But was: True
The problem being that the contextaction would show up anywhere in the type. That is not what I was aiming for. I was aiming for typename only.
And 2 hours later I end up with this.
vbnet
Public Overrides Function IsAvailable(ByVal cache As IUserDataHolder) As Boolean
Dim selectedvbelement = _provider.SelectedElement.Parent.Parent
Dim selectedcsharpelement = _provider.SelectedElement.Parent
Dim literal = _provider.GetSelectedElement(Of JetBrains.ReSharper.Psi.Tree.ITypeDeclaration)(True, True)
If (literal IsNot Nothing AndAlso Not literal.DeclaredName = literal.GetSourceFile().Name.Substring(0, literal.GetSourceFile().Name.Length - 3)) AndAlso (TypeOf selectedvbelement Is JetBrains.ReSharper.Psi.VB.Tree.IClassDeclaration OrElse TypeOf selectedcsharpelement Is JetBrains.ReSharper.Psi.CSharp.Tree.IClassDeclaration OrElse TypeOf selectedvbelement Is JetBrains.ReSharper.Psi.VB.Tree.IInterfaceDeclaration OrElse TypeOf selectedcsharpelement Is JetBrains.ReSharper.Psi.CSharp.Tree.IInterfaceDeclaration OrElse TypeOf selectedvbelement Is JetBrains.ReSharper.Psi.VB.Tree.IModuleDeclaration) Then
Return True
End If
Return False
End Function
And all current tests pass. Look at the difference between the VB.net code and the C# code. Not funny.
Anyway it works. A lot of this is thanks to Matt Ellis.