Skip to content

Commit a1ac1b9

Browse files
SLVS-2573 Split ReportViewModel into multiple classes (#6423)
1 parent 2c66794 commit a1ac1b9

File tree

8 files changed

+534
-284
lines changed

8 files changed

+534
-284
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
using System.Windows;
2+
using SonarLint.VisualStudio.Core;
3+
using SonarLint.VisualStudio.Core.Analysis;
4+
using SonarLint.VisualStudio.IssueVisualization.Security.DependencyRisks;
5+
using SonarLint.VisualStudio.TestInfrastructure;
6+
7+
namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.DependencyRisks;
8+
9+
[TestClass]
10+
public class DependencyRisksReportViewModelTest
11+
{
12+
private IDependencyRisksStore dependencyRisksStore;
13+
private IShowDependencyRiskInBrowserHandler showDependencyRiskInBrowserHandler;
14+
private IChangeDependencyRiskStatusHandler changeDependencyRiskStatusHandler;
15+
private IMessageBox messageBox;
16+
private DependencyRisksReportViewModel testSubject;
17+
18+
[TestInitialize]
19+
public void Initialize()
20+
{
21+
dependencyRisksStore = Substitute.For<IDependencyRisksStore>();
22+
showDependencyRiskInBrowserHandler = Substitute.For<IShowDependencyRiskInBrowserHandler>();
23+
changeDependencyRiskStatusHandler = Substitute.For<IChangeDependencyRiskStatusHandler>();
24+
messageBox = Substitute.For<IMessageBox>();
25+
26+
testSubject = new DependencyRisksReportViewModel(dependencyRisksStore, showDependencyRiskInBrowserHandler, changeDependencyRiskStatusHandler, messageBox);
27+
}
28+
29+
[TestMethod]
30+
public void MefCtor_CheckIsExported() =>
31+
MefTestHelpers.CheckTypeCanBeImported<DependencyRisksReportViewModel, IDependencyRisksReportViewModel>(
32+
MefTestHelpers.CreateExport<IDependencyRisksStore>(),
33+
MefTestHelpers.CreateExport<IShowDependencyRiskInBrowserHandler>(),
34+
MefTestHelpers.CreateExport<IChangeDependencyRiskStatusHandler>(),
35+
MefTestHelpers.CreateExport<IMessageBox>()
36+
);
37+
38+
[TestMethod]
39+
public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent<DependencyRisksReportViewModel>();
40+
41+
[TestMethod]
42+
public void Constructor_SubscribesToIssuesChanged() => dependencyRisksStore.Received().DependencyRisksChanged += Arg.Any<EventHandler>();
43+
44+
[TestMethod]
45+
public void Dispose_UnsubscribesFromIssuesChanged()
46+
{
47+
testSubject.Dispose();
48+
49+
dependencyRisksStore.Received().DependencyRisksChanged -= Arg.Any<EventHandler>();
50+
}
51+
52+
[TestMethod]
53+
public void ShowDependencyRiskInBrowser_CallsHandler()
54+
{
55+
var riskId = Guid.NewGuid();
56+
var dependencyRisk = CreateDependencyRisk(riskId);
57+
58+
testSubject.ShowDependencyRiskInBrowser(dependencyRisk);
59+
60+
showDependencyRiskInBrowserHandler.Received(1).ShowInBrowser(riskId);
61+
}
62+
63+
[TestMethod]
64+
public async Task ChangeDependencyRiskStatusAsync_CallsHandler_Success()
65+
{
66+
var riskId = Guid.NewGuid();
67+
var dependencyRisk = CreateDependencyRisk(riskId);
68+
var transition = DependencyRiskTransition.Accept;
69+
var comment = "test comment";
70+
changeDependencyRiskStatusHandler.ChangeStatusAsync(riskId, transition, comment).Returns(true);
71+
72+
await testSubject.ChangeDependencyRiskStatusAsync(dependencyRisk, transition, comment);
73+
74+
await changeDependencyRiskStatusHandler.Received(1).ChangeStatusAsync(riskId, transition, comment);
75+
messageBox.DidNotReceiveWithAnyArgs().Show(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<MessageBoxButton>(), Arg.Any<MessageBoxImage>());
76+
}
77+
78+
[TestMethod]
79+
public async Task ChangeDependencyRiskStatusAsync_CallsHandler_Failure_ShowsMessageBox()
80+
{
81+
var riskId = Guid.NewGuid();
82+
var dependencyRisk = CreateDependencyRisk(riskId);
83+
const DependencyRiskTransition transition = DependencyRiskTransition.Accept;
84+
const string comment = "test comment";
85+
changeDependencyRiskStatusHandler.ChangeStatusAsync(riskId, transition, comment).Returns(false);
86+
87+
await testSubject.ChangeDependencyRiskStatusAsync(dependencyRisk, transition, comment);
88+
89+
await changeDependencyRiskStatusHandler.Received(1).ChangeStatusAsync(riskId, transition, comment);
90+
messageBox.Received(1).Show(Resources.DependencyRiskStatusChangeFailedTitle, Resources.DependencyRiskStatusChangeError, MessageBoxButton.OK, MessageBoxImage.Error);
91+
}
92+
93+
[TestMethod]
94+
public async Task ChangeDependencyRiskStatusAsync_NullTransition_DoesNotCallHandler_ShowsMessageBox()
95+
{
96+
var dependencyRisk = CreateDependencyRisk();
97+
DependencyRiskTransition? transition = null;
98+
const string comment = "test comment";
99+
100+
await testSubject.ChangeDependencyRiskStatusAsync(dependencyRisk, transition, comment);
101+
102+
await changeDependencyRiskStatusHandler.DidNotReceiveWithAnyArgs().ChangeStatusAsync(Arg.Any<Guid>(), Arg.Any<DependencyRiskTransition>(), Arg.Any<string>());
103+
messageBox.Received(1).Show(Resources.DependencyRiskStatusChangeFailedTitle, Resources.DependencyRiskNullTransitionError, MessageBoxButton.OK, MessageBoxImage.Error);
104+
}
105+
106+
[TestMethod]
107+
public void GetDependencyRisksGroup_ReturnsGroup_WhenNotFixedRisksExist()
108+
{
109+
var risk1 = CreateDependencyRisk();
110+
var risk2 = CreateDependencyRisk(isFixed: true);
111+
var risk3 = CreateDependencyRisk();
112+
dependencyRisksStore.GetAll().Returns([risk1, risk2, risk3]);
113+
114+
var group = testSubject.GetDependencyRisksGroup();
115+
116+
group.Should().NotBeNull();
117+
group.FilteredIssues.Should().HaveCount(2);
118+
group.FilteredIssues.Should().AllBeOfType<DependencyRiskViewModel>();
119+
group.FilteredIssues.Cast<DependencyRiskViewModel>().Select(vm => vm.DependencyRisk).Should().Contain([risk1, risk3]);
120+
}
121+
122+
[TestMethod]
123+
public void GetDependencyRisksGroup_ReturnsNull_WhenAllRisksAreFixed()
124+
{
125+
var fixedRisk = CreateDependencyRisk(isFixed: true);
126+
var fixedRisk2 = CreateDependencyRisk(isFixed: true);
127+
dependencyRisksStore.GetAll().Returns([fixedRisk, fixedRisk2]);
128+
129+
var group = testSubject.GetDependencyRisksGroup();
130+
131+
group.Should().BeNull();
132+
}
133+
134+
[TestMethod]
135+
public void GetDependencyRisksGroup_ReturnsNull_WhenNoRisks()
136+
{
137+
dependencyRisksStore.GetAll().Returns([]);
138+
139+
var group = testSubject.GetDependencyRisksGroup();
140+
141+
group.Should().BeNull();
142+
}
143+
144+
[TestMethod]
145+
public void DependencyRisksChanged_RaisedOnStoreIssuesChanged()
146+
{
147+
var raised = false;
148+
testSubject.DependencyRisksChanged += (_, _) => raised = true;
149+
150+
dependencyRisksStore.DependencyRisksChanged += Raise.Event<EventHandler>(null, null);
151+
152+
raised.Should().BeTrue();
153+
}
154+
155+
private static IDependencyRisk CreateDependencyRisk(Guid? id = null, bool isFixed = false)
156+
{
157+
var risk = Substitute.For<IDependencyRisk>();
158+
risk.Id.Returns(id ?? Guid.NewGuid());
159+
risk.Transitions.Returns([]);
160+
risk.Status.Returns(isFixed ? DependencyRiskStatus.Fixed : DependencyRiskStatus.Open);
161+
return risk;
162+
}
163+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* SonarLint for Visual Studio
3+
* Copyright (C) 2016-2025 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
21+
using SonarLint.VisualStudio.Core.Analysis;
22+
using SonarLint.VisualStudio.IssueVisualization.Models;
23+
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots;
24+
using SonarLint.VisualStudio.IssueVisualization.Security.IssuesStore;
25+
using SonarLint.VisualStudio.IssueVisualization.Security.ReportView;
26+
using SonarLint.VisualStudio.IssueVisualization.Security.ReportView.Hotspots;
27+
using SonarLint.VisualStudio.TestInfrastructure;
28+
29+
namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.ReportView.Hotspots;
30+
31+
[TestClass]
32+
public class HotspotsReportViewModelTest
33+
{
34+
private ILocalHotspotsStore localHotspotsStore;
35+
private HotspotsReportViewModel testSubject;
36+
37+
[TestInitialize]
38+
public void TestInitialize()
39+
{
40+
localHotspotsStore = Substitute.For<ILocalHotspotsStore>();
41+
testSubject = new HotspotsReportViewModel(localHotspotsStore);
42+
}
43+
44+
[TestMethod]
45+
public void MefCtor_CheckIsExported() =>
46+
MefTestHelpers.CheckTypeCanBeImported<HotspotsReportViewModel, IHotspotsReportViewModel>(
47+
MefTestHelpers.CreateExport<ILocalHotspotsStore>());
48+
49+
[TestMethod]
50+
public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent<HotspotsReportViewModel>();
51+
52+
[TestMethod]
53+
public void Constructor_SubscribesToIssuesChanged() => localHotspotsStore.Received().IssuesChanged += Arg.Any<EventHandler<IssuesChangedEventArgs>>();
54+
55+
[TestMethod]
56+
public void Dispose_UnsubscribesFromIssuesChanged()
57+
{
58+
testSubject.Dispose();
59+
60+
localHotspotsStore.Received().IssuesChanged -= Arg.Any<EventHandler<IssuesChangedEventArgs>>();
61+
}
62+
63+
[TestMethod]
64+
public void GetHotspotsGroupViewModels_GroupsByFilePath()
65+
{
66+
var file1 = "file1.cs";
67+
var file2 = "file2.cs";
68+
MockHotspotsInStore(CreateMockedHotspot(file1), CreateMockedHotspot(file1), CreateMockedHotspot(file2));
69+
70+
var groups = testSubject.GetHotspotsGroupViewModels();
71+
72+
groups.Should().HaveCount(2);
73+
groups.Select(g => g.Title).Should().Contain([file1, file2]);
74+
groups.First(g => g.Title == file1).FilteredIssues.Should().HaveCount(2);
75+
groups.First(g => g.Title == file2).FilteredIssues.Should().ContainSingle();
76+
}
77+
78+
[TestMethod]
79+
public void GetHotspotsGroupViewModels_TwoHotspotsInSameFile_CreatesOneGroupVmWithTwoIssues()
80+
{
81+
var path = "myFile.cs";
82+
var hotspot1 = CreateMockedHotspot(path);
83+
var hotspot2 = CreateMockedHotspot(path);
84+
MockHotspotsInStore(hotspot1, hotspot2);
85+
86+
var groups = testSubject.GetHotspotsGroupViewModels();
87+
88+
groups.Should().ContainSingle();
89+
VerifyExpectedHotspotGroupViewModel(groups[0] as GroupFileViewModel, hotspot1, hotspot2);
90+
}
91+
92+
[TestMethod]
93+
public void GetHotspotsGroupViewModels_TwoHotspotsInDifferentFiles_CreatesTwoGroupsWithOneIssueEach()
94+
{
95+
var hotspot1 = CreateMockedHotspot("myFile.cs");
96+
var hotspot2 = CreateMockedHotspot("myFile.js");
97+
MockHotspotsInStore(hotspot1, hotspot2);
98+
99+
var groups = testSubject.GetHotspotsGroupViewModels();
100+
101+
groups.Should().HaveCount(2);
102+
VerifyExpectedHotspotGroupViewModel(groups[0] as GroupFileViewModel, hotspot1);
103+
VerifyExpectedHotspotGroupViewModel(groups[1] as GroupFileViewModel, hotspot2);
104+
}
105+
106+
[TestMethod]
107+
public void HotspotsChanged_RaisedOnStoreIssuesChanged()
108+
{
109+
var raised = false;
110+
testSubject.HotspotsChanged += (_, _) => raised = true;
111+
112+
localHotspotsStore.IssuesChanged += Raise.Event<EventHandler<IssuesChangedEventArgs>>(null, null);
113+
114+
raised.Should().BeTrue();
115+
}
116+
117+
private static LocalHotspot CreateMockedHotspot(string filePath)
118+
{
119+
var analysisIssueVisualization = Substitute.For<IAnalysisIssueVisualization>();
120+
var analysisIssueBase = Substitute.For<IAnalysisIssueBase>();
121+
analysisIssueBase.PrimaryLocation.FilePath.Returns(filePath);
122+
analysisIssueVisualization.Issue.Returns(analysisIssueBase);
123+
124+
return new LocalHotspot(analysisIssueVisualization, default, default);
125+
}
126+
127+
private void MockHotspotsInStore(params LocalHotspot[] hotspots) => localHotspotsStore.GetAllLocalHotspots().Returns(hotspots);
128+
129+
private static void VerifyExpectedHotspotGroupViewModel(GroupFileViewModel groupFileVm, params LocalHotspot[] expectedHotspots)
130+
{
131+
groupFileVm.Should().NotBeNull();
132+
groupFileVm.FilePath.Should().Be(expectedHotspots[0].Visualization.Issue.PrimaryLocation.FilePath);
133+
groupFileVm.FilteredIssues.Should().HaveCount(expectedHotspots.Length);
134+
foreach (var expectedHotspot in expectedHotspots)
135+
{
136+
groupFileVm.FilteredIssues.Should().ContainSingle(vm => ((HotspotViewModel)vm).LocalHotspot == expectedHotspot);
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)