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();