Skip to content

Commit bcb0d22

Browse files
committed
Allow test run cancellation. Closes #3158
1 parent f6e8a80 commit bcb0d22

File tree

6 files changed

+137
-40
lines changed

6 files changed

+137
-40
lines changed

Rubberduck.Core/UI/UnitTesting/TestExplorerControl.xaml

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
xmlns:controls="clr-namespace:Rubberduck.UI.Controls"
99
xmlns:themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
1010
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
11+
xmlns:converters="clr-namespace:Rubberduck.UI.Converters"
1112
Language="{UICulture}"
1213
mc:Ignorable="d"
1314
d:DesignHeight="255" d:DesignWidth="455"
@@ -29,6 +30,7 @@
2930
<BitmapImage x:Key="RepeatLastRunImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/arrow-repeat.png" />
3031

3132
<BitmapImage x:Key="ResetResultsImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/flask-undo.png" />
33+
<BitmapImage x:Key="StopImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/control-stop-square.png" />
3234

3335
<BitmapImage x:Key="AddIcon" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/flask--plus.png" />
3436
<BitmapImage x:Key="AddTestMethodIcon" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/flask.png" />
@@ -38,14 +40,23 @@
3840
<BitmapImage x:Key="GroupByLocationImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Custom/PNG/ObjectClass.png" />
3941
<BitmapImage x:Key="GroupByCategoryImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/tag-label-gray.png" />
4042

43+
<BitmapImage x:Key="ExpandAllImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/expand-all.png" />
44+
<BitmapImage x:Key="CollapseAllImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/collapse-all.png" />
45+
4146
<BitmapImage x:Key="CopyResultsImage" UriSource="pack://application:,,,/Rubberduck.Resources;component/Icons/Fugue/document-copy.png" />
4247

4348
<local:TestOutcomeImageSourceConverter x:Key="OutcomeIconConverter" />
4449
<BooleanToVisibilityConverter x:Key="BoolToVisibility"/>
50+
<converters:BoolToHiddenVisibilityConverter x:Key="BoolToCollapsed"/>
4551

4652
<SolidColorBrush x:Key="ToolBarToggleButtonVerticalBackground" Color="Transparent" />
4753
<SolidColorBrush x:Key="ToolBarButtonHover" Color="#210080FF"/>
4854
<SolidColorBrush x:Key="ToolBarGripper" Color="#FF6D6D6D"/>
55+
<SolidColorBrush x:Key="ToolBarVerticalBackground" Color="#FFEEF5FD"/>
56+
<SolidColorBrush x:Key="ToolBarToggleButtonHorizontalBackground" Color="#FFEEF5FD"/>
57+
<SolidColorBrush x:Key="ToolBarMenuBorder" Color="#FFB6BDC5"/>
58+
<SolidColorBrush x:Key="ToolBarSubMenuBackground" Color="#FFEEF5FD"/>
59+
4960
<Style x:Key="ToolBarVerticalOverflowButtonStyle" TargetType="{x:Type ToggleButton}">
5061
<Setter Property="Background" Value="{StaticResource ToolBarToggleButtonVerticalBackground}"/>
5162
<Setter Property="MinHeight" Value="0"/>
@@ -81,8 +92,7 @@
8192
</DataTrigger>
8293
</Style.Triggers>
8394
</Style>
84-
<SolidColorBrush x:Key="ToolBarVerticalBackground" Color="#FFEEF5FD"/>
85-
<SolidColorBrush x:Key="ToolBarToggleButtonHorizontalBackground" Color="#FFEEF5FD"/>
95+
8696
<Style x:Key="ToolBarHorizontalOverflowButtonStyle" TargetType="{x:Type ToggleButton}">
8797
<Setter Property="Background" Value="{StaticResource ToolBarToggleButtonHorizontalBackground}"/>
8898
<Setter Property="MinHeight" Value="0"/>
@@ -118,8 +128,7 @@
118128
</DataTrigger>
119129
</Style.Triggers>
120130
</Style>
121-
<SolidColorBrush x:Key="ToolBarMenuBorder" Color="#FFB6BDC5"/>
122-
<SolidColorBrush x:Key="ToolBarSubMenuBackground" Color="#FFEEF5FD"/>
131+
123132
<Style x:Key="ToolBarThumbStyle" TargetType="{x:Type Thumb}">
124133
<Setter Property="Template">
125134
<Setter.Value>
@@ -147,6 +156,7 @@
147156
</Setter.Value>
148157
</Setter>
149158
</Style>
159+
150160
<Style x:Key="ToolBarMainPanelBorderStyle" TargetType="{x:Type Border}">
151161
<Setter Property="Margin" Value="0,0,11,0"/>
152162
<Setter Property="CornerRadius" Value="3,3,3,3"/>
@@ -156,6 +166,7 @@
156166
</DataTrigger>
157167
</Style.Triggers>
158168
</Style>
169+
159170
<Style x:Key="ToolBarWithOverflowOnlyShowingWhenNeededStyle" TargetType="{x:Type ToolBar}">
160171
<Setter Property="Template">
161172
<Setter.Value>
@@ -224,6 +235,17 @@
224235
</DataTrigger>
225236
</Style.Triggers>
226237
</Style>
238+
239+
<Style x:Key="ToolbarImageOpacity" TargetType="Image" >
240+
<Setter Property="Height" Value="16" />
241+
<Setter Property="Width" Value="16" />
242+
<Setter Property="Margin" Value="2,0,2,0" />
243+
<Style.Triggers>
244+
<Trigger Property="IsEnabled" Value="False">
245+
<Setter Property="Opacity" Value="0.3" />
246+
</Trigger>
247+
</Style.Triggers>
248+
</Style>
227249
</UserControl.Resources>
228250

