Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MMF-3915 Open a fix suggestion from SonarCloud into the IDE - part 1 #5733

Merged
merged 8 commits into from
Oct 9, 2024
41 changes: 41 additions & 0 deletions src/ConnectedMode.UnitTests/UI/ConnectedModeUIManagerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using SonarLint.VisualStudio.ConnectedMode.UI;
using SonarLint.VisualStudio.TestInfrastructure;

namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.UI
{
[TestClass]
public class ConnectedModeUIManagerTests
{
[TestMethod]
public void MefCtor_CheckIsExported()
{
MefTestHelpers.CheckTypeCanBeImported<ConnectedModeUIManager, IConnectedModeUIManager>(
MefTestHelpers.CreateExport<IConnectedModeServices>(),
MefTestHelpers.CreateExport<IConnectedModeBindingServices>());
}

[TestMethod]
public void MefCtor_CheckIsNonShared()
=> MefTestHelpers.CheckIsNonSharedMefComponent<ConnectedModeUIManager>();
}
}
53 changes: 53 additions & 0 deletions src/ConnectedMode/UI/ConnectedModeUIManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.ComponentModel.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Windows;
using SonarLint.VisualStudio.ConnectedMode.UI.ManageBinding;

namespace SonarLint.VisualStudio.ConnectedMode.UI;

public interface IConnectedModeUIManager
{
void ShowManageBindingDialog(bool useSharedBindingOnInitialization = false);
}

[Export(typeof(IConnectedModeUIManager))]
[PartCreationPolicy(CreationPolicy.NonShared)]
internal sealed class ConnectedModeUIManager : IConnectedModeUIManager
{
private readonly IConnectedModeServices connectedModeServices;
private readonly IConnectedModeBindingServices connectedModeBindingServices;

[ImportingConstructor]
public ConnectedModeUIManager(IConnectedModeServices connectedModeServices, IConnectedModeBindingServices connectedModeBindingServices)
{
this.connectedModeServices = connectedModeServices;
this.connectedModeBindingServices = connectedModeBindingServices;
}

[ExcludeFromCodeCoverage] // UI, not really unit-testable
public void ShowManageBindingDialog(bool useSharedBindingOnInitialization = false)
{
var manageBindingDialog = new ManageBindingDialog(connectedModeServices, connectedModeBindingServices, useSharedBindingOnInitialization);
manageBindingDialog.ShowDialog(Application.Current.MainWindow);
}
}
2 changes: 1 addition & 1 deletion src/EmbeddedSonarAnalyzer.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
<EmbeddedSonarJSAnalyzerVersion>10.14.0.26080</EmbeddedSonarJSAnalyzerVersion>
<EmbeddedSonarSecretsJarVersion>2.15.0.3845</EmbeddedSonarSecretsJarVersion>
<!-- SLOOP: Binaries for SonarLint Out Of Process -->
<EmbeddedSloopVersion>10.6.0.79033</EmbeddedSloopVersion>
<EmbeddedSloopVersion>10.7.1.79146</EmbeddedSloopVersion>
</PropertyGroup>
</Project>
65 changes: 34 additions & 31 deletions src/Integration.UnitTests/Binding/BindingSuggestionHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,44 @@
*/

using SonarLint.VisualStudio.ConnectedMode.Binding.Suggestion;
using SonarLint.VisualStudio.ConnectedMode.UI;
using SonarLint.VisualStudio.Core.Notifications;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.TestInfrastructure;
using SonarLint.VisualStudio.Core.Binding;
using SonarLint.VisualStudio.Integration.TeamExplorer;
using SonarLint.VisualStudio.Integration.Binding;

namespace SonarLint.VisualStudio.Integration.UnitTests.Binding;

