Skip to content

Commit b2f847a

Browse files
authored
Merge pull request #4956 from IvenBach/Issue4149_TextExplorerFilter
Add filtering to TestExplorer
2 parents f5e9c2a + 7ba5932 commit b2f847a

26 files changed

+2577
-126
lines changed

Rubberduck.CodeAnalysis/Rubberduck.CodeAnalysis.xml

Lines changed: 2352 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rubberduck.Core/UI/Controls/SearchBox.xaml.cs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
11
using NLog;
22
using Rubberduck.UI.Command;
3-
using System;
4-
using System.Collections.Generic;
5-
using System.Linq;
6-
using System.Text;
7-
using System.Threading.Tasks;
83
using System.Windows;
94
using System.Windows.Controls;
10-
using System.Windows.Data;
11-
using System.Windows.Documents;
125
using System.Windows.Input;
13-
using System.Windows.Media;
14-
using System.Windows.Media.Imaging;
15-
using System.Windows.Navigation;
16-
using System.Windows.Shapes;
176

187
namespace Rubberduck.UI.Controls
198
{
@@ -34,10 +23,10 @@ private static void PropertyChangedCallback(DependencyObject source, DependencyP
3423
var newValue = (string)e.NewValue;
3524
switch (e.Property.Name)
3625
{
37-
case "Text":
26+
case nameof(Text):
3827
control.Text = newValue;
3928
break;
40-
case "Hint":
29+
case nameof(Hint):
4130
control.Hint = newValue;
4231
break;
4332
}
@@ -65,7 +54,7 @@ public string Hint
6554
}
6655
}
6756

68-
public ICommand ClearSearchCommand { get => new DelegateCommand(LogManager.GetCurrentClassLogger(), (arg) => Text = ""); }
57+
public ICommand ClearSearchCommand => new DelegateCommand(LogManager.GetCurrentClassLogger(), (arg) => Text = "");
6958

