Skip to content

Commit 4c7d5cc

Browse files
authored
Vbe add-in optimize test explorer (#64)
VBE add-in: * show source code * show detail test result
1 parent e2cf4c9 commit 4c7d5cc

12 files changed

+490
-38
lines changed

vbe-add-In/AccUnit.VbeAddIn/About/AboutViewModel.cs

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -41,34 +41,6 @@ private void Navigate(string url)
4141

4242
}
4343

44-
public class RelayCommand<T> : ICommand
45-
{
46-
private readonly Action<T> _execute;
47-
private readonly Predicate<T> _canExecute;
48-
49-
public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
50-
{
51-
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
52-
_canExecute = canExecute;
53-
}
54-
55-
public bool CanExecute(object parameter)
56-
{
57-
return _canExecute == null || _canExecute((T)parameter);
58-
}
59-
60-
public void Execute(object parameter)
61-
{
62-
_execute((T)parameter);
63-
}
64-
65-
public event EventHandler CanExecuteChanged
66-
{
67-
add => CommandManager.RequerySuggested += value;
68-
remove => CommandManager.RequerySuggested -= value;
69-
}
70-
}
71-
7244
public class Contributor
7345
{
7446
public Contributor(string name)

vbe-add-In/AccUnit.VbeAddIn/AccessCodeLib.AccUnit.VbeAddIn.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@
160160
<Compile Include="CommitMethodNameEventArgs.cs" />
161161
<Compile Include="ComRegistration.cs" />
162162
<Compile Include="Connect.cs" />
163+
<Compile Include="TestExplorer\TestResultDetailView.xaml.cs">
164+
<DependentUpon>TestResultDetailView.xaml</DependentUpon>
165+
</Compile>
166+
<Compile Include="TestExplorer\TestResultViewModel.cs" />
163167
<Compile Include="TestImportExport\ImportExportWindow.xaml.cs">
164168
<DependentUpon>ImportExportWindow.xaml</DependentUpon>
165169
</Compile>
@@ -331,6 +335,10 @@
331335
<SubType>Designer</SubType>
332336
<Generator>MSBuild:Compile</Generator>
333337
</Page>
338+
<Page Include="TestExplorer\TestResultDetailView.xaml">
339+
<SubType>Designer</SubType>
340+
<Generator>MSBuild:Compile</Generator>
341+
</Page>
334342
<Page Include="TestImportExport\ImportExportWindow.xaml">
335343
<SubType>Designer</SubType>
336344
<Generator>MSBuild:Compile</Generator>

vbe-add-In/AccUnit.VbeAddIn/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@
3131
// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
3232
// indem Sie "*" wie unten gezeigt eingeben:
3333
// [assembly: AssemblyVersion("1.0.*")]
34-
[assembly: AssemblyVersion("0.9.2.0")]
35-
[assembly: AssemblyFileVersion("0.9.2.0")]
34+
[assembly: AssemblyVersion("0.9.3.0")]
35+
[assembly: AssemblyFileVersion("0.9.3.0")]

vbe-add-In/AccUnit.VbeAddIn/RelayCommand.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,32 @@ public void Execute(object parameter)
4646
execute();
4747
}
4848
}
49+
50+
public class RelayCommand<T> : ICommand
51+
{
52+
private readonly Action<T> _execute;
53+
private readonly Predicate<T> _canExecute;
54+
55+
public RelayCommand(Action<T> execute, Predicate<T> canExecute = null)
56+
{
57+
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
58+
_canExecute = canExecute;
59+
}
60+
61+
public bool CanExecute(object parameter)
62+
{
63+
return _canExecute == null || _canExecute((T)parameter);
64+
}
65+
66+
public void Execute(object parameter)
67+
{
68+
_execute((T)parameter);
69+
}
70+
71+
public event EventHandler CanExecuteChanged
72+
{
73+
add => CommandManager.RequerySuggested += value;
74+
remove => CommandManager.RequerySuggested -= value;
75+
}
76+
}
4977
}

