Skip to content

Commit a1b7b45

Browse files
authored
Merge pull request #197 from Nfactor26/controls-and-prefabs-usage-details-in-testss-and-fixtures
Count of Controls and Prefabs used in test cases and Prefab are tracked now
2 parents ff6287c + c5ba8e4 commit a1b7b45

File tree

27 files changed

+736
-33
lines changed

27 files changed

+736
-33
lines changed

src/Pixel.Automation.AppExplorer.ViewModels/Control/ControlExplorerViewModel.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ public async Task ChangeImageFromExistingAsync(ControlDescriptionViewModel selec
363363
{
364364
try
365365
{
366+
Guard.Argument(selectedControl, nameof(selectedControl)).NotNull();
366367
OpenFileDialog openFileDialog = new OpenFileDialog();
367368
openFileDialog.Filter = "PNG File (*.Png)|*.Png";
368369
openFileDialog.InitialDirectory = Environment.CurrentDirectory;
@@ -398,8 +399,7 @@ public async Task CloneControl(ControlDescriptionViewModel controlToClone)
398399
{
399400
try
400401
{
401-
Guard.Argument(controlToClone).NotNull();
402-
402+
Guard.Argument(controlToClone, nameof(controlToClone)).NotNull();
403403
var clonedControl = controlToClone.ControlDescription.Clone() as ControlDescription;
404404
var controlDescriptionViewModel = new ControlDescriptionViewModel(clonedControl);
405405
controlDescriptionViewModel.ControlName = Path.GetRandomFileName();
@@ -423,8 +423,7 @@ public async Task CreateRevision(ControlDescriptionViewModel control)
423423
{
424424
try
425425
{
426-
Guard.Argument(control).NotNull();
427-
426+
Guard.Argument(control, nameof(control)).NotNull();
428427
var clonedControl = control.ControlDescription.Clone() as ControlDescription;
429428
clonedControl.ControlId = control.ControlId;
430429
clonedControl.Version = new Version(control.Version.Major + 1, 0);
@@ -452,6 +451,7 @@ public async Task CreateRevision(ControlDescriptionViewModel control)
452451
/// <returns></returns>
453452
public async Task SaveControlDetails(ControlDescriptionViewModel controlToSave, bool updateApplication)
454453
{
454+
Guard.Argument(controlToSave, nameof(controlToSave)).NotNull();
455455
await this.applicationDataManager.AddOrUpdateControlAsync(controlToSave.ControlDescription);
456456
lock (locker)
457457
{
@@ -505,6 +505,19 @@ private async Task SaveBitMapSource(ControlDescription controlDescription, Image
505505
throw new ArgumentException($"{nameof(imageSource)} must be a BitmapImage");
506506
}
507507

508+
509+
/// <summary>
510+
/// Broadcast a FilterTestMessage which is processed by Test explorer view to filter and show only those test cases
511+
/// which uses this prefab
512+
/// </summary>
513+
/// <param name="targetPrefab"></param>
514+
/// <returns></returns>
515+
public async Task ShowUsage(ControlDescriptionViewModel controlDescription)
516+
{
517+
Guard.Argument(controlDescription, nameof(controlDescription)).NotNull();
518+
await this.eventAggregator.PublishOnUIThreadAsync(new TestFilterNotification("control", controlDescription.ControlId));
519+
}
520+
508521
#endregion Manage Controls
509522

510523
/// <summary>

src/Pixel.Automation.AppExplorer.ViewModels/PrefabDropHandler/PrefabDropHandlerViewModel.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
using Pixel.Automation.AppExplorer.ViewModels.Prefab;
1+
using Caliburn.Micro;
2+
using Dawn;
3+
using Pixel.Automation.AppExplorer.ViewModels.Prefab;
24
using Pixel.Automation.Core;
35
using Pixel.Automation.Core.Attributes;
46
using Pixel.Automation.Core.Components.Prefabs;
7+
using Pixel.Automation.Core.Components.TestCase;
58
using Pixel.Automation.Core.Interfaces;
69
using Pixel.Automation.Editor.Core;
710
using Pixel.Automation.Editor.Core.ViewModels;
@@ -22,6 +25,7 @@ public class PrefabDropHandlerViewModel : Wizard
2225
private readonly IProjectFileSystem projectFileSystem;
2326
private readonly IScriptEngine scriptEngine;
2427
private readonly IScriptEditorFactory scriptEditorFactory;
28+
private readonly IEventAggregator eventAggregator;
2529

2630
private PrefabEntity prefabEntity;
2731
private EntityComponentViewModel dropTarget;
@@ -30,14 +34,18 @@ public class PrefabDropHandlerViewModel : Wizard
3034
/// constructor
3135
/// </summary>
3236
/// <param name="entityManager">EntityManager of the Entity which is the drop target</param>
33-
public PrefabDropHandlerViewModel(IEntityManager entityManager, PrefabProjectViewModel prefabProjectViewModel, EntityComponentViewModel dropTarget)
37+
public PrefabDropHandlerViewModel(IEntityManager entityManager, PrefabProjectViewModel prefabProjectViewModel,
38+
EntityComponentViewModel dropTarget)
3439
{
35-
this.DisplayName = "(1/3) Select prefab version and mapping scripts";
40+
Guard.Argument(entityManager, nameof(entityManager)).NotNull();
41+
Guard.Argument(prefabProjectViewModel, nameof(prefabProjectViewModel)).NotNull();
42+
this.DisplayName = "(1/3) Select prefab version and mapping scripts";
3643
this.scriptEditorFactory = entityManager.GetServiceOfType<IScriptEditorFactory>(); ;
3744
this.projectFileSystem = entityManager.GetCurrentFileSystem() as IProjectFileSystem;
3845
this.prefabFileSystem = entityManager.GetServiceOfType<IPrefabFileSystem>();
3946
this.scriptEngine = entityManager.GetScriptEngine();
40-
this.dropTarget = dropTarget;
47+
this.eventAggregator = entityManager.GetServiceOfType<IEventAggregator>();
48+
this.dropTarget = Guard.Argument(dropTarget, nameof(dropTarget)).NotNull();
4149

4250
this.prefabEntity = new PrefabEntity()
4351
{
@@ -71,6 +79,21 @@ public PrefabDropHandlerViewModel(IEntityManager entityManager, PrefabProjectVie
7179
this.stagedScreens.Add(prefabOutputMappingScript);
7280
}
7381

82+
83+
public override async Task Finish()
84+
{
85+
var addToComponent = this.dropTarget.Model;
86+
if (addToComponent.TryGetAnsecstorOfType<TestCaseEntity>(out TestCaseEntity testCaseEntity))
87+
{
88+
await this.eventAggregator.PublishOnBackgroundThreadAsync(new PrefabAddedEventArgs(this.prefabEntity.PrefabId, testCaseEntity.Tag));
89+
}
90+
else if (addToComponent.TryGetAnsecstorOfType<TestFixtureEntity>(out TestFixtureEntity testFixtureEntity))
91+
{
92+
await this.eventAggregator.PublishOnBackgroundThreadAsync(new PrefabAddedEventArgs(this.prefabEntity.PrefabId, testFixtureEntity.Tag));
93+
}
94+
await base.Finish();
95+
}
96+
7497
public override async Task Cancel()
7598
{
7699
var prefabComponentViewModel = this.dropTarget.ComponentCollection.FirstOrDefault(a => a.Model.Equals(this.prefabEntity));

src/Pixel.Automation.AppExplorer.ViewModels/PrefabDropHandler/PrefabVersionSelectorViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
using Microsoft.Win32;
22
using Pixel.Automation.AppExplorer.ViewModels.Prefab;
33
using Pixel.Automation.Core.Components.Prefabs;
4+
using Pixel.Automation.Core.Components.TestCase;
45
using Pixel.Automation.Core.Interfaces;
56
using Pixel.Automation.Core.Models;
67
using Pixel.Automation.Editor.Core;
78
using Pixel.Automation.Editor.Core.ViewModels;
89
using Pixel.Automation.Reference.Manager;
910
using Pixel.Automation.Reference.Manager.Contracts;
10-
using Pixel.Persistence.Services.Client.Interfaces;
1111
using Serilog;
1212
using System.IO;
1313
using System.Windows;

src/Pixel.Automation.AppExplorer.Views/Control/ControlExplorerView.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
<MenuItem x:Name="CreateRevision" Header="Create Revision" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}, AncestorLevel=2}}" cal:Message.Attach="[Event Click] = [Action CreateRevision($dataContext)]"></MenuItem>
7777
<MenuItem x:Name="ChangeImage" Header="Change Image" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}, AncestorLevel=2}}" cal:Message.Attach="[Event Click] = [Action ChangeImageFromExistingAsync($dataContext)]"></MenuItem>
7878
<MenuItem x:Name="MoveToScreen" Header="Move To Screen" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}, AncestorLevel=2}}" cal:Message.Attach="[Event Click] = [Action MoveToScreen($dataContext)]"></MenuItem>
79+
<MenuItem x:Name="ShowUsage" Header="Show Usage" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}, AncestorLevel=2}}" cal:Message.Attach="[Event Click] = [Action ShowUsage($dataContext)]"></MenuItem>
7980
<MenuItem x:Name="Delete" Header="Delete" cal:Action.TargetWithoutContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type Grid}, AncestorLevel=2}}" cal:Message.Attach="[Event Click] = [Action DeleteControlAsync($dataContext)]" ></MenuItem>
8081
</StackPanel>
8182
</ControlTemplate>