7059
public SearchBox()
7160
{

Rubberduck.Core/UI/Inspections/InspectionResultsControl.xaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
<BitmapImage x:Key="GroupByLocationImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Custom/PNG/ObjectClass.png" />
6262
<BitmapImage x:Key="GroupBySeverityImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Custom/PNG/Severity.png" />
6363

64+
<BitmapImage x:Key="FilterImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/Funnel.png" />
6465
<BitmapImage x:Key="FilterByHintImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/information-white.png" />
6566
<BitmapImage x:Key="FilterBySuggestionImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/information.png" />
6667
<BitmapImage x:Key="FilterByWarningImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/exclamation.png" />
@@ -131,6 +132,8 @@
131132

132133
<Separator />
133134

135+
<Image Source="{StaticResource FilterImage}" />
136+
134137
<Label Content="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=GroupingGrid_Filter}" VerticalContentAlignment="Center" />
135138

136139
<ToggleButton Style="{StaticResource ToolBarToggleStyle}"

Rubberduck.Core/UI/Settings/IndenterSettingsViewModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ public IndenterSettingsViewModel(Configuration config, IConfigurationService<Sma
4949
private void IndenterSettingsViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
5050
{
5151
// ReSharper disable once ExplicitCallerInfoArgument
52-
if (e.PropertyName != "PreviewSampleCode")
52+
if (e.PropertyName != nameof(PreviewSampleCode))
5353
{
54-
OnPropertyChanged("PreviewSampleCode");
54+
OnPropertyChanged(nameof(PreviewSampleCode));
5555
}
5656
}
5757

Rubberduck.Core/UI/Settings/InspectionSettings.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
xmlns:controls="clr-namespace:Rubberduck.UI.Controls"
1111
xmlns:themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
1212
mc:Ignorable="d"
13-
d:DesignWidth="500"
13+
d:DesignWidth="700"
1414
d:DataContext="{d:DesignInstance {x:Type settings:InspectionSettingsViewModel}, IsDesignTimeCreatable=False}">
1515
<UserControl.Resources>
1616
<converters:CodeInspectionSeverityEnumToTextConverter x:Key="CodeInspectionSeverityEnumToText" />

Rubberduck.Core/UI/UnitTesting/TestExplorerControl.xaml

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66
xmlns:local="clr-namespace:Rubberduck.UI.UnitTesting"
77
xmlns:vm="clr-namespace:Rubberduck.UI.UnitTesting.ViewModels"
88
xmlns:controls="clr-namespace:Rubberduck.UI.Controls"
9-
xmlns:themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
109
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
1110
xmlns:converters="clr-namespace:Rubberduck.UI.Converters"
1211
Language="{UICulture}"
1312
mc:Ignorable="d"
14-
d:DesignHeight="400" d:DesignWidth="600"
13+
d:DesignHeight="400" d:DesignWidth="800"
1514
d:DataContext="{d:DesignInstance local:TestExplorerViewModel}">
1615

1716
<UserControl.Resources>
@@ -49,6 +48,12 @@
4948

5049
<BitmapImage x:Key="CopyResultsImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/document-copy.png" />
5150

51+
<BitmapImage x:Key="FilterImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/Funnel.png" />
52+
<BitmapImage x:Key="OutcomeUnknown" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/question-white.png" />
53+
<BitmapImage x:Key="OutcomeFail" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/cross-circle.png" />
54+
<BitmapImage x:Key="OutcomeInconclusive" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/exclamation.png" />
55+
<BitmapImage x:Key="OutcomeSucceeded" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/tick-circle.png" />
56+
5257
<local:TestOutcomeImageSourceConverter x:Key="OutcomeIconConverter" />
5358
<converters:MillisecondToTimeMagnitudeConverter x:Key="FormattedTime" />
5459
<converters:InvertBoolValueConverter x:Key="InvertBoolValue" />
@@ -82,6 +87,7 @@
8287
<local:TestExplorerGroupingBooleanConverter x:Key="OutcomeConverter" />
8388
<local:TestExplorerGroupingBooleanConverter x:Key="LocationConverter" />
8489
<local:TestExplorerGroupingBooleanConverter x:Key="CategoryConverter" />
90+
<local:TestExplorerOutcomeFilterToBooleanConverter x:Key="OutcomeFilterConverter" />
8591
<Style x:Key="ToolBarToggleStyle" TargetType="ToggleButton">
8692
<Setter Property="Margin" Value="2" />
8793
<Setter Property="BorderBrush" Value="{x:Static SystemColors.ActiveBorderBrush}" />
@@ -216,6 +222,36 @@
216222

217223
<Separator />
218224

225+
<Image Source="{StaticResource FilterImage}" />
226+
227+
<Label Content="{Resx Key=TestExplorer_Filter, ResxName=Rubberduck.Resources.UnitTesting.TestExplorer}" />
228+
<controls:SearchBox Width="100"
229+
Text="{Binding TestNameFilter, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" />
230+
231+
<Label Content="{Resx Key=TestExplorer_Outcome, ResxName=Rubberduck.Resources.UnitTesting.TestExplorer}" />
232+
<ToggleButton Style="{StaticResource ToolBarToggleStyle}"
233+
ToolTip="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=TestOutcome_Unknown}"
234+
IsChecked="{Binding Path=OutcomeFilter, Converter={StaticResource OutcomeFilterConverter}, ConverterParameter={x:Static local:TestExplorerOutcomeFilter.Unknown}}" >
235+
<Image Source="{StaticResource OutcomeUnknown}" />
236+
</ToggleButton>
237+
<ToggleButton Style="{StaticResource ToolBarToggleStyle}"
238+
ToolTip="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=TestOutcome_Fail}"
239+
IsChecked="{Binding Path=OutcomeFilter, Converter={StaticResource OutcomeFilterConverter}, ConverterParameter={x:Static local:TestExplorerOutcomeFilter.Fail}}" >
240+
<Image Source="{StaticResource OutcomeFail}" />
241+
</ToggleButton>
242+
<ToggleButton Style="{StaticResource ToolBarToggleStyle}"
243+
ToolTip="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=TestOutcome_Inconclusive}"
244+
IsChecked="{Binding Path=OutcomeFilter, Converter={StaticResource OutcomeFilterConverter}, ConverterParameter={x:Static local:TestExplorerOutcomeFilter.Inconclusive}}" >
245+
<Image Source="{StaticResource OutcomeInconclusive}" />
246+
</ToggleButton>
247+
<ToggleButton Style="{StaticResource ToolBarToggleStyle}"
248+
ToolTip="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=TestOutcome_Succeeded}"
249+
IsChecked="{Binding Path=OutcomeFilter, Converter={StaticResource OutcomeFilterConverter}, ConverterParameter={x:Static local:TestExplorerOutcomeFilter.Succeeded}}" >
250+
<Image Source="{StaticResource OutcomeSucceeded}" />
251+
</ToggleButton>
252+
253+
<Separator />
254+
219255
<Button Name="CollapseAll" Command="{Binding CollapseAllCommand}" Margin="2"
220256
ToolTip="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=InspectionResults_CollapseAll}">
221257
<Image Source="{StaticResource CollapseAllImage}" Style="{StaticResource ToolbarImageOpacity}" />
@@ -276,13 +312,6 @@
276312
<TextBlock Margin="2" Padding="4,2,4,2" FontWeight="Bold" VerticalAlignment="Center"
277313
Text="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestOutcome_SummaryCaption}"/>
278314
<TextBlock Style="{StaticResource ResultsTestOutcomeStyle}" Text="{Binding Model.LastTestRunSummary, Mode=OneWay}"/>
279-
<StackPanel Orientation="Horizontal" Visibility="{Binding Model.LastTestSpectacularFailCount, Mode=OneWay, Converter={StaticResource NonZeroToVisibility}}" >
280-
<Image Style="{StaticResource TestOutcomeIconStyle}" Source="{StaticResource EasterEggTestImage}" Margin="4,0,4,0"/>
281-
<TextBlock Style="{StaticResource ResultsTestOutcomeStyle}">
282-
<Run Text="{Binding Model.LastTestSpectacularFailCount, Mode=OneWay}"/>
283-
<Run Text="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestOutcome_SpectacularFail}"/>
284-
</TextBlock>
285-
</StackPanel>
286315
<StackPanel Orientation="Horizontal" Visibility="{Binding Model.LastTestFailedCount, Mode=OneWay, Converter={StaticResource NonZeroToVisibility}}" >
287316
<Image Style="{StaticResource TestOutcomeIconStyle}" Source="{StaticResource RunFailedTestsImage}" Margin="4,0,4,0"/>
288317
<TextBlock Style="{StaticResource ResultsTestOutcomeStyle}">

Rubberduck.Core/UI/UnitTesting/TestExplorerGroupingBooleanConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace Rubberduck.UI.UnitTesting
66
{
77
/// <summary>
8-
/// Binds an individual flag of TestExplorerGrouping to a boolean. Note: This is a stateful converter, so each bound control
8+
/// Binds an individual flag of <see cref="TestExplorerGrouping"/> to a boolean. Note: This is a stateful converter, so each bound control
99
/// requires its own converter instance.
1010
/// </summary>
1111
internal class TestExplorerGroupingBooleanConverter : IValueConverter

Rubberduck.Core/UI/UnitTesting/TestExplorerModel.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,6 @@ public bool IsRefreshing
100100
public string LastTestRunSummary =>
101101
string.Format(Resources.UnitTesting.TestExplorer.TestOutcome_RunSummaryFormat, CurrentRunTestCount, Tests.Count, TimeSpan.FromMilliseconds(TotalDuration));
102102

103-
public int LastTestSpectacularFailCount => Tests.Count(test => test.Result.Outcome == TestOutcome.SpectacularFail && _testEngine.LastRunTests.Contains(test.Method));
104-
105103
public int LastTestFailedCount => Tests.Count(test => test.Result.Outcome == TestOutcome.Failed && _testEngine.LastRunTests.Contains(test.Method));
106104

107105
public int LastTestInconclusiveCount => Tests.Count(test => test.Result.Outcome == TestOutcome.Inconclusive && _testEngine.LastRunTests.Contains(test.Method));
@@ -181,7 +179,6 @@ private void HandleRunCompletion(object sender, TestRunCompletedEventArgs e)
181179
IsBusy = false;
182180

183181
OnPropertyChanged(nameof(LastTestRunSummary));
184-
OnPropertyChanged(nameof(LastTestSpectacularFailCount));
185182
OnPropertyChanged(nameof(LastTestFailedCount));
186183
OnPropertyChanged(nameof(LastTestIgnoredCount));
187184
OnPropertyChanged(nameof(LastTestInconclusiveCount));
@@ -246,7 +243,6 @@ private void OnTestCompleted(TestCompletedEventArgs args)
246243
{ TestOutcome.Inconclusive, Colors.Gold },
247244
{ TestOutcome.Ignored, Colors.Orange },
248245
{ TestOutcome.Failed, Colors.Red },
249-
{ TestOutcome.SpectacularFail, Colors.Black }
250246
};
251247

252248
private TestOutcome _worstOutcome = TestOutcome.Unknown;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Windows.Data;
4+
5+
namespace Rubberduck.UI.UnitTesting
6+
{
7+
/// <summary>
8+
/// Binds an individual flag of <see cref="TestExplorerOutcomeFilter"/> to a boolean. Note: This is a stateful converter, so each bound control
9+
/// requires its own converter instance.
10+
/// </summary>
11+
class TestExplorerOutcomeFilterToBooleanConverter : IValueConverter
12+
{
13+
private TestExplorerOutcomeFilter _cachedOutcomeFilter;
14+
15+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
16+
{
17+
if (!(parameter is TestExplorerOutcomeFilter outcomeParameter)
18+
|| !(value is TestExplorerOutcomeFilter outcomeCurrentlyFiltering))
19+
{
20+
return false;
21+
}
22+
23+
_cachedOutcomeFilter = outcomeCurrentlyFiltering;
24+
return _cachedOutcomeFilter.HasFlag(outcomeParameter);
25+
}
26+
27+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
28+
{
29+
if (!(parameter is TestExplorerOutcomeFilter outcomeParameter)
30+
|| !(value is bool isApplied))
31+
{
32+
return _cachedOutcomeFilter;
33+
}
34+
35+
_cachedOutcomeFilter = isApplied
36+
? _cachedOutcomeFilter | outcomeParameter
37+
: _cachedOutcomeFilter ^ outcomeParameter;
38+
return _cachedOutcomeFilter;
39+
}
40+
}
41+
}

Rubberduck.Core/UI/UnitTesting/TestExplorerViewModel.cs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4+
using System.Collections.ObjectModel;
45
using System.ComponentModel;
56
using System.Globalization;
67
using System.Linq;
@@ -15,7 +16,6 @@
1516
using Rubberduck.UI.UnitTesting.Commands;
1617
using Rubberduck.UI.UnitTesting.ViewModels;
1718
using Rubberduck.UnitTesting;
18-
using Rubberduck.VBEditor.ComManagement;
1919
using Rubberduck.VBEditor.Utility;
2020
using DataFormats = System.Windows.DataFormats;
2121

@@ -29,6 +29,17 @@ public enum TestExplorerGrouping
2929
Location
3030
}
3131

32+
[Flags]
33+
public enum TestExplorerOutcomeFilter
34+
{
35+
None = 0,
36+
Unknown = 1,
37+
Fail = 1 << 1,
38+
Inconclusive = 1 << 2,
39+
Succeeded = 1 << 3,
40+
All = Unknown | Fail | Inconclusive | Succeeded
41+
}
42+
3243
internal sealed class TestExplorerViewModel : ViewModelBase, INavigateSelection, IDisposable
3344
{
3445
private readonly IClipboardWriter _clipboard;
@@ -67,6 +78,8 @@ public TestExplorerViewModel(ISelectionService selectionService,
6778

6879
OnPropertyChanged(nameof(Tests));
6980
TestGrouping = TestExplorerGrouping.Outcome;
81+
82+
OutcomeFilter = TestExplorerOutcomeFilter.All;
7083
}
7184

7285
public TestExplorerModel Model { get; }
@@ -132,6 +145,40 @@ public TestExplorerGrouping TestGrouping
132145
}
133146
}
134147