vbe-add-In/AccUnit.VbeAddIn/TestExplorer/TestExplorerManager.cs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
using AccessCodeLib.AccUnit.Interfaces;
1+
using AccessCodeLib.AccUnit.Configuration;
2+
using AccessCodeLib.AccUnit.Interfaces;
23
using AccessCodeLib.Common.Tools.Logging;
34
using AccessCodeLib.Common.VBIDETools;
45
using AccessCodeLib.Common.VBIDETools.Commandbar;
56
using Microsoft.Office.Core;
7+
using Microsoft.Vbe.Interop;
68
using System;
79

810
namespace AccessCodeLib.AccUnit.VbeAddIn.TestExplorer
@@ -46,6 +48,52 @@ private void InitViewModel()
4648
{
4749
e.TestClassInfo = VbeIntegrationManager.TestClassManager.GetTestClassInfo(e.ClassName, true);
4850
};
51+
_viewModel.GotoSource += (sender, e) =>
52+
{
53+
try
54+
{
55+
ShowSourceCode(e.FullName);
56+
}
57+
catch { }
58+
};
59+
}
60+
61+
private void ShowSourceCode(string fullName)
62+
{
63+
var nameParts = fullName.Split('.');
64+
var classname = nameParts[0];
65+
var membername = nameParts.Length > 1 ? nameParts[1] : null;
66+
ShowSourceCode(classname, membername);
67+
}
68+
69+
private void ShowSourceCode(string classname, string membername)
70+
{
71+
var codePane = ActivateCodePane(classname, membername);
72+
EnsureTextCursorIsVisible(codePane);
73+
}
74+
75+
private CodePane ActivateCodePane(string classname, string membername = null)
76+
{
77+
var modul = VbeIntegrationManager.TestClassManager.ActiveVBProject.VBComponents.Item(classname).CodeModule;
78+
var pane = modul.CodePane;
79+
pane.Show();
80+
pane.Window.SetFocus();
81+
var procLine = 1;
82+
if (!string.IsNullOrEmpty(membername))
83+
{
84+
// TODO: Determine upfront if the member does not exist and throw appropriate exception (including name of the missing member)
85+
procLine = modul.ProcBodyLine[membername, vbext_ProcKind.vbext_pk_Proc];
86+
}
87+
pane.SetSelection(procLine, 1, procLine, 1);
88+
return pane;
89+
}
90+
91+
private static void EnsureTextCursorIsVisible(_CodePane codePane)
92+
{
93+
var window = codePane.Window;
94+
window.Visible = false;
95+
window.Visible = true;
96+
window.SetFocus();
4997
}
5098

5199
public VbeIntegrationManager VbeIntegrationManager { get; set; }

