Skip to content

Commit 95c6934

Browse files
committed
Add context menu, option to run tests by group, and button for resetting all test results. Closes #4703
1 parent 2ef8d61 commit 95c6934

File tree

10 files changed

+420
-138
lines changed

10 files changed

+420
-138
lines changed

Rubberduck.Core/UI/UnitTesting/TestExplorerControl.xaml

Lines changed: 90 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
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:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
109
xmlns:themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
11-
xmlns:converters="clr-namespace:Rubberduck.UI.Converters"
10+
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
1211
Language="{UICulture}"
1312
mc:Ignorable="d"
1413
d:DesignHeight="255" d:DesignWidth="455"
@@ -21,13 +20,16 @@
2120
<BitmapImage x:Key="RunImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/control.png" />
2221
<BitmapImage x:Key="RunAllTestsImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/flask--arrow.png" />
2322
<BitmapImage x:Key="RunNotRunTestsImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/question-white.png" />
23+
<BitmapImage x:Key="RunSingleTestImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/flask.png" />
2424
<BitmapImage x:Key="RunSelectedTestImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/flask--arrow.png" />
25-
<BitmapImage x:Key="RunSelectedCategoryImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/flask--arrow.png" />
25+
<BitmapImage x:Key="RunSelectedGroupImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/flask-tag.png" />
2626
<BitmapImage x:Key="RunInconclusiveTestsImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/flask--exclamation.png" />
2727
<BitmapImage x:Key="RunPassedTestsImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/tick-circle.png" />
2828
<BitmapImage x:Key="RunFailedTestsImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/cross-circle.png" />
2929
<BitmapImage x:Key="RepeatLastRunImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/arrow-repeat.png" />
3030

31+
<BitmapImage x:Key="ResetResultsImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/flask-undo.png" />
32+
3133
<BitmapImage x:Key="AddIcon" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/flask--plus.png" />
3234
<BitmapImage x:Key="AddTestMethodIcon" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/flask.png" />
3335
<BitmapImage x:Key="AddErrorTestMethodIcon" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/flask--exclamation.png" />
@@ -37,7 +39,7 @@
3739
<BitmapImage x:Key="GroupByCategoryImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/tag-label-gray.png" />
3840

3941
<BitmapImage x:Key="CopyResultsImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/document-copy.png" />
40-
42+
4143
<local:TestOutcomeImageSourceConverter x:Key="OutcomeIconConverter" />
4244
<BooleanToVisibilityConverter x:Key="BoolToVisibility"/>
4345

@@ -255,45 +257,45 @@
255257
<Separator />
256258