148+
private TestExplorerOutcomeFilter _outcomeFilter = TestExplorerOutcomeFilter.All;
149+
public TestExplorerOutcomeFilter OutcomeFilter
150+
{
151+
get => _outcomeFilter;
152+
set
153+
{
154+
if (value == _outcomeFilter)
155+
{
156+
return;
157+
}
158+
159+
_outcomeFilter = value;
160+
OnPropertyChanged();
161+
162+
Tests.Filter = FilterResults;
163+
}
164+
}
165+
166+
private string _testNameFilter = string.Empty;
167+
public string TestNameFilter
168+
{
169+
get => _testNameFilter;
170+
set
171+
{
172+
if (_testNameFilter != value)
173+
{
174+
_testNameFilter = value;
175+
OnPropertyChanged();
176+
Tests.Filter = FilterResults;
177+
OnPropertyChanged(nameof(Tests));
178+
}
179+
}
180+
}
181+
135182
private bool _expanded;
136183
public bool ExpandedState
137184
{
@@ -142,6 +189,21 @@ public bool ExpandedState
142189
OnPropertyChanged();
143190
}
144191
}
192+
/// <summary>
193+
/// Filtering for displaying the correct tests.
194+
/// Uses both <see cref="OutcomeFilter"/> and <see cref="TestNameFilter"/>
195+
/// </summary>
196+
private bool FilterResults(object unitTest)
197+
{
198+
var testMethodViewModel = unitTest as TestMethodViewModel;
199+
200+
var passesNameFilter = testMethodViewModel.QualifiedName.MemberName.ToUpper().Contains(TestNameFilter?.ToUpper() ?? string.Empty);
201+
202+
Enum.TryParse(testMethodViewModel.Result.Outcome.ToString(), out TestExplorerOutcomeFilter convertedOutcome);
203+
var passesOutcomeFilter = (OutcomeFilter & convertedOutcome) == convertedOutcome;
204+
205+
return passesNameFilter && passesOutcomeFilter;
206+
}
145207

146208
private void HandleTestCompletion(object sender, TestCompletedEventArgs e)
147209
{

0 commit comments

Comments
 (0)