diff --git a/src/TestCentric/testcentric.gui/Presenters/NUnitTreeDisplayStrategy.cs b/src/TestCentric/testcentric.gui/Presenters/NUnitTreeDisplayStrategy.cs
index 99f2f328..df3b49e4 100644
--- a/src/TestCentric/testcentric.gui/Presenters/NUnitTreeDisplayStrategy.cs
+++ b/src/TestCentric/testcentric.gui/Presenters/NUnitTreeDisplayStrategy.cs
@@ -38,7 +38,8 @@ public override void OnTestLoaded(TestNode testNode, VisualState visualState)
_foldedNodeNames.Clear();
foreach (var topLevelNode in testNode.Children)
- _view.Add(CreateNUnitTreeNode(null, topLevelNode));
+ if (topLevelNode.IsVisible)
+ _view.Add(CreateNUnitTreeNode(null, topLevelNode));
if (visualState != null)
visualState.ApplyTo(_view.TreeView);
@@ -81,9 +82,8 @@ private TreeNode CreateNUnitTreeNode(TreeNode parentNode, TestNode testNode)
}
foreach (TestNode child in testNode.Children)
- {
- CreateNUnitTreeNode(parentNode, child);
- }
+ if (child.IsVisible)
+ CreateNUnitTreeNode(parentNode, child);
return treeNode;
}
diff --git a/src/TestCentric/testcentric.gui/Presenters/TreeViewPresenter.cs b/src/TestCentric/testcentric.gui/Presenters/TreeViewPresenter.cs
index fa317fa8..cc5b1975 100644
--- a/src/TestCentric/testcentric.gui/Presenters/TreeViewPresenter.cs
+++ b/src/TestCentric/testcentric.gui/Presenters/TreeViewPresenter.cs
@@ -110,6 +110,11 @@ private void WireUpEvents()
Strategy.OnTestRunFinished();
};
+ _model.Events.TestFilterChanged += (ea) =>
+ {
+ Strategy?.Reload();
+ };
+
_model.Events.TestFinished += OnTestFinished;
_model.Events.SuiteFinished += OnTestFinished;
diff --git a/src/TestModel/model/ITestCentricTestFilter.cs b/src/TestModel/model/ITestCentricTestFilter.cs
new file mode 100644
index 00000000..7f193461
--- /dev/null
+++ b/src/TestModel/model/ITestCentricTestFilter.cs
@@ -0,0 +1,20 @@
+// ***********************************************************************
+// Copyright (c) Charlie Poole and TestCentric contributors.
+// Licensed under the MIT License. See LICENSE file in root directory.
+// ***********************************************************************
+
+using System.Collections.Generic;
+
+namespace TestCentric.Gui.Model
+{
+ ///
+ /// Provides filter functionality: by outcome, by duration, by category...
+ ///
+ public interface ITestCentricTestFilter
+ {
+ ///
+ /// Filters the loaded TestNodes by outcome
+ ///
+ IEnumerable OutcomeFilter { get; set; }
+ }
+}
diff --git a/src/TestModel/model/ITestEvents.cs b/src/TestModel/model/ITestEvents.cs
index 07cdc5c7..ff292040 100644
--- a/src/TestModel/model/ITestEvents.cs
+++ b/src/TestModel/model/ITestEvents.cs
@@ -61,5 +61,6 @@ public interface ITestEvents
event TestSelectionEventHandler SelectedTestsChanged;
event TestEventHandler CategorySelectionChanged;
+ event TestEventHandler TestFilterChanged;
}
}
diff --git a/src/TestModel/model/ITestModel.cs b/src/TestModel/model/ITestModel.cs
index b2dc78e7..b4fa22a8 100644
--- a/src/TestModel/model/ITestModel.cs
+++ b/src/TestModel/model/ITestModel.cs
@@ -88,6 +88,11 @@ public interface ITestModel : IDisposable
TestFilter CategoryFilter { get; }
+ ///
+ /// Provides filter functionality: by outcome, by duration, by category...
+ ///
+ ITestCentricTestFilter TestCentricTestFilter { get; }
+
#endregion
#region Methods
diff --git a/src/TestModel/model/TestCentricTestFilter.cs b/src/TestModel/model/TestCentricTestFilter.cs
new file mode 100644
index 00000000..76cd0afa
--- /dev/null
+++ b/src/TestModel/model/TestCentricTestFilter.cs
@@ -0,0 +1,84 @@
+// ***********************************************************************
+// Copyright (c) Charlie Poole and TestCentric contributors.
+// Licensed under the MIT License. See LICENSE file in root directory.
+// ***********************************************************************
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace TestCentric.Gui.Model
+{
+ internal class TestCentricTestFilter : ITestCentricTestFilter
+ {
+ // By default: all outcome filters are enabled
+ List _outcomeFilter = new List()
+ {
+ "Passed",
+ "Failed",
+ "Ignored",
+ "Skipped",
+ "Inconclusive",
+ "Not Run",
+ };
+
+ internal TestCentricTestFilter(TestModel model, Action filterChangedEvent)
+ {
+ TestModel = model;
+ FireFilterChangedEvent = filterChangedEvent;
+ }
+
+ private ITestModel TestModel { get; }
+
+ private Action FireFilterChangedEvent;
+
+ public IEnumerable OutcomeFilter
+ {
+ get => _outcomeFilter;
+
+ set
+ {
+ _outcomeFilter = value.ToList();
+ FilterNodes(TestModel.LoadedTests);
+ FireFilterChangedEvent();
+ }
+ }
+
+ private bool FilterNodes(TestNode testNode)
+ {
+ // 1. Check if any child is visible => parent must be visible too
+ bool childIsVisible = false;
+ foreach (TestNode child in testNode.Children)
+ if (FilterNodes(child))
+ childIsVisible = true;
+
+ // 2. Check if node itself is visible
+ bool isVisible = IsOutcomeFilterMatching(testNode);
+ testNode.IsVisible = isVisible || childIsVisible;
+ return testNode.IsVisible;
+ }
+
+ private bool IsOutcomeFilterMatching(TestNode testNode)
+ {
+ string outcome = "Not Run";
+
+ var result = TestModel.GetResultForTest(testNode.Id);
+ if (result != null)
+ {
+ switch (result.Outcome.Status)
+ {
+ case TestStatus.Failed:
+ case TestStatus.Passed:
+ case TestStatus.Inconclusive:
+ outcome = result.Outcome.Status.ToString();
+ break;
+ case TestStatus.Skipped:
+ outcome = result.Outcome.Label == "Ignored" ? "Ignored" : "Skippeed";
+ break;
+ }
+ }
+
+ return OutcomeFilter.Contains(outcome);
+ }
+ }
+}
diff --git a/src/TestModel/model/TestEventDispatcher.cs b/src/TestModel/model/TestEventDispatcher.cs
index a61a1860..9a5f3c15 100644
--- a/src/TestModel/model/TestEventDispatcher.cs
+++ b/src/TestModel/model/TestEventDispatcher.cs
@@ -124,6 +124,11 @@ public void FireCategorySelectionChanged()
CategorySelectionChanged?.Invoke(new TestEventArgs());
}
+ public void FireTestFilterChanged()
+ {
+ TestFilterChanged?.Invoke(new TestEventArgs());
+ }
+
#endregion
#region ITestEvents Implementation
@@ -164,6 +169,7 @@ public void FireCategorySelectionChanged()
public event TestSelectionEventHandler SelectedTestsChanged;
public event TestEventHandler CategorySelectionChanged;
+ public event TestEventHandler TestFilterChanged;
#endregion
diff --git a/src/TestModel/model/TestModel.cs b/src/TestModel/model/TestModel.cs
index 6cf0d057..e8b9a6b5 100644
--- a/src/TestModel/model/TestModel.cs
+++ b/src/TestModel/model/TestModel.cs
@@ -56,6 +56,7 @@ public TestModel(ITestEngine testEngine, CommandLineOptions options = null)
RecentFiles = new RecentFiles(_settingsService);
Services = new TestServices(testEngine);
+ TestCentricTestFilter = new TestCentricTestFilter(this, () => _events.FireTestFilterChanged());
AvailableAgents = new List(
Services.TestAgentService.GetAvailableAgents().Select((a) => a.AgentName));
@@ -207,6 +208,8 @@ public TestSelection SelectedTests
public TestFilter CategoryFilter { get; private set; } = TestFilter.Empty;
+ public ITestCentricTestFilter TestCentricTestFilter { get; private set; }
+
#endregion
#region Specifications passed as arguments to methods
diff --git a/src/TestModel/model/TestNode.cs b/src/TestModel/model/TestNode.cs
index 724e1e84..8a55cf80 100644
--- a/src/TestModel/model/TestNode.cs
+++ b/src/TestModel/model/TestNode.cs
@@ -33,6 +33,7 @@ public class TestNode : ITestItem
public TestNode(XmlNode xmlNode)
{
+ IsVisible = true;
Xml = xmlNode;
// It's a quirk of the test engine that the test-run element does;
@@ -76,6 +77,10 @@ public TestFilter GetTestFilter()
public bool IsAssembly => Type == "Assembly";
public bool IsProject => Type == "Project";
+ ///
+ /// Controls if the TestNode should be visible or hidden in the TestTree
+ ///
+ public bool IsVisible { get; set; }
public int TestCount => IsSuite ? GetAttribute("testcasecount", 0) : 1;
public RunState RunState => GetRunState();