vbe-add-In/AccUnit.VbeAddIn/TestExplorer/TestExplorerTreeView.xaml

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@
77
d:DataContext="{d:DesignInstance Type=local:TestItem}"
88
mc:Ignorable="d"
99
d:DesignHeight="450" d:DesignWidth="800">
10+
<UserControl.Resources>
11+
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
12+
</UserControl.Resources>
1013
<Grid>
1114
<TreeView d:DataContext="{d:DesignInstance Type=local:TestExplorerViewModel}" ItemsSource="{Binding TestItems}">
1215
<TreeView.ItemContainerStyle>
1316
<Style TargetType="TreeViewItem">
1417
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
18+
<EventSetter Event="GotFocus" Handler="TreeViewItem_GotFocus"/>
19+
<EventSetter Event="LostFocus" Handler="TreeViewItem_LostFocus"/>
1520
</Style>
1621
</TreeView.ItemContainerStyle>
1722
<TreeView.ItemTemplate>
@@ -21,12 +26,28 @@
2126
<ColumnDefinition Width="Auto" />
2227
<ColumnDefinition Width="Auto" />
2328
<ColumnDefinition Width="Auto" />
29+
<ColumnDefinition Width="Auto" />
2430
<ColumnDefinition Width="*" />
31+
<ColumnDefinition Width="Auto" />
2532
</Grid.ColumnDefinitions>
2633
<CheckBox Grid.Column="0" IsChecked="{Binding IsChecked}" Margin="0,0,10,0" />
2734
<Image Grid.Column="1" Source="{Binding ImageSource}" Margin="0,0,10,0" />
28-
<TextBlock Grid.Column="2" Text="{Binding Name}" Margin="0,0,10,0" />
29-
<TextBlock Grid.Column="3" Text="{Binding Result}" />
35+
<TextBlock Grid.Column="2" Text="{Binding Name}" />
36+
<Button Grid.Column="3" Content="Source"
37+
Command="{Binding Path=DataContext.GoToSourceCommand, RelativeSource={RelativeSource AncestorType={x:Type local:TestExplorerTreeView}}}"
38+
CommandParameter="{Binding}"
39+
Visibility="{Binding ShowGoToSourceButton, Converter={StaticResource BooleanToVisibilityConverter}}"
40+
VerticalAlignment="Center" Margin="2 0 4 0" Padding="3 0"
41+
Background="Transparent"
42+
/>
43+
<TextBlock Grid.Column="4" Text="{Binding Result}" />
44+
<Button Grid.Column="5" Content="Details"
45+
Command="{Binding Path=DataContext.ShowTestResultDetailCommand, RelativeSource={RelativeSource AncestorType={x:Type local:TestExplorerTreeView}}}"
46+
CommandParameter="{Binding}"
47+
Visibility="{Binding ShowTestDetailButton, Converter={StaticResource BooleanToVisibilityConverter}}"
48+
VerticalAlignment="Center" Margin="2 0 1 0" Padding="3 0"
49+
Background="Transparent"
50+
/>
3051
</Grid>
3152
</HierarchicalDataTemplate>
3253
</TreeView.ItemTemplate>

vbe-add-In/AccUnit.VbeAddIn/TestExplorer/TestExplorerTreeView.xaml.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Windows.Controls;
1+
using System.Windows;
2+
using System.Windows.Controls;
23

34
namespace AccessCodeLib.AccUnit.VbeAddIn.TestExplorer
45
{
@@ -9,5 +10,21 @@ public TestExplorerTreeView()
910
InitializeComponent();
1011
//DataContext = new TestExplorerViewModel();
1112
}
13+
14+
private void TreeViewItem_GotFocus(object sender, RoutedEventArgs e)
15+
{
16+
if (sender is TreeViewItem treeViewItem && treeViewItem.DataContext is TestItem testItem)
17+
{
18+
testItem.IsFocused = true;
19+
}
20+
}
21+
22+
private void TreeViewItem_LostFocus(object sender, RoutedEventArgs e)
23+
{
24+
if (sender is TreeViewItem treeViewItem && treeViewItem.DataContext is TestItem testItem)
25+
{
26+
testItem.IsFocused = false;
27+
}
28+
}
1229
}
1330
}

vbe-add-In/AccUnit.VbeAddIn/TestExplorer/TestExplorerViewModel.cs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
using System;
44
using System.Collections.Specialized;
55
using System.ComponentModel;
6+
using System.Globalization;
67
using System.Linq;
8+
using System.Windows.Data;
9+
using System.Windows;
710
using System.Windows.Input;
811
using System.Windows.Media;
912

@@ -20,6 +23,7 @@ public class TestExplorerViewModel : ITestResultReporter, INotifyPropertyChanged
2023
public event EventHandler<RunTestsEventArgs> RunTests;
2124
//public event EventHandler CancelTestRun;
2225
public event EventHandler<GetTestClassInfoEventArgs> GetTestClassInfo;
26+
public event EventHandler<TestItem> GotoSource;
2327

2428
public TestExplorerViewModel()
2529
{
@@ -29,6 +33,8 @@ public TestExplorerViewModel()
2933
TestItems = new TestClassInfoTestItems();
3034
RefreshCommand = new RelayCommand(Refresh);
3135
CommitCommand = new RelayCommand(Commit);
36+
ShowTestResultDetailCommand = new RelayCommand<TestItem>(ShowTestResultDetail);
37+
GoToSourceCommand = new RelayCommand<TestItem>(GoToSource);
3238
}
3339