229251
<Grid>
@@ -250,16 +272,16 @@
250272
</Style>
251273
</ToolBar.Resources>
252274

253-
<Button Command="{Binding RefreshCommand}">
254-
<Image Source="{StaticResource RefreshImage}" />
275+
<Button Command="{Binding RefreshCommand}" >
276+
<Image Source="{StaticResource RefreshImage}" Style="{StaticResource ToolbarImageOpacity}" />
255277
</Button>
256278

257279
<Separator />
258280

259281
<Menu Background="Transparent">
260282
<MenuItem VerticalAlignment="Center" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunMenuButtonText}">
261283
<MenuItem.Icon>
262-
<Image Source="{StaticResource RunImage}" />
284+
<Image Source="{StaticResource RunImage}" Style="{StaticResource ToolbarImageOpacity}" />
263285
</MenuItem.Icon>
264286
<MenuItem Command="{Binding RunAllTestsCommand}" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_RunMenuAllTests}">
265287
<MenuItem.Icon>
@@ -305,10 +327,16 @@
305327

306328
<Separator />
307329

308-
<Button Command="{Binding ResetResultsCommand}">
309-
<Image Source="{StaticResource ResetResultsImage}" />
330+
<Button Command="{Binding CancelTestRunCommand}" >
331+
<Image Source="{StaticResource StopImage}" Style="{StaticResource ToolbarImageOpacity}" />
332+
</Button>
333+
334+
<Separator />
335+
336+
<Button Command="{Binding ResetResultsCommand}" >
337+
<Image Source="{StaticResource ResetResultsImage}" Style="{StaticResource ToolbarImageOpacity}" />
310338
<Button.ToolTip>
311-
<TextBlock Text="{Resx ResxName=Rubberduck.Resources.CodeExplorer.CodeExplorerUI, Key=TestExplorer_ResetButtonTooltip}" />
339+
<TextBlock Text="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_ResetButtonTooltip}" />
312340
</Button.ToolTip>
313341
</Button>
314342

@@ -317,7 +345,7 @@
317345
<Menu Background="Transparent">
318346
<MenuItem VerticalAlignment="Center" Header="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=Add}">
319347
<MenuItem.Icon>
320-
<Image Source="{StaticResource AddIcon}" />
348+
<Image Source="{StaticResource AddIcon}" Style="{StaticResource ToolbarImageOpacity}" />
321349
</MenuItem.Icon>
322350
<MenuItem Command="{Binding AddTestModuleCommand}" Header="{Resx ResxName=Rubberduck.Resources.UnitTesting.TestExplorer, Key=TestExplorer_AddTestModule}">
323351
</MenuItem>
@@ -359,8 +387,20 @@
359387

360388
<Separator />
361389

