diff --git a/.gitignore b/.gitignore
index 2e785c9..61eefd6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,5 @@ _dotTrace*
# NuGet tools
.nuget/
+
+**/TestResult.xml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50abfea..060855a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,7 @@
# Changelog
All notable changes to this project will be documented in this file.
-## 26.09.04XX
+## 26.09.19XX
- "Go to Handler" now also works with the caret anywhere inside a `mediator.Send(...)` /
`Publish(...)` call: the first argument's type is resolved, so there is no need to navigate to the
request declaration first.
diff --git a/src/dotnet/MediatorPlugin.Tests/Infrastructure/TestSolutionFiles.cs b/src/dotnet/MediatorPlugin.Tests/Infrastructure/TestSolutionFiles.cs
index 7f328d6..43a4301 100644
--- a/src/dotnet/MediatorPlugin.Tests/Infrastructure/TestSolutionFiles.cs
+++ b/src/dotnet/MediatorPlugin.Tests/Infrastructure/TestSolutionFiles.cs
@@ -14,6 +14,13 @@ public static class TestSolutionFiles
{
private static readonly string TestSolutionPath = ResolveTestSolutionPath();
+ ///
+ /// Absolute path to the TestSolution folder, used to turn an on-disk file path (e.g. a
+ /// resolved handler's source file) back into the relative path form used elsewhere in these
+ /// tests (e.g. "MediatR/Handlers/GetEntityHandler.cs").
+ ///
+ public static string RootPath => TestSolutionPath;
+
public static string[] MediatR { get; } = LibraryFiles("MediatR");
public static string[] Mediator { get; } = LibraryFiles("Mediator");
diff --git a/src/dotnet/MediatorPlugin.Tests/LookupTestBase.cs b/src/dotnet/MediatorPlugin.Tests/LookupTestBase.cs
index 3bcc55e..609686b 100644
--- a/src/dotnet/MediatorPlugin.Tests/LookupTestBase.cs
+++ b/src/dotnet/MediatorPlugin.Tests/LookupTestBase.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using JetBrains.Lifetimes;
using JetBrains.ProjectModel;
@@ -9,6 +10,7 @@
using JetBrains.ReSharper.Resources.Shell;
using JetBrains.ReSharper.TestFramework;
using NUnit.Framework;
+using Octelys.MediatorPlugin.Tests.Infrastructure;
using ReSharper.MediatorPlugin.Services.Libraries;
namespace Octelys.MediatorPlugin.Tests;
@@ -23,6 +25,7 @@ public abstract class LookupTestBase : BaseTestWithSingleProject
{
private string _requestTypeName;
private string[] _expectedHandlerNames;
+ private string[] _expectedHandlerFilePaths;
protected override string RelativeTestDataPath => "TestSolution";
@@ -42,7 +45,18 @@ ISolution solution
{
IIdentifier requestIdentifier = FindDeclarationIdentifier(solution, _requestTypeName);
- IEnumerable handlers = new LibraryAdaptor().FindHandlers(requestIdentifier);
+ ITypeElement[] handlers = new LibraryAdaptor().FindHandlers(requestIdentifier).ToArray();
+
+ if (_expectedHandlerFilePaths is not null)
+ {
+ string[] handlerFilePaths = handlers
+ .SelectMany(GetRelativeFilePaths)
+ .OrderBy(path => path)
+ .ToArray();
+
+ Assert.That(handlerFilePaths, Is.EqualTo(_expectedHandlerFilePaths.OrderBy(path => path).ToArray()));
+ return;
+ }
string[] handlerNames = handlers
.Select(handler => handler.ShortName)
@@ -66,10 +80,49 @@ params string[] fileNames
{
_requestTypeName = requestTypeName;
_expectedHandlerNames = expectedHandlerNames;
+ _expectedHandlerFilePaths = null;
+
+ DoTestSolution(fileNames);
+ }
+
+ ///
+ /// Loads the given files as a test solution and asserts that the request or notification
+ /// resolves to handlers declared in exactly the expected files (relative to
+ /// test/data/TestSolution, e.g. "MediatR/Handlers/GetEntityHandler.cs") - i.e. the file(s)
+ /// "Go to Handler" would navigate to when the caret sits on the request. The lookup itself
+ /// runs in .
+ ///
+ protected void AssertHandlerFilesFor
+ (
+ string requestTypeName,
+ string[] expectedHandlerFilePaths,
+ params string[] fileNames
+ )
+ {
+ _requestTypeName = requestTypeName;
+ _expectedHandlerNames = null;
+ _expectedHandlerFilePaths = expectedHandlerFilePaths;
DoTestSolution(fileNames);
}
+ ///
+ /// Resolves the on-disk file(s) declaring the given handler back to their path relative to
+ /// test/data/TestSolution, matching the relative path form used to load test files (e.g. via
+ /// ).
+ ///
+ private static IEnumerable GetRelativeFilePaths
+ (
+ ITypeElement handler
+ )
+ {
+ return handler
+ .GetSourceFiles()
+ .Select(sourceFile => sourceFile.GetLocation().FullPath)
+ .Select(fullPath => fullPath.Substring(TestSolutionFiles.RootPath.Length + 1))
+ .Select(relativePath => relativePath.Replace(Path.DirectorySeparatorChar, '/'));
+ }
+
///
/// Returns the name token of the type declaration, which is the node the plugin receives
/// when the caret sits on a request in the editor. may be a
diff --git a/src/dotnet/MediatorPlugin.Tests/MediatR.GoToHandler.CaretTests.V12.cs b/src/dotnet/MediatorPlugin.Tests/MediatR.GoToHandler.CaretTests.V12.cs
new file mode 100644
index 0000000..fb84666
--- /dev/null
+++ b/src/dotnet/MediatorPlugin.Tests/MediatR.GoToHandler.CaretTests.V12.cs
@@ -0,0 +1,8 @@
+using Octelys.MediatorPlugin.Tests.Infrastructure;
+
+namespace Octelys.MediatorPlugin.Tests;
+
+[MediatRV12TestPackages]
+public sealed class MediatrGoToHandlerCaretTestsV12 : MediatrGoToHandlerCaretTestsBase
+{
+}
diff --git a/src/dotnet/MediatorPlugin.Tests/MediatR.GoToHandler.CaretTests.V13.cs b/src/dotnet/MediatorPlugin.Tests/MediatR.GoToHandler.CaretTests.V13.cs
new file mode 100644
index 0000000..d609537
--- /dev/null
+++ b/src/dotnet/MediatorPlugin.Tests/MediatR.GoToHandler.CaretTests.V13.cs
@@ -0,0 +1,8 @@
+using Octelys.MediatorPlugin.Tests.Infrastructure;
+
+namespace Octelys.MediatorPlugin.Tests;
+
+[MediatRV13TestPackages]
+public sealed class MediatrGoToHandlerCaretTestsV13 : MediatrGoToHandlerCaretTestsBase
+{
+}
diff --git a/src/dotnet/MediatorPlugin.Tests/MediatR.GoToHandler.CaretTests.V14.cs b/src/dotnet/MediatorPlugin.Tests/MediatR.GoToHandler.CaretTests.V14.cs
new file mode 100644
index 0000000..7049d62
--- /dev/null
+++ b/src/dotnet/MediatorPlugin.Tests/MediatR.GoToHandler.CaretTests.V14.cs
@@ -0,0 +1,8 @@
+using Octelys.MediatorPlugin.Tests.Infrastructure;
+
+namespace Octelys.MediatorPlugin.Tests;
+
+[MediatRV14TestPackages]
+public sealed class MediatrGoToHandlerCaretTestsV14 : MediatrGoToHandlerCaretTestsBase
+{
+}
diff --git a/src/dotnet/MediatorPlugin.Tests/MediatR.GoToHandler.CaretTests.cs b/src/dotnet/MediatorPlugin.Tests/MediatR.GoToHandler.CaretTests.cs
new file mode 100644
index 0000000..9d400a3
--- /dev/null
+++ b/src/dotnet/MediatorPlugin.Tests/MediatR.GoToHandler.CaretTests.cs
@@ -0,0 +1,84 @@
+using System.IO;
+using System.Linq;
+using JetBrains.Lifetimes;
+using JetBrains.ProjectModel;
+using JetBrains.ReSharper.Feature.Services.Util;
+using JetBrains.ReSharper.Psi;
+using JetBrains.ReSharper.Psi.Tree;
+using JetBrains.ReSharper.Resources.Shell;
+using JetBrains.ReSharper.TestFramework;
+using JetBrains.TextControl;
+using NUnit.Framework;
+using ReSharper.MediatorPlugin.Services.Libraries;
+
+namespace Octelys.MediatorPlugin.Tests;
+
+///
+/// Positions a real caret (via the "{caret}" marker in each test's data file) and asserts which
+/// handler file the plugin resolves, if any, for the identifier under it. Unlike ,
+/// which locates the request's declaration identifier directly through the PSI, this test goes
+/// through the same caret-to-PSI resolution () that the plugin's
+/// own actions use (e.g. GoToHandlerAction.GetSelectedMediatrRequest), so it exercises the literal
+/// "caret sits on this token" scenario rather than simulating it by name.
+/// and place the caret on identifiers that are not a Mediator request
+/// declaration and assert that no handler is resolved.
+///
+[TestNetCoreLatest]
+[TestReferences("System.Runtime")]
+public abstract class MediatrGoToHandlerCaretTestsBase : BaseTestWithTextControl
+{
+ private static readonly string[] TestsExpectingNoHandler =
+ {
+ nameof(CaretInNamespace),
+ nameof(CaretOnSendCall)
+ };
+
+ protected override string RelativeTestDataPath => "GoToHandlerCaretTests";
+
+ [Test]
+ public void Request() => DoNamedTest("Handler.cs");
+
+ [Test]
+ public void RequestCaretAtBeginning() => DoNamedTest("Handler.cs");
+
+ [Test]
+ public void CaretInNamespace() => DoNamedTest("Handler.cs");
+
+ [Test]
+ public void CaretOnSendCall() => DoNamedTest("Handler.cs");
+
+ protected override void DoTest
+ (
+ Lifetime lifetime,
+ ISolution solution
+ )
+ {
+ solution.GetPsiServices().Files.CommitAllDocuments();
+
+ using (ReadLockCookie.Create())
+ {
+ ITextControl textControl = OpenTextControl(lifetime, GetCaretPosition());
+
+ IIdentifier requestIdentifier = TextControlToPsi.GetElement(solution, textControl);
+
+ Assert.That(requestIdentifier, Is.Not.Null, "No identifier was found at the caret.");
+
+ ITypeElement[] handlers = new LibraryAdaptor().FindHandlers(requestIdentifier).ToArray();
+
+ if (TestsExpectingNoHandler.Contains(TestMethodName))
+ {
+ Assert.That(handlers, Is.Empty, "Expected no handler to be resolved from this caret position.");
+
+ return;
+ }
+
+ string handlerFileName = handlers
+ .Single()
+ .GetSourceFiles()
+ .Select(sourceFile => Path.GetFileName(sourceFile.GetLocation().FullPath))
+ .Single();
+
+ Assert.That(handlerFileName, Is.EqualTo("Handler.cs"));
+ }
+ }
+}
diff --git a/src/dotnet/MediatorPlugin.Tests/MediatR.Requests.Tests.cs b/src/dotnet/MediatorPlugin.Tests/MediatR.Requests.Tests.cs
index 5232371..f902267 100644
--- a/src/dotnet/MediatorPlugin.Tests/MediatR.Requests.Tests.cs
+++ b/src/dotnet/MediatorPlugin.Tests/MediatR.Requests.Tests.cs
@@ -115,4 +115,16 @@ public void FindHandlers_HandlerImplementsSharedGenericHandlerInterface_HandlerR
TestSolutionFiles.MediatR
);
}
+
+ [Test]
+ public void FindHandlers_CaretOnRequest_HandlerFileReturned()
+ {
+ // Act & Assert.
+ AssertHandlerFilesFor
+ (
+ "Domain.GetEntityRequest",
+ ["MediatR/Handlers/GetEntityHandler.cs"],
+ TestSolutionFiles.MediatR
+ );
+ }
}
\ No newline at end of file
diff --git a/src/dotnet/MediatorPlugin.Tests/MediatorPlugin.Tests.csproj b/src/dotnet/MediatorPlugin.Tests/MediatorPlugin.Tests.csproj
index f502f60..0baf2db 100644
--- a/src/dotnet/MediatorPlugin.Tests/MediatorPlugin.Tests.csproj
+++ b/src/dotnet/MediatorPlugin.Tests/MediatorPlugin.Tests.csproj
@@ -52,6 +52,15 @@
Mediator.Requests.Tests.cs
+
+ MediatR.GoToHandler.CaretTests.cs
+
+
+ MediatR.GoToHandler.CaretTests.cs
+
+
+ MediatR.GoToHandler.CaretTests.cs
+
diff --git a/src/dotnet/MediatorPlugin.Tests/test/data/GoToHandlerCaretTests/CaretInNamespace.cs b/src/dotnet/MediatorPlugin.Tests/test/data/GoToHandlerCaretTests/CaretInNamespace.cs
new file mode 100644
index 0000000..17f285b
--- /dev/null
+++ b/src/dotnet/MediatorPlugin.Tests/test/data/GoToHandlerCaretTests/CaretInNamespace.cs
@@ -0,0 +1,7 @@
+using MediatR;
+
+namespace {caret}Domain;
+
+public class GetEntityRequest : IRequest
+{
+}
diff --git a/src/dotnet/MediatorPlugin.Tests/test/data/GoToHandlerCaretTests/CaretOnSendCall.cs b/src/dotnet/MediatorPlugin.Tests/test/data/GoToHandlerCaretTests/CaretOnSendCall.cs
new file mode 100644
index 0000000..1f97e88
--- /dev/null
+++ b/src/dotnet/MediatorPlugin.Tests/test/data/GoToHandlerCaretTests/CaretOnSendCall.cs
@@ -0,0 +1,24 @@
+using System.Threading;
+using System.Threading.Tasks;
+using MediatR;
+
+namespace Domain;
+
+public class GetEntityRequest : IRequest
+{
+}
+
+public class GetEntityRequestSender
+{
+ private readonly IMediator _mediator;
+
+ public GetEntityRequestSender(IMediator mediator)
+ {
+ _mediator = mediator;
+ }
+
+ public Task SendAsync(CancellationToken cancellationToken)
+ {
+ return _mediator.{caret}Send(new GetEntityRequest(), cancellationToken);
+ }
+}
diff --git a/src/dotnet/MediatorPlugin.Tests/test/data/GoToHandlerCaretTests/Handler.cs b/src/dotnet/MediatorPlugin.Tests/test/data/GoToHandlerCaretTests/Handler.cs
new file mode 100644
index 0000000..341c369
--- /dev/null
+++ b/src/dotnet/MediatorPlugin.Tests/test/data/GoToHandlerCaretTests/Handler.cs
@@ -0,0 +1,14 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Domain;
+using MediatR;
+
+namespace Application;
+
+internal sealed class GetEntityRequestHandler : IRequestHandler
+{
+ public Task Handle(GetEntityRequest request, CancellationToken cancellationToken)
+ {
+ throw new System.NotImplementedException();
+ }
+}
diff --git a/src/dotnet/MediatorPlugin.Tests/test/data/GoToHandlerCaretTests/Request.cs b/src/dotnet/MediatorPlugin.Tests/test/data/GoToHandlerCaretTests/Request.cs
new file mode 100644
index 0000000..f087915
--- /dev/null
+++ b/src/dotnet/MediatorPlugin.Tests/test/data/GoToHandlerCaretTests/Request.cs
@@ -0,0 +1,7 @@
+using MediatR;
+
+namespace Domain;
+
+public class GetEntity{caret}Request : IRequest
+{
+}
diff --git a/src/dotnet/MediatorPlugin.Tests/test/data/GoToHandlerCaretTests/RequestCaretAtBeginning.cs b/src/dotnet/MediatorPlugin.Tests/test/data/GoToHandlerCaretTests/RequestCaretAtBeginning.cs
new file mode 100644
index 0000000..afde079
--- /dev/null
+++ b/src/dotnet/MediatorPlugin.Tests/test/data/GoToHandlerCaretTests/RequestCaretAtBeginning.cs
@@ -0,0 +1,7 @@
+using MediatR;
+
+namespace Domain;
+
+public class {caret}GetEntityRequest : IRequest
+{
+}
diff --git a/test-solution/DotNetCore6Project/MediatR/MediatRCallsite.cs b/test-solution/DotNetCore6Project/MediatR/MediatRCallsite.cs
new file mode 100644
index 0000000..b47912d
--- /dev/null
+++ b/test-solution/DotNetCore6Project/MediatR/MediatRCallsite.cs
@@ -0,0 +1,19 @@
+using DotNetCore6_Domain.MediatR.Requests;
+using MediatR;
+
+namespace DotNetCore6_Domain.MediatR;
+
+public class MediatRCallsite
+{
+ private readonly IMediator _mediator;
+
+ public MediatRCallsite(IMediator mediator)
+ {
+ _mediator = mediator;
+ }
+
+ public async Task RunAsync(CancellationToken cancellationToken)
+ {
+ await _mediator.Send(new CustomClassRequest(), cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/test-solution/DotNetCore6Project/Mediator/MediatorCallsite.cs b/test-solution/DotNetCore6Project/Mediator/MediatorCallsite.cs
new file mode 100644
index 0000000..73d81d8
--- /dev/null
+++ b/test-solution/DotNetCore6Project/Mediator/MediatorCallsite.cs
@@ -0,0 +1,19 @@
+using DotNetCore6_Domain.Mediator.Requests;
+using MediatR;
+
+namespace DotNetCore6_Domain.Mediator;
+
+public class MediatorCallsite
+{
+ private readonly IMediator _mediator;
+
+ public MediatorCallsite(IMediator mediator)
+ {
+ _mediator = mediator;
+ }
+
+ public async Task RunAsync(CancellationToken cancellationToken)
+ {
+ await _mediator.Send(new NewMediatorRequest(), cancellationToken);
+ }
+}
\ No newline at end of file