3440
private CheckableItems<TestItem> _testItems;
@@ -332,16 +338,51 @@ public ImageSource RefreshCommandImageSource
332338

333339
protected void Refresh()
334340
{
335-
RefreshList?.Invoke(this, new CheckableTestItemsEventArgs(TestItems));
341+
try
342+
{
343+
RefreshList?.Invoke(this, new CheckableTestItemsEventArgs(TestItems));
344+
}
345+
catch (Exception ex)
346+
{
347+
UITools.ShowException(ex);
348+
}
336349
}
337350

338351
protected virtual void Commit()
339352
{
340-
TestClassList list = new TestClassList();
341-
list.AddRange(TestItems.Where(ti => ti.IsChecked).Select(ti => ((TestClassInfoTestItem)ti).TestClassInfo));
342-
RunTests?.Invoke(this, new RunTestsEventArgs(list));
353+
try
354+
{
355+
TestClassList list = new TestClassList();
356+
list.AddRange(TestItems.Where(ti => ti.IsChecked).Select(ti => ((TestClassInfoTestItem)ti).TestClassInfo));
357+
RunTests?.Invoke(this, new RunTestsEventArgs(list));
358+
}
359+
catch (Exception ex)
360+
{
361+
UITools.ShowException(ex);
362+
}
363+
}
364+
365+
public ICommand ShowTestResultDetailCommand { get; }
366+
protected virtual void ShowTestResultDetail(TestItem testItem)
367+
{
368+
try
369+
{
370+
var testResultViewModel = new TestResultViewModel(testItem.TestResult);
371+
var testResultView = new TestResultDetailView(testResultViewModel);
372+
testResultView.ShowDialog();
373+
}
374+
catch(Exception ex )
375+
{
376+
UITools.ShowException(ex);
377+
}
378+
343379
}
344380

381+
public ICommand GoToSourceCommand { get; }
382+
protected virtual void GoToSource(TestItem testItem)
383+
{
384+
GotoSource?.Invoke(this, testItem);
385+
}
345386
}
346387

347388
public static class TestExplorerInfo

vbe-add-In/AccUnit.VbeAddIn/TestExplorer/TestItem.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
using AccessCodeLib.AccUnit.Interfaces;
2+
using System.Globalization;
3+
using System.Windows.Data;
4+
using System.Windows;
5+
using System;
26
using System.Windows.Media;
7+
using System.Linq;
38

49
namespace AccessCodeLib.AccUnit.VbeAddIn.TestExplorer
510
{
@@ -86,5 +91,60 @@ private ImageSource CalculatedImageSource
8691
return null;
8792
}
8893
}
94+
95+
private bool isFocused;
96+
public bool IsFocused
97+
{
98+
get => isFocused;
99+
set { isFocused = value; OnPropertyChanged(nameof(IsFocused)); OnPropertyChanged(nameof(ShowTestDetailButton)); OnPropertyChanged(nameof(ShowGoToSourceButton)); }
100+
}
101+
102+
private bool ChildrenAreFocused
103+
{
104+
get
105+
{
106+
return Children.Count > 0 && Children.Any(c => c.IsFocused);
107+
}
108+
109+
110+
}
111+
112+
public bool ShowTestDetailButton
113+
{
114+
get
115+
{
116+
return IsFocused && TestResult != null && !TestResult.Success && Children.Count == 0;
117+
}
118+
}
119+
120+
public bool ShowGoToSourceButton
121+
{
122+
get
123+
{
124+
return IsFocused && !ChildrenAreFocused;
125+
}
126+
}
127+
128+
}
129+
130+
public class BooleanToVisibilityConverter : IValueConverter
131+
{
132+
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
133+
{
134+
if (value is bool booleanValue)
135+
{
136+
return booleanValue ? Visibility.Visible : Visibility.Hidden;
137+
}
138+
return Visibility.Hidden;
139+
}
140+
141+
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
142+
{
143+
if (value is Visibility visibility)
144+
{
145+
return visibility == Visibility.Visible;
146+
}
147+
return false;
148+
}
89149
}
90150
}

0 commit comments

Comments
 (0)