390+
<Button Name="CollapseAll" Command="{Binding CollapseAllCommand}" Margin="2"
391+
ToolTip="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=InspectionResults_CollapseAll}">
392+
<Image Source="{StaticResource CollapseAllImage}" Style="{StaticResource ToolbarImageOpacity}" />
393+
</Button>
394+
395+
<Button Name="ExpandAll" Command="{Binding ExpandAllCommand}" Margin="2"
396+
ToolTip="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=InspectionResults_ExpandAll}">
397+
<Image Source="{StaticResource ExpandAllImage}" Style="{StaticResource ToolbarImageOpacity}" />
398+
</Button>
399+
400+
<Separator />
401+
362402
<Button Command="{Binding CopyResultsCommand}">
363-
<Image Source="{StaticResource CopyResultsImage}" />
403+
<Image Source="{StaticResource CopyResultsImage}" Style="{StaticResource ToolbarImageOpacity}" />
364404
<Button.ToolTip>
365405
<TextBlock Text="{Resx ResxName=Rubberduck.Resources.CodeExplorer.CodeExplorerUI, Key=CodeExplorer_CopyToolTip}" />
366406
</Button.ToolTip>
@@ -478,6 +518,7 @@
478518
</DataGrid.ContextMenu>
479519
<i:Interaction.Behaviors>
480520
<local:TestExplorerRowMouseOverBehavior MouseOverTest="{Binding MouseOverTest, Mode=TwoWay}" MouseOverGroup="{Binding MouseOverGroup, Mode=TwoWay}" />
521+
<controls:GroupItemExpandedBehavior ExpandedState="{Binding ExpandedState, Mode=TwoWay}" />
481522
</i:Interaction.Behaviors>
482523
</controls:GroupingGrid>
483524
</Grid>

Rubberduck.Core/UI/UnitTesting/TestExplorerModel.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,13 @@ public void ExecuteTests(IReadOnlyCollection<TestMethodViewModel> tests)
4242
catch (Exception ex)
4343
{
4444
Logger.Warn(ex, "Test engine exception caught in TestExplorerModel.");
45+
IsBusy = false;
4546
}
47+
}
4648

47-
IsBusy = false;
49+
public void CancelTestRun()
50+
{
51+
_testEngine.RequestCancellation();
4852
}
4953

5054
private void HandleTestRunStarted(object sender, TestRunStartedEventArgs e)
@@ -54,6 +58,9 @@ private void HandleTestRunStarted(object sender, TestRunStartedEventArgs e)
5458
return;
5559
}
5660

61+
// This also needs to be set in the handler - the test engine has other entry points.
62+
IsBusy = true;
63+
5764
UpdateProgressBar(TestOutcome.Unknown, true);
5865

5966
var running = Tests.Where(test => e.Tests.Contains(test.Method)).ToList();

Rubberduck.Core/UI/UnitTesting/TestExplorerViewModel.cs

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
using NLog;
99
using Rubberduck.Common;
1010
using Rubberduck.Interaction.Navigation;
11-
using Rubberduck.Parsing.VBA;
1211
using Rubberduck.Settings;
1312
using Rubberduck.UI.Command;
1413
using Rubberduck.UI.Settings;
1514
using Rubberduck.UI.UnitTesting.Commands;
1615
using Rubberduck.UI.UnitTesting.ViewModels;
1716
using Rubberduck.UnitTesting;
17+
using Rubberduck.VBEditor.ComManagement;
1818
using DataFormats = System.Windows.DataFormats;
1919

2020
namespace Rubberduck.UI.UnitTesting
@@ -29,29 +29,28 @@ internal enum TestExplorerGrouping
2929

3030
internal sealed class TestExplorerViewModel : ViewModelBase, INavigateSelection, IDisposable
3131
{
32-
private readonly ITestEngine _testEngine;
3332
private readonly IClipboardWriter _clipboard;
3433
private readonly ISettingsFormFactory _settingsFormFactory;
3534

36-
public TestExplorerViewModel(RubberduckParserState state,
37-
ITestEngine testEngine,
35+
public TestExplorerViewModel(IProjectsProvider projectsProvider,
3836
TestExplorerModel model,
3937
IClipboardWriter clipboard,
4038
IGeneralConfigService configService,
4139
ISettingsFormFactory settingsFormFactory)
4240
{
43-
_testEngine = testEngine;
44-
_testEngine.TestCompleted += TestEngineTestCompleted;
4541
_clipboard = clipboard;
4642
_settingsFormFactory = settingsFormFactory;
4743

48-
NavigateCommand = new NavigateCommand(state.ProjectsProvider);
44+
NavigateCommand = new NavigateCommand(projectsProvider);
4945
RunSingleTestCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteSingleTestCommand, CanExecuteSingleTest);
5046
RunSelectedTestsCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteSelectedTestsCommand, CanExecuteSelectedTestsCommand);
5147
RunSelectedGroupCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteRunSelectedGroupCommand, CanExecuteSelectedGroupCommand);
48+
CancelTestRunCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteCancelTestRunCommand, CanExecuteCancelTestRunCommand);
5249
ResetResultsCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteResetResultsCommand, CanExecuteResetResultsCommand);
5350
CopyResultsCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteCopyResultsCommand);
5451
OpenTestSettingsCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), OpenSettings);
52+
CollapseAllCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteCollapseAll);
53+
ExpandAllCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), ExecuteExpandAll);
5554