[TestClass]
public class BindingSuggestionHandlerTests
{
private BindingSuggestionHandler testSubject;
private INotificationService notificationService;
private IActiveSolutionBoundTracker activeSolutionBoundTracker;
private IIDEWindowService ideWindowService;
private IConnectedModeUIManager connectedModeManager;
private IBrowserService browserService;

[TestInitialize]
public void TestInitialize()
{
notificationService = Substitute.For<INotificationService>();
activeSolutionBoundTracker = Substitute.For<IActiveSolutionBoundTracker>();
ideWindowService = Substitute.For<IIDEWindowService>();
connectedModeManager = Substitute.For<IConnectedModeUIManager>();
browserService = Substitute.For<IBrowserService>();
testSubject = new BindingSuggestionHandler(notificationService, activeSolutionBoundTracker, ideWindowService, connectedModeManager, browserService);
}

[TestMethod]
public void MefCtor_CheckExports()
{
MefTestHelpers.CheckTypeCanBeImported<BindingSuggestionHandler, IBindingSuggestionHandler>(
MefTestHelpers.CreateExport<INotificationService>(),
MefTestHelpers.CreateExport<IActiveSolutionBoundTracker>(),
MefTestHelpers.CreateExport<IIDEWindowService>(),
MefTestHelpers.CreateExport<ITeamExplorerController>(),
MefTestHelpers.CreateExport<IConnectedModeUIManager>(),
MefTestHelpers.CreateExport<IBrowserService>());
}

Expand All @@ -47,9 +65,8 @@ public void MefCtor_CheckExports()
[DataRow(SonarLintMode.Connected)]
public void Notify_BringsWindowToFront(SonarLintMode sonarLintMode)
{
var ideWindowService = Substitute.For<IIDEWindowService>();
MockCurrentConfiguration(sonarLintMode);

var testSubject = CreateTestSubject(sonarLintMode: sonarLintMode, ideWindowService: ideWindowService);
testSubject.Notify();

ideWindowService.Received().BringToFront();
Expand All @@ -58,9 +75,8 @@ public void Notify_BringsWindowToFront(SonarLintMode sonarLintMode)
[TestMethod]
public void Notify_WithStandaloneProject_PromptsToConnect()
{
var notificationService = Substitute.For<INotificationService>();
MockCurrentConfiguration(SonarLintMode.Standalone);

var testSubject = CreateTestSubject(sonarLintMode: SonarLintMode.Standalone, notificationService: notificationService);
testSubject.Notify();

notificationService.Received().ShowNotification(Arg.Is<INotification>(
Expand All @@ -73,9 +89,8 @@ public void Notify_WithStandaloneProject_PromptsToConnect()
[TestMethod]
public void Notify_WithBoundProject_ShowsConflictMessage()
{
var notificationService = Substitute.For<INotificationService>();
MockCurrentConfiguration(SonarLintMode.Connected);

var testSubject = CreateTestSubject(sonarLintMode: SonarLintMode.Connected, notificationService: notificationService);
testSubject.Notify();

notificationService.Received().ShowNotification(Arg.Is<INotification>(
Expand All @@ -86,30 +101,28 @@ public void Notify_WithBoundProject_ShowsConflictMessage()
}

[TestMethod]
public void Notify_ConnectAction_OpensSonarQubePage()
public void Notify_ConnectAction_ShowsManageBindingDialog()
{
var notificationService = Substitute.For<INotificationService>();
var teamExplorerController = Substitute.For<ITeamExplorerController>();
MockCurrentConfiguration(SonarLintMode.Standalone);

var testSubject = CreateTestSubject(sonarLintMode: SonarLintMode.Standalone, notificationService: notificationService, teamExplorerController: teamExplorerController);
testSubject.Notify();

var notification = (Notification)notificationService.ReceivedCalls().Single().GetArguments().Single();
var connectAction = notification.Actions.First(x => x.CommandText.Equals(BindingStrings.BindingSuggestionConnect));

teamExplorerController.DidNotReceive().ShowSonarQubePage();
connectedModeManager.DidNotReceive().ShowManageBindingDialog();
connectAction.Action(notification);

teamExplorerController.Received().ShowSonarQubePage();
connectedModeManager.Received().ShowManageBindingDialog();
}

[TestMethod]
public void Notify_LearnMoreAction_OpensDocumentationInBrowser()
{
var notificationService = Substitute.For<INotificationService>();
var browserService = Substitute.For<IBrowserService>();
MockCurrentConfiguration(SonarLintMode.Standalone);

var testSubject = CreateTestSubject(sonarLintMode: SonarLintMode.Standalone, notificationService: notificationService, browserService: browserService);
testSubject.Notify();

var notification = (Notification)notificationService.ReceivedCalls().Single().GetArguments().Single();
var connectAction = notification.Actions.First(x => x.CommandText.Equals(BindingStrings.BindingSuggestionLearnMore));

Expand All @@ -119,20 +132,10 @@ public void Notify_LearnMoreAction_OpensDocumentationInBrowser()
browserService.Received().Navigate(DocumentationLinks.OpenInIdeBindingSetup);
}

private BindingSuggestionHandler CreateTestSubject(SonarLintMode sonarLintMode,
INotificationService notificationService = null,
IIDEWindowService ideWindowService = null,
ITeamExplorerController teamExplorerController = null,
IBrowserService browserService = null)
private void MockCurrentConfiguration(SonarLintMode sonarLintMode)
{
notificationService ??= Substitute.For<INotificationService>();
var activeSolutionBoundTracker = Substitute.For<IActiveSolutionBoundTracker>();
ideWindowService ??= Substitute.For<IIDEWindowService>();
teamExplorerController ??= Substitute.For<ITeamExplorerController>();
browserService ??= Substitute.For<IBrowserService>();

activeSolutionBoundTracker.CurrentConfiguration.Returns(new BindingConfiguration(new BoundServerProject("solution", "server project", new ServerConnection.SonarCloud("org")), sonarLintMode, "a-directory"));

return new BindingSuggestionHandler(notificationService, activeSolutionBoundTracker, ideWindowService, teamExplorerController, browserService);
activeSolutionBoundTracker.CurrentConfiguration.Returns(new BindingConfiguration(
new BoundServerProject("solution", "server project", new ServerConnection.SonarCloud("org")), sonarLintMode,
"a-directory"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class SharedBindingSuggestionServiceTests
private IConnectedModeServices connectedModeServices;
private IConnectedModeBindingServices connectedModeBindingServices;
private IActiveSolutionTracker activeSolutionTracker;
private IConnectedModeUIManager connectedModeManager;

[TestInitialize]
public void TestInitialize()
Expand All @@ -45,8 +46,9 @@ public void TestInitialize()
connectedModeServices = Substitute.For<IConnectedModeServices>();
connectedModeBindingServices = Substitute.For<IConnectedModeBindingServices>();
activeSolutionTracker = Substitute.For<IActiveSolutionTracker>();
connectedModeManager = Substitute.For<IConnectedModeUIManager>();

testSubject = new SharedBindingSuggestionService(suggestSharedBindingGoldBar, connectedModeServices, connectedModeBindingServices, activeSolutionTracker);
testSubject = new SharedBindingSuggestionService(suggestSharedBindingGoldBar, connectedModeServices, connectedModeBindingServices, connectedModeManager, activeSolutionTracker);
}

[TestMethod]
Expand All @@ -56,6 +58,7 @@ public void MefCtor_CheckExports()
MefTestHelpers.CreateExport<ISuggestSharedBindingGoldBar>(),
MefTestHelpers.CreateExport<IConnectedModeServices>(),
MefTestHelpers.CreateExport<IConnectedModeBindingServices>(),
MefTestHelpers.CreateExport<IConnectedModeUIManager>(),
MefTestHelpers.CreateExport<IActiveSolutionTracker>());
}

Expand Down Expand Up @@ -127,6 +130,24 @@ public void Dispose_UnsubscribesFromActiveSolutionChanged()
activeSolutionTracker.Received(1).ActiveSolutionChanged -= Arg.Any<EventHandler<ActiveSolutionChangedEventArgs>>();
}

[TestMethod]
public void ActiveSolutionChanged_SolutionIsOpened_ShowsGoldBarAndShowManageBindingDialog()
{
MockSharedBindingConfigExists();
MockSolutionMode(SonarLintMode.Standalone);
Action showAction = null;
suggestSharedBindingGoldBar.When(x => x.Show(ServerType.SonarQube, Arg.Any<Action>())).Do(callInfo =>
{
showAction = callInfo.Arg<Action>();
});

RaiseActiveSolutionChanged(true);
showAction();

showAction.Should().NotBeNull();
connectedModeManager.Received(1).ShowManageBindingDialog(true);
}

private void RaiseActiveSolutionChanged(bool isSolutionOpened)
{
activeSolutionTracker.ActiveSolutionChanged += Raise.EventWith(new ActiveSolutionChangedEventArgs(isSolutionOpened));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void ClientConstants_ShouldBeExpected()
public void FeatureFlags_ShouldBeExpected()
{
var testSubject = CreateTestSubject();
var expectedFeatureFlags = new FeatureFlagsDto(true, true, true, true, false, false, true, true);
var expectedFeatureFlags = new FeatureFlagsDto(true, true, true, true, false, false, true, true, true);
var actual = testSubject.FeatureFlags;

actual.Should().BeEquivalentTo(expectedFeatureFlags);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ public void PackageCommandManager_Initialize()
Mock.Of<IProjectPropertyManager>(),
Mock.Of<IOutputWindowService>(),
Mock.Of<IShowInBrowserService>(),
Mock.Of<IBrowserService>(),
Mock.Of<PackageCommandManager.ShowOptionsPage>(),
Mock.Of<IConnectedModeServices>(),
Mock.Of<IConnectedModeBindingServices>());
Mock.Of<IConnectedModeBindingServices>(),
Mock.Of<IConnectedModeUIManager>());

// Assert
menuService.Commands.Should().HaveCountGreaterOrEqualTo(allCommands.Count, "Unexpected number of commands");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,24 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using System.Windows;
using SonarLint.VisualStudio.ConnectedMode.UI;
using SonarLint.VisualStudio.ConnectedMode.UI.ManageBinding;

namespace SonarLint.VisualStudio.Integration.Vsix.Commands.ConnectedModeMenu
{
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal class ManageConnectionsCommand : VsCommandBase
{
private readonly IConnectedModeServices connectedModeServices;
private readonly IConnectedModeBindingServices connectedModeBindingServices;
private readonly IConnectedModeUIManager connectedModeUiManager;
internal const int Id = 0x102;

public ManageConnectionsCommand(IConnectedModeServices connectedModeServices, IConnectedModeBindingServices connectedModeBindingServices)
public ManageConnectionsCommand(IConnectedModeUIManager connectedModeUiManager)
{
this.connectedModeServices = connectedModeServices;
this.connectedModeBindingServices = connectedModeBindingServices;
this.connectedModeUiManager = connectedModeUiManager;
}

protected override void InvokeInternal()
{
new ManageBindingDialog(connectedModeServices, connectedModeBindingServices).ShowDialog(Application.Current.MainWindow);
connectedModeUiManager.ShowManageBindingDialog();
}
}
}
8 changes: 4 additions & 4 deletions src/Integration.Vsix/Commands/PackageCommandManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ public void Initialize(
IProjectPropertyManager projectPropertyManager,
IOutputWindowService outputWindowService,
IShowInBrowserService showInBrowserService,
IBrowserService browserService,
ShowOptionsPage showOptionsPage,
IConnectedModeServices connectedModeServices,
IConnectedModeBindingServices connectedModeBindingServices)
IConnectedModeBindingServices connectedModeBindingServices,
IConnectedModeUIManager connectedModeManager)
{
RegisterCommand((int)PackageCommandId.ProjectExcludePropertyToggle, new ProjectExcludePropertyToggleCommand(projectPropertyManager));
RegisterCommand((int)PackageCommandId.ProjectTestPropertyAuto, new ProjectTestPropertySetCommand(projectPropertyManager, null));
Expand All @@ -65,11 +65,11 @@ public void Initialize(
// Help menu buttons
RegisterCommand(CommonGuids.HelpMenuCommandSet, ShowLogsCommand.Id, new ShowLogsCommand(outputWindowService));
RegisterCommand(CommonGuids.HelpMenuCommandSet, ViewDocumentationCommand.Id, new ViewDocumentationCommand(showInBrowserService));
RegisterCommand(CommonGuids.HelpMenuCommandSet, AboutCommand.Id, new AboutCommand(browserService));
RegisterCommand(CommonGuids.HelpMenuCommandSet, AboutCommand.Id, new AboutCommand(connectedModeServices.BrowserService));
RegisterCommand(CommonGuids.HelpMenuCommandSet, ShowCommunityPageCommand.Id, new ShowCommunityPageCommand(showInBrowserService));

// Connected mode buttons
RegisterCommand(CommonGuids.ConnectedModeMenuCommandSet, ManageConnectionsCommand.Id, new ManageConnectionsCommand(connectedModeServices, connectedModeBindingServices));
RegisterCommand(CommonGuids.ConnectedModeMenuCommandSet, ManageConnectionsCommand.Id, new ManageConnectionsCommand(connectedModeManager));
RegisterCommand(CommonGuids.ConnectedModeMenuCommandSet, SaveSharedConnectionCommand.Id, new SaveSharedConnectionCommand(connectedModeServices.ConfigurationProvider, connectedModeBindingServices.SharedBindingConfigProvider));
}

Expand Down
4 changes: 2 additions & 2 deletions src/Integration.Vsix/SonarLintIntegrationPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ private async Task InitOnUIThreadAsync()
serviceProvider.GetMefService<IProjectPropertyManager>(),
serviceProvider.GetMefService<IOutputWindowService>(),
serviceProvider.GetMefService<IShowInBrowserService>(),
serviceProvider.GetMefService<IBrowserService>(),
ShowOptionPage,
serviceProvider.GetMefService<IConnectedModeServices>(),
serviceProvider.GetMefService<IConnectedModeBindingServices>());
serviceProvider.GetMefService<IConnectedModeBindingServices>(),
serviceProvider.GetMefService<IConnectedModeUIManager>());

this.roslynSettingsFileSynchronizer = await this.GetMefServiceAsync<IRoslynSettingsFileSynchronizer>();
roslynSettingsFileSynchronizer.UpdateFileStorageAsync().Forget(); // don't wait for it to finish
Expand Down
Loading