src/Pixel.Automation.Core/Component/EntityHelpers.cs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Linq;
7+
using System.Text;
78

89
namespace Pixel.Automation.Core
910
{
@@ -28,6 +29,33 @@ public static List<IComponent> GetAllComponents(this Entity rootEntity)
2829
return childComponents;
2930
}
3031

32+
/// <summary>
33+
/// Check if a component of given type exists within specified SearchScope of the Entity
34+
/// </summary>
35+
/// <typeparam name="T"></typeparam>
36+
/// <param name="rootEntity"></param>
37+
/// <param name="searchScope"></param>
38+
/// <returns></returns>
39+
public static bool HasComponentsOfType<T>(this Entity rootEntity, SearchScope searchScope = SearchScope.Descendants)
40+
{
41+
switch (searchScope)
42+
{
43+
case SearchScope.Children:
44+
return rootEntity.Components.OfType<T>().Any();
45+
case SearchScope.Descendants:
46+
List<IComponent> childComponents = GetAllComponents(rootEntity);
47+
foreach (var component in childComponents)
48+
{
49+
if (component is T)
50+
{
51+
return true;
52+
}
53+
}
54+
break;
55+
}
56+
return false;
57+
}
58+
3159
/// <summary>
3260
/// Returns all components of Type : T in the rootEntity or any of its children
3361
/// </summary>
@@ -310,7 +338,7 @@ public static IEnumerable<ILoop> GetInnerLoops(this Entity rootEntity)
310338
/// <param name="component"></param>
311339
/// <returns></returns>
312340
/// <exception cref="MissingComponentException">Thrown when ancestor of specified type T could not be found</exception>
313-
public static T GetAnsecstorOfType<T>(this IComponent component) where T : class,IComponent
341+
public static T GetAnsecstorOfType<T>(this IComponent component) where T : class, IComponent
314342
{
315343
var parent = component.Parent;
316344
while(parent!=null)
@@ -323,7 +351,7 @@ public static T GetAnsecstorOfType<T>(this IComponent component) where T : class
323351
}
324352
throw new MissingComponentException($"Ancestor of type :{typeof(T)} could not be located");
325353
}
326-
354+
327355
/// <summary>
328356
/// Try to get an ancestor component of type T
329357
/// </summary>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Runtime.Serialization;
2+
3+
namespace Pixel.Automation.Core.Models
4+
{
5+
/// <summary>
6+
/// Tracks usage of a control across fixtures and test cases
7+
/// </summary>
8+
[DataContract]
9+
public class ControlUsage
10+
{
11+
/// <summary>
12+
/// Identifier of Control
13+
/// </summary>
14+
[DataMember(IsRequired = true, Order = 10)]
15+
public string ControlId { get; set; }
16+
17+
/// <summary>
18+
/// Reference count
19+
/// </summary>
20+
[DataMember(IsRequired = true, Order = 20)]
21+
public int Count { get; set; }
22+
}
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.Runtime.Serialization;
2+
3+
namespace Pixel.Automation.Core.Models
4+
{
5+
/// <summary>
6+
/// Tracks usage of a prefab across fixtures and test cases
7+
/// </summary>
8+
[DataContract]
9+
public class PrefabUsage
10+
{
11+
/// <summary>
12+
/// Identifier of Prefab
13+
/// </summary>
14+
[DataMember(IsRequired = true, Order = 10)]
15+
public string PrefabId { get; set; }
16+
17+
/// <summary>
18+
/// Reference count
19+
/// </summary>
20+
[DataMember(IsRequired = true, Order = 20)]
21+
public int Count { get; set; }
22+
}
23+
}

src/Pixel.Automation.Core/TestData/TestCase.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using Pixel.Automation.Core.Attributes;
22
using Pixel.Automation.Core.Enums;
3+
using Pixel.Automation.Core.Models;
34
using System;
5+
using System.Collections.Generic;
46
using System.Runtime.Serialization;
57
using System.Text.Json.Serialization;
68

@@ -81,6 +83,18 @@ public class TestCase : ICloneable
8183
[DataMember(IsRequired = true, Order = 110)]
8284
public TagCollection Tags { get; private set; } = new TagCollection();
8385

86+
/// <summary>
87+
/// Collection of Identifiers of controls used by the test case
88+
/// </summary>
89+
[DataMember(IsRequired = true, Order = 120)]
90+
public List<ControlUsage> ControlsUsed { get; private set; } = new();
91+
92+
/// <summary>
93+
/// Collection of Identifiers of Prefabs used by the test case
94+
/// </summary>
95+
[DataMember(IsRequired = true, Order = 130)]
96+
public List<PrefabUsage> PrefabsUsed { get; private set; } = new();
97+
8498
/// <summary>
8599
/// Indicates if the TestCase is deleted. Deleted test cases are not loaded in explorer.
86100
/// </summary>

src/Pixel.Automation.Core/TestData/TestFixture.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Pixel.Automation.Core.Attributes;
2+
using Pixel.Automation.Core.Models;
23
using System;
34
using System.Collections.Generic;
45
using System.Runtime.Serialization;
@@ -66,6 +67,18 @@ public class TestFixture
6667
[DataMember(IsRequired = true, Order = 90)]
6768
public TagCollection Tags { get; private set; } = new TagCollection();
6869

70+
/// <summary>
71+
/// Collection of Identifiers of controls used by the test fixture
72+
/// </summary>
73+
[DataMember(IsRequired = true, Order = 120)]
74+
public List<ControlUsage> ControlsUsed { get; private set; } = new();
75+
76+
/// <summary>
77+
/// Collection of Identifiers of Prefabs used by the test fixture
78+
/// </summary>
79+
[DataMember(IsRequired = true, Order = 130)]
80+
public List<PrefabUsage> PrefabsUsed { get; private set; } = new();
81+
6982
/// <summary>
7083
/// Indicates if the fixture was deleted. Deleted fixtures are not loaded in explorer.
7184
/// </summary>

src/Pixel.Automation.Designer.ViewModels/AutomationBuilder/EditorViewModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using Pixel.Automation.Editor.Core.Interfaces;
1010
using Pixel.Automation.Editor.Core.ViewModels;
1111
using Serilog;
12-
using System.IO;
1312
using System.Windows;
1413
using IDropTarget = GongSolutions.Wpf.DragDrop.IDropTarget;
1514
using MessageBox = System.Windows.MessageBox;
@@ -94,7 +93,8 @@ public async Task DeleteComponent(ComponentViewModel componentViewModel)
9493
scripts = this.scriptExtractor.ExtractScripts(componentViewModel.Model).ToList();
9594
}
9695

97-
var deleteScriptsViewModel = new DeleteComponentViewModel(componentViewModel, scripts ?? Enumerable.Empty<ScriptStatus>(), projectManager);
96+
var deleteScriptsViewModel = new DeleteComponentViewModel(componentViewModel, scripts ?? Enumerable.Empty<ScriptStatus>(),
97+
this.projectManager, this.globalEventAggregator);
9898
var result = await this.windowManager.ShowDialogAsync(deleteScriptsViewModel);
9999
if (!result.GetValueOrDefault())
100100
{

0 commit comments

Comments
 (0)