5655
Model = model;
5756
if (CollectionViewSource.GetDefaultView(Model.Tests) is ListCollectionView tests)
@@ -69,14 +68,6 @@ public TestExplorerViewModel(RubberduckParserState state,
6968

7069
public ICollectionView Tests { get; }
7170

72-
public event EventHandler<TestCompletedEventArgs> TestCompleted;
73-
private void TestEngineTestCompleted(object sender, TestCompletedEventArgs e)
74-
{
75-
// Propagate the event
76-
TestCompleted?.Invoke(sender, e);
77-
Tests.Refresh();
78-
}
79-
8071
public INavigateSource SelectedItem => MouseOverTest;
8172

8273
private TestMethodViewModel _mouseOverTest;
@@ -136,6 +127,17 @@ public TestExplorerGrouping TestGrouping
136127
}
137128
}
138129

130+
private bool _expanded;
131+
public bool ExpandedState
132+
{
133+
get => _expanded;
134+
set
135+
{
136+
_expanded = value;
137+
OnPropertyChanged();
138+
}
139+
}
140+
139141
public ReparseCommand RefreshCommand { get; set; }
140142

141143
public RunAllTestsCommand RunAllTestsCommand { get; set; }
@@ -149,6 +151,7 @@ public TestExplorerGrouping TestGrouping
149151
public CommandBase RunSelectedTestsCommand { get; }
150152
public CommandBase RunSelectedGroupCommand { get; }
151153

154+
public CommandBase CancelTestRunCommand { get; }
152155
public CommandBase ResetResultsCommand { get; }
153156

154157
public AddTestModuleCommand AddTestModuleCommand { get; set; }
@@ -161,6 +164,9 @@ public TestExplorerGrouping TestGrouping
161164

162165
public INavigateCommand NavigateCommand { get; }
163166

167+
public CommandBase CollapseAllCommand { get; }
168+
public CommandBase ExpandAllCommand { get; }
169+
164170
private bool CanExecuteSingleTest(object obj)
165171
{
166172
return !Model.IsBusy && MouseOverTest != null;
@@ -181,6 +187,21 @@ private bool CanExecuteResetResultsCommand(object obj)
181187
return !Model.IsBusy && Tests.OfType<TestMethodViewModel>().Any(test => test.Result.Outcome != TestOutcome.Unknown);
182188
}
183189

190+
private bool CanExecuteCancelTestRunCommand(object obj)
191+
{
192+
return Model.IsBusy;
193+
}
194+
195+
private void ExecuteCollapseAll(object parameter)
196+
{
197+
ExpandedState = false;
198+
}
199+
200+
private void ExecuteExpandAll(object parameter)
201+
{
202+
ExpandedState = true;
203+
}
204+
184205
private void ExecuteSingleTestCommand(object obj)
185206
{
186207
if (MouseOverTest == null)
@@ -222,6 +243,11 @@ private void ExecuteRunSelectedGroupCommand(object obj)
222243
Model.ExecuteTests(tests.Items.OfType<TestMethodViewModel>().ToList());
223244
}
224245

246+
private void ExecuteCancelTestRunCommand(object parameter)
247+
{
248+
Model.CancelTestRun();
249+
}
250+
225251
private void ExecuteResetResultsCommand(object parameter)
226252
{
227253
foreach (var test in Tests.OfType<TestMethodViewModel>())
@@ -291,7 +317,6 @@ private void OpenSettings(object param)
291317

292318
public void Dispose()
293319
{
294-
_testEngine.TestCompleted -= TestEngineTestCompleted;
295320
Model.Dispose();
296321
}
297322
}
Loading

Rubberduck.UnitTesting/UnitTesting/ITestEngine.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public interface ITestEngine
1717
void Run(IEnumerable<TestMethod> tests);
1818
void RunByOutcome(TestOutcome outcome);
1919
void RepeatLastRun();
20+
void RequestCancellation();
2021
}
2122

2223
public class TestRunStartedEventArgs : EventArgs

0 commit comments

Comments
 (0)