257259
<Menu Background="Transparent">
258-
<MenuItem VerticalAlignment="Center" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunButtonText}">
260+
<MenuItem VerticalAlignment="Center" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunMenuButtonText}">
259261
<MenuItem.Icon>
260262
<Image Source="{StaticResource RunImage}" />
261263
</MenuItem.Icon>
262-
<MenuItem Command="{Binding RunAllTestsCommand}" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunAllTests}">
264+
<MenuItem Command="{Binding RunAllTestsCommand}" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunMenuAllTests}">
263265
<MenuItem.Icon>
264266
<Image Source="{StaticResource RunAllTestsImage}" />
265267
</MenuItem.Icon>
266268
</MenuItem>
267269
<Separator />
268-
<MenuItem Command="{Binding RunNotExecutedTestsCommand}" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunNotRunTests}" >
270+
<MenuItem Command="{Binding RunNotExecutedTestsCommand}" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunMenuNotRunTests}" >
269271
<MenuItem.Icon>
270272
<Image Source="{StaticResource RunNotRunTestsImage}" />
271273
</MenuItem.Icon>
272274
</MenuItem>
273275
<MenuItem Command="{Binding RunSelectedTestsCommand}"
274276
CommandParameter="{Binding ElementName=TestGrid, Path=SelectedItems}"
275-
Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer ,Key=TestExplorer_RunSelectedTests}" >
277+
Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer ,Key=TestExplorer_RunMenuSelectedTests}" >
276278
<MenuItem.Icon>
277279
<Image Source="{StaticResource RunSelectedTestImage}" />
278280
</MenuItem.Icon>
279281
</MenuItem>
280-
<MenuItem Command="{Binding RunInconclusiveTestsCommand}" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunInconclusiveTests}" >
282+
<MenuItem Command="{Binding RunInconclusiveTestsCommand}" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunMenuInconclusiveTests}" >
281283
<MenuItem.Icon>
282284
<Image Source="{StaticResource RunInconclusiveTestsImage}" />
283285
</MenuItem.Icon>
284286
</MenuItem>
285-
<MenuItem Command="{Binding RunPassedTestsCommand}" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunPassedTests}" >
287+
<MenuItem Command="{Binding RunPassedTestsCommand}" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunMenuPassedTests}" >
286288
<MenuItem.Icon>
287289
<Image Source="{StaticResource RunPassedTestsImage}" />
288290
</MenuItem.Icon>
289291
</MenuItem>
290-
<MenuItem Command="{Binding RunFailedTestsCommand}" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunFailedTests}">
292+
<MenuItem Command="{Binding RunFailedTestsCommand}" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunMenuFailedTests}">
291293
<MenuItem.Icon>
292294
<Image Source="{StaticResource RunFailedTestsImage}" />
293295
</MenuItem.Icon>
294296
</MenuItem>
295297
<Separator />
296-
<MenuItem Command="{Binding RepeatLastRunCommand}" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunLastRunTests}">
298+
<MenuItem Command="{Binding RepeatLastRunCommand}" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunMenuLastRunTests}">
297299
<MenuItem.Icon>
298300
<Image Source="{StaticResource RepeatLastRunImage}" />
299301
</MenuItem.Icon>
@@ -303,6 +305,15 @@
303305

304306
<Separator />
305307

308+
<Button Command="{Binding ResetResultsCommand}">
309+
<Image Source="{StaticResource ResetResultsImage}" />
310+
<Button.ToolTip>
311+
<TextBlock Text="{Resx ResxName=Rubberduck.Resources.CodeExplorer.CodeExplorerUI, Key=TestExplorer_ResetButtonTooltip}" />
312+
</Button.ToolTip>
313+
</Button>
314+
315+
<Separator />
316+
306317
<Menu Background="Transparent">
307318
<MenuItem VerticalAlignment="Center" Header="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=Add}">
308319
<MenuItem.Icon>
@@ -378,7 +389,6 @@
378389
<Grid>
379390
<controls:GroupingGrid x:Name="TestGrid"
380391
ItemsSource="{Binding Tests}"
381-
SelectedItem="{Binding SelectedTest}"
382392
SelectionMode="Extended"
383393
ShowGroupingItemCount="True">
384394
<DataGrid.Columns>
@@ -389,12 +399,74 @@
389399
</DataTemplate>
390400
</DataGridTemplateColumn.CellTemplate>
391401
</DataGridTemplateColumn>
392-
<DataGridTextColumn Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_QualifiedModuleName}" Binding="{Binding QualifiedName.QualifiedModuleName}" />
393-
<DataGridTextColumn Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_MethodName}" Binding="{Binding Method.Declaration.IdentifierName}" />
394-
<DataGridTextColumn Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_CategoryName}" Binding="{Binding Method.Category.Name}" />
395-
<DataGridTextColumn Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_Message}" Binding="{Binding Result.Output}" Width="*" />
396-
<DataGridTextColumn Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_Duration}" Binding="{Binding Result.Duration, StringFormat={}{0}ms}" />
402+
<DataGridTemplateColumn Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_QualifiedModuleName}">
403+
<DataGridTemplateColumn.CellTemplate>
404+
<DataTemplate DataType="vm:TestMethodViewModel">
405+
<TextBlock></TextBlock>
406+
</DataTemplate>
407+
</DataGridTemplateColumn.CellTemplate>
408+
</DataGridTemplateColumn>
409+
<DataGridTemplateColumn Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_MethodName}">
410+
<DataGridTemplateColumn.CellTemplate>
411+
<DataTemplate DataType="vm:TestMethodViewModel">
412+
<TextBlock Text="{Binding Method.Declaration.IdentifierName}" />
413+
</DataTemplate>
414+
</DataGridTemplateColumn.CellTemplate>
415+
</DataGridTemplateColumn>
416+
<DataGridTemplateColumn Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_CategoryName}">
417+
<DataGridTemplateColumn.CellTemplate>
418+
<DataTemplate DataType="vm:TestMethodViewModel">
419+
<TextBlock Text="{Binding Method.Category.Name}" />
420+
</DataTemplate>
421+
</DataGridTemplateColumn.CellTemplate>
422+
</DataGridTemplateColumn>
423+
<DataGridTemplateColumn Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_Message}">
424+
<DataGridTemplateColumn.CellTemplate>
425+
<DataTemplate DataType="vm:TestMethodViewModel">
426+
<TextBlock Text="{Binding Result.Output}" />
427+
</DataTemplate>
428+
</DataGridTemplateColumn.CellTemplate>
429+
</DataGridTemplateColumn>
430+
<DataGridTemplateColumn Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_Duration}">
431+
<DataGridTemplateColumn.CellTemplate>
432+
<DataTemplate DataType="vm:TestMethodViewModel">
433+
<TextBlock Text="{Binding Result.Duration, StringFormat={}{0}ms}" />
434+
</DataTemplate>
435+
</DataGridTemplateColumn.CellTemplate>
436+
</DataGridTemplateColumn>
397437
</DataGrid.Columns>
438+
<DataGrid.ContextMenu>
439+
<ContextMenu>
440+
<MenuItem Command="{Binding RunSingleTestCommand}"
441+
Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_ContextMenuRunSingle}">
442+
<MenuItem.Icon>
443+
<Image Source="{StaticResource RunSingleTestImage}" />
444+
</MenuItem.Icon>
445+
</MenuItem>
446+
<MenuItem CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget.SelectedItems}"
447+
Command="{Binding RunSelectedTestsCommand}"
448+
Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_ContextMenuRunSelected}">
449+
<MenuItem.Icon>
450+
<Image Source="{StaticResource RunSelectedTestImage}" />
451+
</MenuItem.Icon>
452+
</MenuItem>
453+
<MenuItem Command="{Binding RunSelectedGroupCommand}"
454+
Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_ContextMenuRunGroup}">
455+
<MenuItem.Icon>
456+
<Image Source="{StaticResource RunSelectedGroupImage}" />
457+
</MenuItem.Icon>
458+
</MenuItem>
459+
<MenuItem Command="{Binding RunAllTestsCommand}"
460+
Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_ContextMenuRunAll}">
461+
<MenuItem.Icon>
462+
<Image Source="{StaticResource RunAllTestsImage}" />
463+
</MenuItem.Icon>
464+
</MenuItem>
465+
</ContextMenu>
466+
</DataGrid.ContextMenu>
467+
<i:Interaction.Behaviors>
468+
<local:TestExplorerRowMouseOverBehavior MouseOverTest="{Binding MouseOverTest, Mode=TwoWay}" MouseOverGroup="{Binding MouseOverGroup, Mode=TwoWay}" />
469+
</i:Interaction.Behaviors>
398470
</controls:GroupingGrid>
399471
</Grid>
400472
</Border>
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Windows;
5+
using System.Windows.Controls;
6+
using System.Windows.Data;
7+
using System.Windows.Input;
8+
using System.Windows.Interactivity;
9+
using GongSolutions.Wpf.DragDrop.Utilities;
10+
using Rubberduck.UI.Controls;
11+
using Rubberduck.UI.UnitTesting.ViewModels;
12+
13+
namespace Rubberduck.UI.UnitTesting
14+
{
15+
public class TestExplorerRowMouseOverBehavior : Behavior<DataGrid>
16+
{
17+
public TestMethodViewModel MouseOverTest
18+
{
19+
get => GetValue(MouseOverTestProperty) as TestMethodViewModel;
20+
set => SetValue(MouseOverTestProperty, value);
21+
}
22+
23+
public CollectionViewGroup MouseOverGroup
24+
{
25+
get => GetValue(MouseOverGroupProperty) as CollectionViewGroup;
26+
set => SetValue(MouseOverGroupProperty, value);
27+
}
28+
29+
public static readonly DependencyProperty MouseOverTestProperty = DependencyProperty.Register("MouseOverTest",
30+
typeof(TestMethodViewModel), typeof(TestExplorerRowMouseOverBehavior));
31+
32+
public static readonly DependencyProperty MouseOverGroupProperty = DependencyProperty.Register("MouseOverGroup",
33+
typeof(CollectionViewGroup), typeof(TestExplorerRowMouseOverBehavior));
34+
35+
protected override void OnAttached()
36+
{
37+
base.OnAttached();
38+
39+
var dataGrid = AssociatedObject;
40+
if (dataGrid == null)
41+
{
42+
return;
43+
}
44+
45+
AddHandler(dataGrid);
46+
}
47+
48+
protected override void OnDetaching()
49+
{
50+
var dataGrid = AssociatedObject;
51+
if (dataGrid != null)
52+
{
53+
RemoveHandler(dataGrid);
54+
}
55+
56+
base.OnDetaching();
57+
}
58+
59+
private void AddHandler(DataGrid dataGrid)
60+
{
61+
dataGrid.MouseMove += OnMouseMoved;
62+
dataGrid.ContextMenuOpening += OnContextMenuOpened;
63+
dataGrid.ContextMenuClosing += OnContextMenuClosed;
64+
}
65+
66+
private void RemoveHandler(DataGrid dataGrid)
67+
{
68+
dataGrid.MouseMove -= OnMouseMoved;
69+
dataGrid.ContextMenuOpening -= OnContextMenuOpened;
70+
dataGrid.ContextMenuClosing -= OnContextMenuClosed;
71+
}
72+
73+
private bool _contextMenuOpen;
74+
75+
private void OnContextMenuOpened(object sender, EventArgs e)
76+
{
77+
_contextMenuOpen = true;
78+
}
79+
80+
private void OnContextMenuClosed(object sender, EventArgs e)
81+
{
82+
_contextMenuOpen = false;
83+
}
84+
85+
private void OnMouseMoved(object sender, MouseEventArgs e)
86+
{
87+
if (!(sender is GroupingGrid grid) || _contextMenuOpen)
88+
{
89+
return;
90+
}
91+
92+
var isOverRow = false;
93+
foreach (var (row, model) in GetDataGridRows(grid))
94+
{
95+
if (!row.IsMouseOver)
96+
{
97+
continue;
98+
}
99+
MouseOverTest = model;
100+
isOverRow = true;
101+
break;
102+
}
103+
104+
if (!isOverRow)
105+
{
106+
MouseOverTest = null;
107+
// Over an expander?
108+
MouseOverGroup =
109+
grid.GetVisualDescendents<Expander>().FirstOrDefault(group => group.IsMouseOver)?.DataContext as CollectionViewGroup;
110+
}
111+
else
112+
{
113+
MouseOverGroup = null;
114+
}
115+
}
116+
117+
private static IEnumerable<(DataGridRow GridRow, TestMethodViewModel Model)> GetDataGridRows(ItemsControl grid)
118+
{
119+
var source = grid.ItemsSource;
120+
if (source is null)
121+
{
122+
yield break;
123+
}
124+
125+
foreach (var item in source.OfType<TestMethodViewModel>())
126+
{
127+
if (grid.ItemContainerGenerator.ContainerFromItem(item) is DataGridRow row)
128+
{
129+
yield return (row, item);
130+
}
131+
}
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)