From d6d05daf9e8cf01762da7bb5f3fabe8842e6f3bb Mon Sep 17 00:00:00 2001 From: rowo360 <59574371+rowo360@users.noreply.github.com> Date: Thu, 5 Dec 2024 06:12:22 +0100 Subject: [PATCH 1/2] Extend filter functionality: support filtering by category and filtering by text --- src/TestModel/model/ITestCentricTestFilter.cs | 27 +- src/TestModel/model/TestCentricTestFilter.cs | 116 +++- .../tests/TestCentricTestFilterTests.cs | 506 ++++++++++++++++++ 3 files changed, 633 insertions(+), 16 deletions(-) create mode 100644 src/TestModel/tests/TestCentricTestFilterTests.cs diff --git a/src/TestModel/model/ITestCentricTestFilter.cs b/src/TestModel/model/ITestCentricTestFilter.cs index 7f193461..d3efd5c4 100644 --- a/src/TestModel/model/ITestCentricTestFilter.cs +++ b/src/TestModel/model/ITestCentricTestFilter.cs @@ -13,8 +13,33 @@ namespace TestCentric.Gui.Model public interface ITestCentricTestFilter { /// - /// Filters the loaded TestNodes by outcome + /// Filters the loaded TestNodes by outcome (for example: 'Passed', 'Failed' or 'Not run') /// IEnumerable OutcomeFilter { get; set; } + + /// + /// Filters the loaded TestNodes by matching a text (for example: Namespace, Class name or test method name - filter is case insensitive) + /// + string TextFilter { get; set; } + + /// + /// Filters the loaded TestNodes by test categories. Use item 'No category' to filter for tests without any test category. + /// + IEnumerable CategoryFilter { get; set; } + + /// + /// Returns the list of available test categories defined in the loaded TestNodes + item 'No category' + /// + IEnumerable AllCategories { get; } + + /// + /// Clear all actives filters and reset them to default + /// + void ClearAllFilters(); + + /// + /// Init filter after a project is loaded + /// + void Init(); } } diff --git a/src/TestModel/model/TestCentricTestFilter.cs b/src/TestModel/model/TestCentricTestFilter.cs index 76cd0afa..d893dcd4 100644 --- a/src/TestModel/model/TestCentricTestFilter.cs +++ b/src/TestModel/model/TestCentricTestFilter.cs @@ -6,23 +6,23 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Xml; namespace TestCentric.Gui.Model { - internal class TestCentricTestFilter : ITestCentricTestFilter + public class TestCentricTestFilter : ITestCentricTestFilter { + public const string AllOutcome = "All"; + public const string NotRunOutcome = "Not Run"; + public const string NoCategory = "No category"; + + // By default: all outcome filters are enabled - List _outcomeFilter = new List() - { - "Passed", - "Failed", - "Ignored", - "Skipped", - "Inconclusive", - "Not Run", - }; - - internal TestCentricTestFilter(TestModel model, Action filterChangedEvent) + private List _outcomeFilter = new List() { AllOutcome }; + private string _textFilter = string.Empty; + private List _categoryFilter = new List(); + + public TestCentricTestFilter(ITestModel model, Action filterChangedEvent) { TestModel = model; FireFilterChangedEvent = filterChangedEvent; @@ -44,6 +44,48 @@ public IEnumerable OutcomeFilter } } + public IEnumerable CategoryFilter + { + get => _categoryFilter; + + set + { + _categoryFilter = value.ToList(); + FilterNodes(TestModel.LoadedTests); + FireFilterChangedEvent(); + } + } + + public string TextFilter + { + get { return _textFilter; } + set + { + _textFilter = value; + FilterNodes(TestModel.LoadedTests); + FireFilterChangedEvent(); + } + } + + public IEnumerable AllCategories { get; private set; } + + public void Init() + { + AllCategories = GetAllCategories(); + CategoryFilter = AllCategories; + } + + public void ClearAllFilters() + { + _outcomeFilter = new List() { AllOutcome }; + _categoryFilter = GetAllCategories(); + _textFilter = string.Empty; + + FilterNodes(TestModel.LoadedTests); + FireFilterChangedEvent(); + } + + private bool FilterNodes(TestNode testNode) { // 1. Check if any child is visible => parent must be visible too @@ -53,14 +95,51 @@ private bool FilterNodes(TestNode testNode) childIsVisible = true; // 2. Check if node itself is visible - bool isVisible = IsOutcomeFilterMatching(testNode); + bool isVisible = IsOutcomeFilterMatching(testNode) && IsTextFilterMatching(testNode) && IsCategoryMatching(testNode); testNode.IsVisible = isVisible || childIsVisible; return testNode.IsVisible; } + private bool IsTextFilterMatching(TestNode testNode) + { + if (string.IsNullOrEmpty(_textFilter)) + { + return true; + } + + return testNode.FullName.IndexOf(_textFilter, StringComparison.InvariantCultureIgnoreCase) > -1; + } + + private bool IsCategoryMatching(TestNode testNode) + { + if (CategoryFilter.Any() == false) + return false; + + string xpathExpression = "ancestor-or-self::*/properties/property[@name='Category']"; + + // 1. Get list of available categories at TestNode + IList categories = new List(); + foreach (XmlNode node in testNode.Xml.SelectNodes(xpathExpression)) + { + var groupName = node.Attributes["value"].Value; + if (!string.IsNullOrEmpty(groupName)) + categories.Add(groupName); + } + + if (categories.Any() == false) + categories.Add(NoCategory); + + // 2. Check if any filter category matches the available categories + return CategoryFilter.Intersect(categories).Any(); + } + private bool IsOutcomeFilterMatching(TestNode testNode) { - string outcome = "Not Run"; + // All kind of outcomes should be displayed (no outcome filtering) + if (OutcomeFilter.Contains(AllOutcome)) + return true; + + string outcome = NotRunOutcome; var result = TestModel.GetResultForTest(testNode.Id); if (result != null) @@ -73,12 +152,19 @@ private bool IsOutcomeFilterMatching(TestNode testNode) outcome = result.Outcome.Status.ToString(); break; case TestStatus.Skipped: - outcome = result.Outcome.Label == "Ignored" ? "Ignored" : "Skippeed"; + outcome = result.Outcome.Label == "Ignored" ? "Ignored" : "Skipped"; break; } } return OutcomeFilter.Contains(outcome); } + + private List GetAllCategories() + { + var items = TestModel.AvailableCategories; + var allCategories = items.Concat(new[] { NoCategory }); + return allCategories.ToList(); + } } } diff --git a/src/TestModel/tests/TestCentricTestFilterTests.cs b/src/TestModel/tests/TestCentricTestFilterTests.cs new file mode 100644 index 00000000..9ff6be29 --- /dev/null +++ b/src/TestModel/tests/TestCentricTestFilterTests.cs @@ -0,0 +1,506 @@ +// *********************************************************************** +// 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; +using NSubstitute; +using NUnit.Framework; +using NUnit.Framework.Internal; + +namespace TestCentric.Gui.Model +{ + [TestFixture] + internal class TestCentricTestFilterTests + { + private ITestModel _model; + + [SetUp] + public void Setup() + { + _model = Substitute.For(); + } + + [Test] + public void Set_OutcomeFilter_FilterChangedEvent_IsInvoked() + { + // Arrange + bool isCalled = false; + var testNode = new TestNode($""); + _model.LoadedTests.Returns(testNode); + + // Act + TestCentricTestFilter testFilter = new TestCentricTestFilter(_model, () => { isCalled = true; }); + testFilter.OutcomeFilter = new List(); + + // Assert + Assert.That(isCalled, Is.True); + } + + [Test] + public void Set_TextFilter_FilterChangedEvent_IsInvoked() + { + // Arrange + bool isCalled = false; + var testNode = new TestNode($""); + _model.LoadedTests.Returns(testNode); + + // Act + TestCentricTestFilter testFilter = new TestCentricTestFilter(_model, () => { isCalled = true; }); + testFilter.TextFilter = ""; + + // Assert + Assert.That(isCalled, Is.True); + } + + [Test] + public void Set_CategoryFilter_FilterChangedEvent_IsInvoked() + { + // Arrange + bool isCalled = false; + var testNode = new TestNode($""); + _model.LoadedTests.Returns(testNode); + + // Act + TestCentricTestFilter testFilter = new TestCentricTestFilter(_model, () => { isCalled = true; }); + testFilter.CategoryFilter = new List(); + + // Assert + Assert.That(isCalled, Is.True); + } + + [Test] + public void AllCategories_NoCategoriesDefinedInModel_ReturnsDefaultCategory() + { + // Arrange + var testNode = new TestNode($""); + _model.LoadedTests.Returns(testNode); + + // Act + TestCentricTestFilter testFilter = new TestCentricTestFilter(_model, () => { }); + testFilter.Init(); + var allCategories = testFilter.AllCategories; + + // Assert + Assert.That(allCategories.Count(), Is.EqualTo(1)); + Assert.That(allCategories, Contains.Item(TestCentricTestFilter.NoCategory)); + } + + [Test] + public void AllCategories_CategoriesDefinedInModel_ReturnsModelAndDefaultCategory() + { + // Arrange + var testNode = new TestNode($""); + _model.LoadedTests.Returns(testNode); + _model.AvailableCategories.Returns(new List() { "Feature_1" }); + + // Act + TestCentricTestFilter testFilter = new TestCentricTestFilter(_model, () => { }); + testFilter.Init(); + var allCategories = testFilter.AllCategories; + + // Assert + Assert.That(allCategories.Count(), Is.EqualTo(2)); + Assert.That(allCategories, Contains.Item("Feature_1")); + Assert.That(allCategories, Contains.Item(TestCentricTestFilter.NoCategory)); + } + + [Test] + public void ClearFilter_AllFiltersAreReset() + { + // Arrange + var testNode = new TestNode($""); + _model.LoadedTests.Returns(testNode); + _model.AvailableCategories.Returns(new List() { "Feature_1" }); + + TestCentricTestFilter testFilter = new TestCentricTestFilter(_model, () => { }); + testFilter.Init(); + testFilter.TextFilter = "TestA"; + testFilter.CategoryFilter = new List() { "Feature_1" }; + testFilter.OutcomeFilter = new List() { "Passed" }; + + // Act + testFilter.ClearAllFilters(); + + // Assert + var allCategories = testFilter.AllCategories; + Assert.That(allCategories.Count(), Is.EqualTo(2)); + Assert.That(allCategories, Contains.Item("Feature_1")); + Assert.That(allCategories, Contains.Item(TestCentricTestFilter.NoCategory)); + + var outcomeFilter = testFilter.OutcomeFilter; + Assert.That(outcomeFilter.Count, Is.EqualTo(1)); + Assert.That(outcomeFilter, Contains.Item(TestCentricTestFilter.AllOutcome)); + + Assert.That(testFilter.TextFilter, Is.Empty); + } + + private static object[] FilterByOutcomeTestCases = + { + new object[] { new List() { "Passed" }, new List() { "3-1000", "3-1001", "3-1010", "3-1011", "3-1012", "3-1020", "3-1022" } }, + new object[] { new List() { "Failed" }, new List() { "3-1000", "3-1001", "3-1020", "3-1021" } }, + new object[] { new List() { TestCentricTestFilter.NotRunOutcome }, new List() { "3-1000", "3-1001", "3-1030", "3-1031", "3-1032" } }, + new object[] { new List() { "Passed", TestCentricTestFilter.NotRunOutcome }, new List() { "3-1000", "3-1001", "3-1010", "3-1011", "3-1012", "3-1020", "3-1022", "3-1030", "3-1031", "3-1032" } }, + new object[] { new List() { "Passed", "Failed" }, new List() { "3-1000", "3-1001", "3-1010", "3-1011", "3-1012", "3-1020", "3-1022", "3-1020", "3-1021" } }, + new object[] { new List() { TestCentricTestFilter.NotRunOutcome, "Failed" }, new List() { "3-1000", "3-1001", "3-1030", "3-1031", "3-1032", "3-1020", "3-1021" } }, + new object[] { new List() { TestCentricTestFilter.AllOutcome }, new List() { "3-1000", "3-1001", "3-1010", "3-1011", "3-1012", "3-1020", "3-1021", "3-1022", "3-1030", "3-1031", "3-1032" } }, + }; + + [Test] + [TestCaseSource(nameof(FilterByOutcomeTestCases))] + public void FilterByOutcome_TestNodesAreVisible(IList outcomeFilter, IList expectedVisibleNodes) + { + // Arrange + TestNode testNode = new TestNode( + CreateTestSuiteXml("3-1000", "LibraryA", "Failed", + CreateTestSuiteXml("3-1001", "NamespaceA", "Failed", + CreateTestFixtureXml("3-1010", "Fixture_1", "Passed", + CreateTestcaseXml("3-1011", "TestA", "Passed"), + CreateTestcaseXml("3-1012", "TestB", "Passed")) + + CreateTestFixtureXml("3-1020", "Fixture_2", "Failed", + CreateTestcaseXml("3-1021", "TestA", "Failed"), + CreateTestcaseXml("3-1022", "TestB", "Passed")) + + CreateTestFixtureXml("3-1030", "Fixture_3", "", + CreateTestcaseXml("3-1031", "TestA", ""), + CreateTestcaseXml("3-1032", "TestB", ""))))); + _model.LoadedTests.Returns(testNode); + + // Act + TestCentricTestFilter testFilter = new TestCentricTestFilter(_model, () => { }); + testFilter.Init(); + testFilter.OutcomeFilter = outcomeFilter; + + // Assert + foreach (string testId in expectedVisibleNodes) + { + TestNode node = GetTestNode(testNode, testId); + Assert.That(node.IsVisible, Is.True); + } + } + + private static object[] FilterByOutcomeNamespacesTestCases = +{ + new object[] { new List() { "Passed" }, new List() { "3-1000", "3-1000", "3-1110", "3-1111", "3-1112", "3-1200", "3-1210", "3-1212" } }, + new object[] { new List() { "Failed" }, new List() { "3-1000", "3-1200", "3-1210", "3-1211", "3-1400", "3-1411" } }, + new object[] { new List() { TestCentricTestFilter.NotRunOutcome }, new List() { "3-1000", "3-1300", "3-1310", "3-1311", "3-1400", "3-1410", "3-1412" } }, + new object[] { new List() { "Passed", TestCentricTestFilter.NotRunOutcome }, new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1212", "3-1300", "3-1310", "3-1311", "3-1312", "3-1400", "3-1410", "3-1412" } }, + new object[] { new List() { "Passed", "Failed" }, new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1210", "3-1211", "3-1212", "3-1400", "3-1411" } }, + new object[] { new List() { TestCentricTestFilter.NotRunOutcome, "Failed" }, new List() { "3-1000", "3-1200", "3-1210", "3-1211", "3-1300", "3-1310", "3-1311", "3-1312", "3-1400", "3-1410", "3-1411", "3-1412" } }, + new object[] { new List() { TestCentricTestFilter.AllOutcome }, new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1210", "3-1211", "3-1212", "3-1300", "3-1310", "3-1311", "3-1312", "3-1400", "3-1410", "3-1411", "3-1412" } }, + }; + + [Test] + [TestCaseSource(nameof(FilterByOutcomeNamespacesTestCases))] + public void FilterByOutcome_Namespaces_TestNodesAreVisible(IList outcomeFilter, IList expectedVisibleNodes) + { + // Arrange + TestNode testNode = new TestNode( + CreateTestSuiteXml("3-1000", "LibraryA", "Failed", + CreateTestSuiteXml("3-1100", "NamespaceA", "Passed", + CreateTestFixtureXml("3-1110", "Fixture_1", "Passed", + CreateTestcaseXml("3-1111", "TestA", "Passed"), + CreateTestcaseXml("3-1112", "TestB", "Passed"))) + + CreateTestSuiteXml("3-1200", "NamespaceB", "Failed", + CreateTestFixtureXml("3-1210", "Fixture_2", "Failed", + CreateTestcaseXml("3-1211", "TestA", "Failed"), + CreateTestcaseXml("3-1212", "TestB", "Passed"))) + + CreateTestSuiteXml("3-1300", "NamespaceC", "", + CreateTestFixtureXml("3-1310", "Fixture_1", "", + CreateTestcaseXml("3-1311", "TestA", ""), + CreateTestcaseXml("3-1312", "TestB", ""))) + + CreateTestSuiteXml("3-1400", "NamespaceD", "Failed", + CreateTestFixtureXml("3-1410", "Fixture_2", "", + CreateTestcaseXml("3-1411", "TestC", "Failed"), + CreateTestcaseXml("3-1412", "TestD", ""))))); + _model.LoadedTests.Returns(testNode); + + // Act + TestCentricTestFilter testFilter = new TestCentricTestFilter(_model, () => { }); + testFilter.Init(); + testFilter.OutcomeFilter = outcomeFilter; + + // Assert + foreach (string testId in expectedVisibleNodes) + { + TestNode node = GetTestNode(testNode, testId); + Assert.That(node.IsVisible, Is.True); + } + } + + private static object[] FilterByTextTestCases = + { + new object[] { "NamespaceA", new List() { "3-1000", "3-1000", "3-1110", "3-1111", "3-1112" } }, + new object[] { "TestA", new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1200", "3-1210", "3-1211", "3-1300", "3-1310", "3-1311" } }, + new object[] { "TestC", new List() { "3-1000", "3-1300", "3-1320", "3-1321"} }, + new object[] { "_1", new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1300", "3-1310", "3-1311", "3-1312", } }, + new object[] { "aryA", new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1210", "3-1211", "3-1212", "3-1300", "3-1310", "3-1311", "3-1312", "3-1320", "3-1321", "3-1322" } }, + new object[] { "Namespace", new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1210", "3-1211", "3-1212", "3-1300", "3-1310", "3-1311", "3-1312", "3-1320", "3-1321", "3-1322" } }, + new object[] { "Fixture", new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1210", "3-1211", "3-1212", "3-1300", "3-1310", "3-1311", "3-1312", "3-1320", "3-1321", "3-1322" } }, + new object[] { "", new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1210", "3-1211", "3-1212", "3-1300", "3-1310", "3-1311", "3-1312", "3-1320", "3-1321", "3-1322" } }, + }; + + [Test] + [TestCaseSource(nameof(FilterByTextTestCases))] + public void FilterByText_TestNodesAreVisible(string textFilter, IList expectedVisibleNodes) + { + // Arrange + TestNode testNode = new TestNode( + CreateTestSuiteXml("3-1000", "LibraryA", "Failed", + CreateTestSuiteXml("3-1100", "LibraryA.NamespaceA", "Passed", + CreateTestFixtureXml("3-1110", "LibraryA.NamespaceA.Fixture_1", "Passed", + CreateTestcaseXml("3-1111", "LibraryA.NamespaceA.Fixture_1.TestA", "Passed"), + CreateTestcaseXml("3-1112", "LibraryA.NamespaceA.Fixture_1.TestB", "Passed"))) + + CreateTestSuiteXml("3-1200", "LibraryA.NamespaceB", "Failed", + CreateTestFixtureXml("3-1210", "LibraryA.NamespaceB.Fixture_2", "Failed", + CreateTestcaseXml("3-1211", "LibraryA.NamespaceB.Fixture_2.TestA", "Failed"), + CreateTestcaseXml("3-1212", "LibraryA.NamespaceB.Fixture_2.TestB", "Passed"))) + + CreateTestSuiteXml("3-1300", "LibraryA.NamespaceC", "", + CreateTestFixtureXml("3-1310", "LibraryA.NamespaceC.Fixture_1", "", + CreateTestcaseXml("3-1311", "LibraryA.NamespaceC.Fixture_1.TestA", ""), + CreateTestcaseXml("3-1312", "LibraryA.NamespaceC.Fixture_1.TestB", "")) + + CreateTestFixtureXml("3-1320", "LibraryA.NamespaceC.Fixture_2", "", + CreateTestcaseXml("3-1321", "LibraryA.NamespaceC.Fixture_2.TestC", "Failed"), + CreateTestcaseXml("3-1322", "LibraryA.NamespaceC.Fixture_2.TestD", ""))))); + _model.LoadedTests.Returns(testNode); + + // Act + TestCentricTestFilter testFilter = new TestCentricTestFilter(_model, () => { }); + testFilter.Init(); + testFilter.TextFilter = textFilter; + + // Assert + foreach (string testId in expectedVisibleNodes) + { + TestNode node = GetTestNode(testNode, testId); + Assert.That(node.IsVisible, Is.True); + } + } + + private static object[] FilterByCategoryTestCases = + { + new object[] { new[] { "Category_1" }, new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1300", "3-1310", "3-1311", "3-1312" } }, + new object[] { new[] { "Category_1", "Category_2" }, new List() { "3-1100", "3-1000", "3-1110", "3-1111", "3-1112", "3-1200", "3-1211", "3-1300", "3-1311", "3-1312" } }, + new object[] { new[] { "Category_2" }, new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1211", "3-1300", "3-1312" } }, + new object[] { new[] { "Category_3" }, new List() { "3-1000", "3-1300", "3-1320", "3-1321" } }, + new object[] { new[] { "Category_3", TestCentricTestFilter.NoCategory }, new List() { "3-1000", "3-1200", "3-1210", "3-1212", "3-1300", "3-1320", "3-1321", "3-1322" } }, + new object[] { new[] { TestCentricTestFilter.NoCategory }, new List() { "3-1000", "3-1200", "3-1210", "3-1212", "3-1300", "3-1320", "3-1322" } }, + }; + + [Test] + [TestCaseSource(nameof(FilterByCategoryTestCases))] + public void FilterByCategory_TestNodesAreVisible(IList categoryFilter, IList expectedVisibleNodes) + { + // Arrange + TestNode testNode = new TestNode( + CreateTestSuiteXml("3-1000", "LibraryA", "Failed", + CreateTestSuiteXml("3-1100", "LibraryA.NamespaceA", "Passed", + CreateTestFixtureXml("3-1110", "LibraryA.NamespaceA.Fixture_1", "Passed", new[] { "Category_1", "Category_2" }, + CreateTestcaseXml("3-1111", "LibraryA.NamespaceA.Fixture_1.TestA", "Passed"), + CreateTestcaseXml("3-1112", "LibraryA.NamespaceA.Fixture_1.TestB", "Passed"))) + + CreateTestSuiteXml("3-1200", "LibraryA.NamespaceB", "Failed", + CreateTestFixtureXml("3-1210", "LibraryA.NamespaceB.Fixture_2", "Failed", + CreateTestcaseXml("3-1211", "LibraryA.NamespaceB.Fixture_2.TestA", "Failed", new[] { "Category_2" }), + CreateTestcaseXml("3-1212", "LibraryA.NamespaceB.Fixture_2.TestB", "Passed"))) + + CreateTestSuiteXml("3-1300", "LibraryA.NamespaceC", "", + CreateTestFixtureXml("3-1310", "LibraryA.NamespaceC.Fixture_1", "", new[] { "Category_1" }, + CreateTestcaseXml("3-1311", "LibraryA.NamespaceC.Fixture_1.TestA", ""), + CreateTestcaseXml("3-1312", "LibraryA.NamespaceC.Fixture_1.TestB", "", new[] { "Category_2" })) + + CreateTestFixtureXml("3-1320", "LibraryA.NamespaceC.Fixture_2", "", + CreateTestcaseXml("3-1321", "LibraryA.NamespaceC.Fixture_2.TestC", "Failed", new[] { "Category_3" }), + CreateTestcaseXml("3-1322", "LibraryA.NamespaceC.Fixture_2.TestD", ""))))); + _model.LoadedTests.Returns(testNode); + + // Act + TestCentricTestFilter testFilter = new TestCentricTestFilter(_model, () => { }); + testFilter.Init(); + testFilter.CategoryFilter= categoryFilter; + + // Assert + foreach (string testId in expectedVisibleNodes) + { + TestNode node = GetTestNode(testNode, testId); + Assert.That(node.IsVisible, Is.True); + } + } + + private static object[] FilterByCategoryCategoryAndTextTestCases = + { + new object[] { new[] { "Category_1" }, new[] { "Passed" }, "", new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112" } }, + new object[] { new[] { "Category_2" }, new[] { "Failed" }, "", new List() { "3-1000", "3-1200", "3-1211" } }, + new object[] { new[] { "Category_2" }, new[] { TestCentricTestFilter.NotRunOutcome }, "", new List() { "3-1000", "3-1300", "3-1312" } }, + new object[] { new[] { "Category_2" }, new[] { TestCentricTestFilter.AllOutcome }, "", new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1211", "3-1300", "3-1312" } }, + new object[] { new[] { "Category_2" }, new[] { "Passed", "Failed" }, "TestB", new List() { "3-1000", "3-1100", "3-1110", "3-1112" } }, + new object[] { new[] { "Category_1" }, new[] { TestCentricTestFilter.AllOutcome }, "NamespaceC", new List() { "3-1000", "3-1300", "3-1311", "3-1312" } }, + new object[] { new[] { "Category_1", "Category_2"}, new[] { "Failed" }, "TestA", new List() { "3-1000", "3-1200", "3-1210", "3-1211" } }, + new object[] { new[] { "Category_3" }, new[] { "Failed" }, "TestC", new List() { "3-1000", "3-1300", "3-1320", "3-1321" } }, + new object[] { new[] { TestCentricTestFilter.NoCategory }, new[] { TestCentricTestFilter.NotRunOutcome }, "NamespaceC", new List() { "3-1000", "3-1300", "3-1320", "3-1322" } }, + }; + + [Test] + [TestCaseSource(nameof(FilterByCategoryCategoryAndTextTestCases))] + public void FilterByOutcomeCategoryAndText_TestNodesAreVisible(IList categoryFilter, IList outcomeFilter, string textFilter, IList expectedVisibleNodes) + { + // Arrange + TestNode testNode = new TestNode( + CreateTestSuiteXml("3-1000", "LibraryA", "Failed", + CreateTestSuiteXml("3-1100", "LibraryA.NamespaceA", "Passed", + CreateTestFixtureXml("3-1110", "LibraryA.NamespaceA.Fixture_1", "Passed", new[] { "Category_1", "Category_2" }, + CreateTestcaseXml("3-1111", "LibraryA.NamespaceA.Fixture_1.TestA", "Passed"), + CreateTestcaseXml("3-1112", "LibraryA.NamespaceA.Fixture_1.TestB", "Passed"))) + + CreateTestSuiteXml("3-1200", "LibraryA.NamespaceB", "Failed", + CreateTestFixtureXml("3-1210", "LibraryA.NamespaceB.Fixture_2", "Failed", + CreateTestcaseXml("3-1211", "LibraryA.NamespaceB.Fixture_2.TestA", "Failed", new[] { "Category_2" }), + CreateTestcaseXml("3-1212", "LibraryA.NamespaceB.Fixture_2.TestB", "Passed"))) + + CreateTestSuiteXml("3-1300", "LibraryA.NamespaceC", "", + CreateTestFixtureXml("3-1310", "LibraryA.NamespaceC.Fixture_1", "", new[] { "Category_1" }, + CreateTestcaseXml("3-1311", "LibraryA.NamespaceC.Fixture_1.TestA", ""), + CreateTestcaseXml("3-1312", "LibraryA.NamespaceC.Fixture_1.TestB", "", new[] { "Category_2" })) + + CreateTestFixtureXml("3-1320", "LibraryA.NamespaceC.Fixture_2", "", + CreateTestcaseXml("3-1321", "LibraryA.NamespaceC.Fixture_2.TestC", "Failed", new[] { "Category_3" }), + CreateTestcaseXml("3-1322", "LibraryA.NamespaceC.Fixture_2.TestD", ""))))); + _model.LoadedTests.Returns(testNode); + + // Act + TestCentricTestFilter testFilter = new TestCentricTestFilter(_model, () => { }); + testFilter.Init(); + testFilter.CategoryFilter = categoryFilter; + testFilter.OutcomeFilter = outcomeFilter; + testFilter.TextFilter = textFilter; + + // Assert + foreach (string testId in expectedVisibleNodes) + { + TestNode node = GetTestNode(testNode, testId); + Assert.That(node.IsVisible, Is.True); + } + } + + private static object[] FilterByCategoryCategoryAndTextAllInvisibleTestCases = +{ + new object[] { new[] { "Category_XY" }, new[] { TestCentricTestFilter.AllOutcome }, ""}, + new object[] { new[] { "Category_1" }, new[] { "Passed", "Failed" }, "NamespaceXY"}, + new object[] { new[] { "Category_3" }, new[] { TestCentricTestFilter.NotRunOutcome }, ""}, + new object[] { new[] { TestCentricTestFilter.NoCategory }, new[] { TestCentricTestFilter.NotRunOutcome }, "TestC"}, + new object[] { new[] { "Category_2" }, new[] { "Failed" }, "TestB"}, + }; + + [Test] + [TestCaseSource(nameof(FilterByCategoryCategoryAndTextAllInvisibleTestCases))] + public void FilterByOutcomeCategoryAndText_AllNodesAreInvisible(IList categoryFilter, IList outcomeFilter, string textFilter) + { + // Arrange + TestNode testNode = new TestNode( + CreateTestSuiteXml("3-1000", "LibraryA", "Failed", + CreateTestSuiteXml("3-1100", "LibraryA.NamespaceA", "Passed", + CreateTestFixtureXml("3-1110", "LibraryA.NamespaceA.Fixture_1", "Passed", new[] { "Category_1", "Category_2" }, + CreateTestcaseXml("3-1111", "LibraryA.NamespaceA.Fixture_1.TestA", "Passed"), + CreateTestcaseXml("3-1112", "LibraryA.NamespaceA.Fixture_1.TestB", "Passed"))) + + CreateTestSuiteXml("3-1200", "LibraryA.NamespaceB", "Failed", + CreateTestFixtureXml("3-1210", "LibraryA.NamespaceB.Fixture_2", "Failed", + CreateTestcaseXml("3-1211", "LibraryA.NamespaceB.Fixture_2.TestA", "Failed", new[] { "Category_2" }), + CreateTestcaseXml("3-1212", "LibraryA.NamespaceB.Fixture_2.TestB", "Passed"))) + + CreateTestSuiteXml("3-1300", "LibraryA.NamespaceC", "", + CreateTestFixtureXml("3-1310", "LibraryA.NamespaceC.Fixture_1", "", new[] { "Category_1" }, + CreateTestcaseXml("3-1311", "LibraryA.NamespaceC.Fixture_1.TestA", ""), + CreateTestcaseXml("3-1312", "LibraryA.NamespaceC.Fixture_1.TestB", "", new[] { "Category_2" })) + + CreateTestFixtureXml("3-1320", "LibraryA.NamespaceC.Fixture_2", "", + CreateTestcaseXml("3-1321", "LibraryA.NamespaceC.Fixture_2.TestC", "Failed", new[] { "Category_3" }), + CreateTestcaseXml("3-1322", "LibraryA.NamespaceC.Fixture_2.TestD", ""))))); + _model.LoadedTests.Returns(testNode); + + // Act + TestCentricTestFilter testFilter = new TestCentricTestFilter(_model, () => { }); + testFilter.Init(); + testFilter.CategoryFilter = categoryFilter; + testFilter.OutcomeFilter = outcomeFilter; + testFilter.TextFilter = textFilter; + + // Assert + AssertTestNodeIsInvisible(testNode); + } + + private void AssertTestNodeIsInvisible(TestNode testNode) + { + Assert.That(testNode.IsVisible, Is.False, $"TestNode {testNode.Id} is not invisible."); + foreach (TestNode child in testNode.Children) + AssertTestNodeIsInvisible(child); + } + + private TestNode GetTestNode(TestNode testNode, string testId) + { + if (testNode.Id == testId) + return testNode; + + foreach (TestNode child in testNode.Children) + { + TestNode n = GetTestNode(child, testId); + if (n != null) + return n; + } + + return null; + } + + private string CreateTestcaseXml(string testId, string testName, string outcome) + { + return CreateTestcaseXml(testId, testName, outcome, new List()); + } + + private string CreateTestcaseXml(string testId, string testName, string outcome, IList categories) + { + string str = $" "; + + str += " "; + foreach (string category in categories) + str += $" "; + str += " "; + + str += " "; + + if (!string.IsNullOrEmpty(outcome)) + _model.GetResultForTest(testId).Returns(new ResultNode($"")); + return str; + } + + private string CreateTestFixtureXml(string testId, string testName, string outcome, params string[] testCases) + { + return CreateTestFixtureXml(testId, testName, outcome, new List(), testCases); + } + + private string CreateTestFixtureXml(string testId, string testName, string outcome, IEnumerable categories, params string[] testCases) + { + string str = $" "; + + str += " "; + foreach (string category in categories) + str += $" "; + str += " "; + + foreach (string testCase in testCases) + str += testCase; + + str += ""; + + if (!string.IsNullOrEmpty(outcome)) + _model.GetResultForTest(testId).Returns(new ResultNode($"")); + + return str; + } + + private string CreateTestSuiteXml(string testId, string testName, string outcome, params string[] testSuites) + { + string str = $" "; + foreach (string testSuite in testSuites) + str += testSuite; + + str += ""; + + if (!string.IsNullOrEmpty(outcome)) + _model.GetResultForTest(testId).Returns(new ResultNode($"")); + + return str; + } + } +} From 9e061a093a0afd5bc0579416ff2a7f7878ed59ca Mon Sep 17 00:00:00 2001 From: rowo360 <59574371+rowo360@users.noreply.github.com> Date: Fri, 6 Dec 2024 08:32:09 +0100 Subject: [PATCH 2/2] Refactor TestCentricTestFilter class: introduce separate TestFilter classes, each one dedicated to one single filter condition --- src/TestModel/model/Filter/CategoryFilter.cs | 79 ++++++++ .../{ => Filter}/ITestCentricTestFilter.cs | 2 +- src/TestModel/model/Filter/ITestFilter.cs | 40 +++++ src/TestModel/model/Filter/OutcomeFilter.cs | 73 ++++++++ .../model/Filter/TestCentricTestFilter.cs | 110 ++++++++++++ src/TestModel/model/Filter/TextFilter.cs | 47 +++++ src/TestModel/model/ITestModel.cs | 1 + src/TestModel/model/TestCentricTestFilter.cs | 170 ------------------ src/TestModel/model/TestModel.cs | 1 + .../tests/TestCentricTestFilterTests.cs | 43 ++--- 10 files changed, 374 insertions(+), 192 deletions(-) create mode 100644 src/TestModel/model/Filter/CategoryFilter.cs rename src/TestModel/model/{ => Filter}/ITestCentricTestFilter.cs (97%) create mode 100644 src/TestModel/model/Filter/ITestFilter.cs create mode 100644 src/TestModel/model/Filter/OutcomeFilter.cs create mode 100644 src/TestModel/model/Filter/TestCentricTestFilter.cs create mode 100644 src/TestModel/model/Filter/TextFilter.cs delete mode 100644 src/TestModel/model/TestCentricTestFilter.cs diff --git a/src/TestModel/model/Filter/CategoryFilter.cs b/src/TestModel/model/Filter/CategoryFilter.cs new file mode 100644 index 00000000..52e2ee99 --- /dev/null +++ b/src/TestModel/model/Filter/CategoryFilter.cs @@ -0,0 +1,79 @@ +// *********************************************************************** +// Copyright (c) Charlie Poole and TestCentric contributors. +// Licensed under the MIT License. See LICENSE file in root directory. +// *********************************************************************** + +using System.Collections.Generic; +using System.Linq; +using System.Xml; + +namespace TestCentric.Gui.Model.Filter +{ + /// + /// Filters the TestNodes by test categories. Use item 'No category' to filter for tests without any test category. + /// + public class CategoryFilter : ITestFilter + { + public const string NoCategory = "No category"; + + private List _condition = new List(); + + internal CategoryFilter(ITestModel model) + { + TestModel = model; + } + + private ITestModel TestModel { get; } + + public string FilterId => "CategoryFilter"; + + public IEnumerable Condition + { + get { return _condition; } + set { _condition = value.ToList(); } + } + + public IEnumerable AllCategories { get; private set; } + + public bool IsMatching(TestNode testNode) + { + if (_condition.Any() == false) + return false; + + string xpathExpression = "ancestor-or-self::*/properties/property[@name='Category']"; + + // 1. Get list of available categories at TestNode + IList categories = new List(); + foreach (XmlNode node in testNode.Xml.SelectNodes(xpathExpression)) + { + var groupName = node.Attributes["value"].Value; + if (!string.IsNullOrEmpty(groupName)) + categories.Add(groupName); + } + + if (categories.Any() == false) + categories.Add(NoCategory); + + // 2. Check if any filter category matches the available categories + return _condition.Intersect(categories).Any(); + } + + public void Reset() + { + _condition = GetAllCategories(); + } + + public void Init() + { + AllCategories = GetAllCategories(); + _condition = AllCategories.ToList(); + } + + private List GetAllCategories() + { + var items = TestModel.AvailableCategories; + var allCategories = items.Concat(new[] { NoCategory }); + return allCategories.ToList(); + } + } +} diff --git a/src/TestModel/model/ITestCentricTestFilter.cs b/src/TestModel/model/Filter/ITestCentricTestFilter.cs similarity index 97% rename from src/TestModel/model/ITestCentricTestFilter.cs rename to src/TestModel/model/Filter/ITestCentricTestFilter.cs index d3efd5c4..fd635f2a 100644 --- a/src/TestModel/model/ITestCentricTestFilter.cs +++ b/src/TestModel/model/Filter/ITestCentricTestFilter.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; -namespace TestCentric.Gui.Model +namespace TestCentric.Gui.Model.Filter { /// /// Provides filter functionality: by outcome, by duration, by category... diff --git a/src/TestModel/model/Filter/ITestFilter.cs b/src/TestModel/model/Filter/ITestFilter.cs new file mode 100644 index 00000000..98981036 --- /dev/null +++ b/src/TestModel/model/Filter/ITestFilter.cs @@ -0,0 +1,40 @@ +// *********************************************************************** +// 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.Filter +{ + /// + /// Interface for all test filters + /// + internal interface ITestFilter + { + /// + /// Unqiue identifier of the filter + /// + string FilterId { get; } + + /// + /// The filter condition + /// + IEnumerable Condition { get; set; } + + /// + /// Reset the filter condition to its default state + /// + void Reset(); + + /// + /// Init filter after a project is loaded + /// + void Init(); + + /// + /// Checks if the testNode matches the filter condition + /// + bool IsMatching(TestNode testNode); + } +} diff --git a/src/TestModel/model/Filter/OutcomeFilter.cs b/src/TestModel/model/Filter/OutcomeFilter.cs new file mode 100644 index 00000000..a27eee7f --- /dev/null +++ b/src/TestModel/model/Filter/OutcomeFilter.cs @@ -0,0 +1,73 @@ +// *********************************************************************** +// Copyright (c) Charlie Poole and TestCentric contributors. +// Licensed under the MIT License. See LICENSE file in root directory. +// *********************************************************************** + +using System.Collections.Generic; +using System.Linq; + +namespace TestCentric.Gui.Model.Filter +{ + /// + /// Filters the TestNodes by outcome (for example: 'Passed', 'Failed' or 'Not run') + /// + public class OutcomeFilter : ITestFilter + { + public const string AllOutcome = "All"; + public const string NotRunOutcome = "Not Run"; + + private List _condition = new List() { AllOutcome }; + + internal OutcomeFilter(ITestModel model) + { + TestModel = model; + } + + public string FilterId => "OutcomeFilter"; + + private ITestModel TestModel { get; } + + public IEnumerable Condition + { + get { return _condition; } + set { _condition = value.ToList(); } + } + + public bool IsMatching(TestNode testNode) + { + // All kind of outcomes should be displayed (no outcome filtering) + if (_condition.Contains(AllOutcome)) + return true; + + string outcome = NotRunOutcome; + + 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" : "Skipped"; + break; + } + } + + return _condition.Contains(outcome); + } + + public void Reset() + { + _condition = new List() { AllOutcome }; + } + + public void Init() + { + _condition = new List() { AllOutcome }; + } + } +} diff --git a/src/TestModel/model/Filter/TestCentricTestFilter.cs b/src/TestModel/model/Filter/TestCentricTestFilter.cs new file mode 100644 index 00000000..b1b7b5f0 --- /dev/null +++ b/src/TestModel/model/Filter/TestCentricTestFilter.cs @@ -0,0 +1,110 @@ +// *********************************************************************** +// 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.Filter +{ + public class TestCentricTestFilter : ITestCentricTestFilter + { + private List _filters = new List(); + + public TestCentricTestFilter(ITestModel model, Action filterChangedEvent) + { + TestModel = model; + FireFilterChangedEvent = filterChangedEvent; + + _filters.Add(new OutcomeFilter(model)); + _filters.Add(new TextFilter()); + _filters.Add(new CategoryFilter(model)); + } + + private ITestModel TestModel { get; } + + private Action FireFilterChangedEvent; + + public IEnumerable OutcomeFilter + { + get => GetFilterCondition("OutcomeFilter"); + set => SetFilterCondition("OutcomeFilter", value); + } + + public IEnumerable CategoryFilter + { + get => GetFilterCondition("CategoryFilter"); + + set => SetFilterCondition("CategoryFilter", value); + } + + public string TextFilter + { + get => GetFilterCondition("TextFilter").First(); + set => SetFilterCondition("TextFilter", new string[] { value }); + } + + public IEnumerable AllCategories + { + get + { + var categoryFilter = _filters.FirstOrDefault(f => f.FilterId == "CategoryFilter") as CategoryFilter; + return categoryFilter?.AllCategories ?? Enumerable.Empty(); + } + } + + public void ClearAllFilters() + { + foreach (ITestFilter filter in _filters) + { + filter.Reset(); + } + + FilterNodes(TestModel.LoadedTests); + FireFilterChangedEvent(); + } + + public void Init() + { + foreach (ITestFilter filter in _filters) + { + filter.Init(); + } + } + + 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 = _filters.All(f => f.IsMatching(testNode)); + testNode.IsVisible = isVisible || childIsVisible; + return testNode.IsVisible; + } + + private IEnumerable GetFilterCondition(string filterId) + { + var testFilter = _filters.FirstOrDefault(f => f.FilterId == filterId); + return testFilter.Condition ?? Enumerable.Empty(); + } + + private void SetFilterCondition(string filterId, IEnumerable filter) + { + // 1. Get concrete filter by ID + var testFilter = _filters.FirstOrDefault(f => f.FilterId == filterId); + if (testFilter == null) + return; + + // 2. Set condition, apply new filter to all nodes and fire event + testFilter.Condition = filter; + FilterNodes(TestModel.LoadedTests); + FireFilterChangedEvent(); + } + } +} diff --git a/src/TestModel/model/Filter/TextFilter.cs b/src/TestModel/model/Filter/TextFilter.cs new file mode 100644 index 00000000..217beec6 --- /dev/null +++ b/src/TestModel/model/Filter/TextFilter.cs @@ -0,0 +1,47 @@ +// *********************************************************************** +// 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.Filter +{ + /// + /// Filters the TestNodes by matching a text (for example: Namespace, Class name or test method name - filter is case insensitive) + /// + internal class TextFilter : ITestFilter + { + private string _condition = string.Empty; + + public string FilterId => "TextFilter"; + + public IEnumerable Condition + { + get { return new List() { _condition }; } + set { _condition = value.FirstOrDefault(); } + } + + public bool IsMatching(TestNode testNode) + { + if (string.IsNullOrEmpty(_condition)) + { + return true; + } + + return testNode.FullName.IndexOf(_condition, StringComparison.InvariantCultureIgnoreCase) > -1; + } + + public void Reset() + { + _condition = string.Empty; + } + + public void Init() + { + _condition = string.Empty; + } + } +} diff --git a/src/TestModel/model/ITestModel.cs b/src/TestModel/model/ITestModel.cs index b4fa22a8..886132c8 100644 --- a/src/TestModel/model/ITestModel.cs +++ b/src/TestModel/model/ITestModel.cs @@ -11,6 +11,7 @@ namespace TestCentric.Gui.Model using TestCentric.Engine; using Services; using Settings; + using TestCentric.Gui.Model.Filter; public interface ITestModel : IDisposable { diff --git a/src/TestModel/model/TestCentricTestFilter.cs b/src/TestModel/model/TestCentricTestFilter.cs deleted file mode 100644 index d893dcd4..00000000 --- a/src/TestModel/model/TestCentricTestFilter.cs +++ /dev/null @@ -1,170 +0,0 @@ -// *********************************************************************** -// 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; -using System.Xml; - -namespace TestCentric.Gui.Model -{ - public class TestCentricTestFilter : ITestCentricTestFilter - { - public const string AllOutcome = "All"; - public const string NotRunOutcome = "Not Run"; - public const string NoCategory = "No category"; - - - // By default: all outcome filters are enabled - private List _outcomeFilter = new List() { AllOutcome }; - private string _textFilter = string.Empty; - private List _categoryFilter = new List(); - - public TestCentricTestFilter(ITestModel 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(); - } - } - - public IEnumerable CategoryFilter - { - get => _categoryFilter; - - set - { - _categoryFilter = value.ToList(); - FilterNodes(TestModel.LoadedTests); - FireFilterChangedEvent(); - } - } - - public string TextFilter - { - get { return _textFilter; } - set - { - _textFilter = value; - FilterNodes(TestModel.LoadedTests); - FireFilterChangedEvent(); - } - } - - public IEnumerable AllCategories { get; private set; } - - public void Init() - { - AllCategories = GetAllCategories(); - CategoryFilter = AllCategories; - } - - public void ClearAllFilters() - { - _outcomeFilter = new List() { AllOutcome }; - _categoryFilter = GetAllCategories(); - _textFilter = string.Empty; - - 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) && IsTextFilterMatching(testNode) && IsCategoryMatching(testNode); - testNode.IsVisible = isVisible || childIsVisible; - return testNode.IsVisible; - } - - private bool IsTextFilterMatching(TestNode testNode) - { - if (string.IsNullOrEmpty(_textFilter)) - { - return true; - } - - return testNode.FullName.IndexOf(_textFilter, StringComparison.InvariantCultureIgnoreCase) > -1; - } - - private bool IsCategoryMatching(TestNode testNode) - { - if (CategoryFilter.Any() == false) - return false; - - string xpathExpression = "ancestor-or-self::*/properties/property[@name='Category']"; - - // 1. Get list of available categories at TestNode - IList categories = new List(); - foreach (XmlNode node in testNode.Xml.SelectNodes(xpathExpression)) - { - var groupName = node.Attributes["value"].Value; - if (!string.IsNullOrEmpty(groupName)) - categories.Add(groupName); - } - - if (categories.Any() == false) - categories.Add(NoCategory); - - // 2. Check if any filter category matches the available categories - return CategoryFilter.Intersect(categories).Any(); - } - - private bool IsOutcomeFilterMatching(TestNode testNode) - { - // All kind of outcomes should be displayed (no outcome filtering) - if (OutcomeFilter.Contains(AllOutcome)) - return true; - - string outcome = NotRunOutcome; - - 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" : "Skipped"; - break; - } - } - - return OutcomeFilter.Contains(outcome); - } - - private List GetAllCategories() - { - var items = TestModel.AvailableCategories; - var allCategories = items.Concat(new[] { NoCategory }); - return allCategories.ToList(); - } - } -} diff --git a/src/TestModel/model/TestModel.cs b/src/TestModel/model/TestModel.cs index e8b9a6b5..a1afbd4a 100644 --- a/src/TestModel/model/TestModel.cs +++ b/src/TestModel/model/TestModel.cs @@ -18,6 +18,7 @@ namespace TestCentric.Gui.Model using System.Runtime.InteropServices; using Services; using Settings; + using TestCentric.Gui.Model.Filter; public class TestModel : ITestModel { diff --git a/src/TestModel/tests/TestCentricTestFilterTests.cs b/src/TestModel/tests/TestCentricTestFilterTests.cs index 9ff6be29..f44db2ca 100644 --- a/src/TestModel/tests/TestCentricTestFilterTests.cs +++ b/src/TestModel/tests/TestCentricTestFilterTests.cs @@ -9,6 +9,7 @@ using NSubstitute; using NUnit.Framework; using NUnit.Framework.Internal; +using TestCentric.Gui.Model.Filter; namespace TestCentric.Gui.Model { @@ -85,7 +86,7 @@ public void AllCategories_NoCategoriesDefinedInModel_ReturnsDefaultCategory() // Assert Assert.That(allCategories.Count(), Is.EqualTo(1)); - Assert.That(allCategories, Contains.Item(TestCentricTestFilter.NoCategory)); + Assert.That(allCategories, Contains.Item(CategoryFilter.NoCategory)); } [Test] @@ -104,7 +105,7 @@ public void AllCategories_CategoriesDefinedInModel_ReturnsModelAndDefaultCategor // Assert Assert.That(allCategories.Count(), Is.EqualTo(2)); Assert.That(allCategories, Contains.Item("Feature_1")); - Assert.That(allCategories, Contains.Item(TestCentricTestFilter.NoCategory)); + Assert.That(allCategories, Contains.Item(CategoryFilter.NoCategory)); } [Test] @@ -128,11 +129,11 @@ public void ClearFilter_AllFiltersAreReset() var allCategories = testFilter.AllCategories; Assert.That(allCategories.Count(), Is.EqualTo(2)); Assert.That(allCategories, Contains.Item("Feature_1")); - Assert.That(allCategories, Contains.Item(TestCentricTestFilter.NoCategory)); + Assert.That(allCategories, Contains.Item(CategoryFilter.NoCategory)); var outcomeFilter = testFilter.OutcomeFilter; Assert.That(outcomeFilter.Count, Is.EqualTo(1)); - Assert.That(outcomeFilter, Contains.Item(TestCentricTestFilter.AllOutcome)); + Assert.That(outcomeFilter, Contains.Item(OutcomeFilter.AllOutcome)); Assert.That(testFilter.TextFilter, Is.Empty); } @@ -141,11 +142,11 @@ public void ClearFilter_AllFiltersAreReset() { new object[] { new List() { "Passed" }, new List() { "3-1000", "3-1001", "3-1010", "3-1011", "3-1012", "3-1020", "3-1022" } }, new object[] { new List() { "Failed" }, new List() { "3-1000", "3-1001", "3-1020", "3-1021" } }, - new object[] { new List() { TestCentricTestFilter.NotRunOutcome }, new List() { "3-1000", "3-1001", "3-1030", "3-1031", "3-1032" } }, - new object[] { new List() { "Passed", TestCentricTestFilter.NotRunOutcome }, new List() { "3-1000", "3-1001", "3-1010", "3-1011", "3-1012", "3-1020", "3-1022", "3-1030", "3-1031", "3-1032" } }, + new object[] { new List() { OutcomeFilter.NotRunOutcome }, new List() { "3-1000", "3-1001", "3-1030", "3-1031", "3-1032" } }, + new object[] { new List() { "Passed", OutcomeFilter.NotRunOutcome }, new List() { "3-1000", "3-1001", "3-1010", "3-1011", "3-1012", "3-1020", "3-1022", "3-1030", "3-1031", "3-1032" } }, new object[] { new List() { "Passed", "Failed" }, new List() { "3-1000", "3-1001", "3-1010", "3-1011", "3-1012", "3-1020", "3-1022", "3-1020", "3-1021" } }, - new object[] { new List() { TestCentricTestFilter.NotRunOutcome, "Failed" }, new List() { "3-1000", "3-1001", "3-1030", "3-1031", "3-1032", "3-1020", "3-1021" } }, - new object[] { new List() { TestCentricTestFilter.AllOutcome }, new List() { "3-1000", "3-1001", "3-1010", "3-1011", "3-1012", "3-1020", "3-1021", "3-1022", "3-1030", "3-1031", "3-1032" } }, + new object[] { new List() { OutcomeFilter.NotRunOutcome, "Failed" }, new List() { "3-1000", "3-1001", "3-1030", "3-1031", "3-1032", "3-1020", "3-1021" } }, + new object[] { new List() { OutcomeFilter.AllOutcome }, new List() { "3-1000", "3-1001", "3-1010", "3-1011", "3-1012", "3-1020", "3-1021", "3-1022", "3-1030", "3-1031", "3-1032" } }, }; [Test] @@ -184,11 +185,11 @@ public void FilterByOutcome_TestNodesAreVisible(IList outcomeFilter, ILi { new object[] { new List() { "Passed" }, new List() { "3-1000", "3-1000", "3-1110", "3-1111", "3-1112", "3-1200", "3-1210", "3-1212" } }, new object[] { new List() { "Failed" }, new List() { "3-1000", "3-1200", "3-1210", "3-1211", "3-1400", "3-1411" } }, - new object[] { new List() { TestCentricTestFilter.NotRunOutcome }, new List() { "3-1000", "3-1300", "3-1310", "3-1311", "3-1400", "3-1410", "3-1412" } }, - new object[] { new List() { "Passed", TestCentricTestFilter.NotRunOutcome }, new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1212", "3-1300", "3-1310", "3-1311", "3-1312", "3-1400", "3-1410", "3-1412" } }, + new object[] { new List() { OutcomeFilter.NotRunOutcome }, new List() { "3-1000", "3-1300", "3-1310", "3-1311", "3-1400", "3-1410", "3-1412" } }, + new object[] { new List() { "Passed", OutcomeFilter.NotRunOutcome }, new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1212", "3-1300", "3-1310", "3-1311", "3-1312", "3-1400", "3-1410", "3-1412" } }, new object[] { new List() { "Passed", "Failed" }, new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1210", "3-1211", "3-1212", "3-1400", "3-1411" } }, - new object[] { new List() { TestCentricTestFilter.NotRunOutcome, "Failed" }, new List() { "3-1000", "3-1200", "3-1210", "3-1211", "3-1300", "3-1310", "3-1311", "3-1312", "3-1400", "3-1410", "3-1411", "3-1412" } }, - new object[] { new List() { TestCentricTestFilter.AllOutcome }, new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1210", "3-1211", "3-1212", "3-1300", "3-1310", "3-1311", "3-1312", "3-1400", "3-1410", "3-1411", "3-1412" } }, + new object[] { new List() { OutcomeFilter.NotRunOutcome, "Failed" }, new List() { "3-1000", "3-1200", "3-1210", "3-1211", "3-1300", "3-1310", "3-1311", "3-1312", "3-1400", "3-1410", "3-1411", "3-1412" } }, + new object[] { new List() { OutcomeFilter.AllOutcome }, new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1210", "3-1211", "3-1212", "3-1300", "3-1310", "3-1311", "3-1312", "3-1400", "3-1410", "3-1411", "3-1412" } }, }; [Test] @@ -284,8 +285,8 @@ public void FilterByText_TestNodesAreVisible(string textFilter, IList ex new object[] { new[] { "Category_1", "Category_2" }, new List() { "3-1100", "3-1000", "3-1110", "3-1111", "3-1112", "3-1200", "3-1211", "3-1300", "3-1311", "3-1312" } }, new object[] { new[] { "Category_2" }, new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1211", "3-1300", "3-1312" } }, new object[] { new[] { "Category_3" }, new List() { "3-1000", "3-1300", "3-1320", "3-1321" } }, - new object[] { new[] { "Category_3", TestCentricTestFilter.NoCategory }, new List() { "3-1000", "3-1200", "3-1210", "3-1212", "3-1300", "3-1320", "3-1321", "3-1322" } }, - new object[] { new[] { TestCentricTestFilter.NoCategory }, new List() { "3-1000", "3-1200", "3-1210", "3-1212", "3-1300", "3-1320", "3-1322" } }, + new object[] { new[] { "Category_3", CategoryFilter.NoCategory }, new List() { "3-1000", "3-1200", "3-1210", "3-1212", "3-1300", "3-1320", "3-1321", "3-1322" } }, + new object[] { new[] { CategoryFilter.NoCategory }, new List() { "3-1000", "3-1200", "3-1210", "3-1212", "3-1300", "3-1320", "3-1322" } }, }; [Test] @@ -329,13 +330,13 @@ public void FilterByCategory_TestNodesAreVisible(IList categoryFilter, I { new object[] { new[] { "Category_1" }, new[] { "Passed" }, "", new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112" } }, new object[] { new[] { "Category_2" }, new[] { "Failed" }, "", new List() { "3-1000", "3-1200", "3-1211" } }, - new object[] { new[] { "Category_2" }, new[] { TestCentricTestFilter.NotRunOutcome }, "", new List() { "3-1000", "3-1300", "3-1312" } }, - new object[] { new[] { "Category_2" }, new[] { TestCentricTestFilter.AllOutcome }, "", new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1211", "3-1300", "3-1312" } }, + new object[] { new[] { "Category_2" }, new[] { OutcomeFilter.NotRunOutcome }, "", new List() { "3-1000", "3-1300", "3-1312" } }, + new object[] { new[] { "Category_2" }, new[] { OutcomeFilter.AllOutcome }, "", new List() { "3-1000", "3-1100", "3-1110", "3-1111", "3-1112", "3-1200", "3-1211", "3-1300", "3-1312" } }, new object[] { new[] { "Category_2" }, new[] { "Passed", "Failed" }, "TestB", new List() { "3-1000", "3-1100", "3-1110", "3-1112" } }, - new object[] { new[] { "Category_1" }, new[] { TestCentricTestFilter.AllOutcome }, "NamespaceC", new List() { "3-1000", "3-1300", "3-1311", "3-1312" } }, + new object[] { new[] { "Category_1" }, new[] { OutcomeFilter.AllOutcome }, "NamespaceC", new List() { "3-1000", "3-1300", "3-1311", "3-1312" } }, new object[] { new[] { "Category_1", "Category_2"}, new[] { "Failed" }, "TestA", new List() { "3-1000", "3-1200", "3-1210", "3-1211" } }, new object[] { new[] { "Category_3" }, new[] { "Failed" }, "TestC", new List() { "3-1000", "3-1300", "3-1320", "3-1321" } }, - new object[] { new[] { TestCentricTestFilter.NoCategory }, new[] { TestCentricTestFilter.NotRunOutcome }, "NamespaceC", new List() { "3-1000", "3-1300", "3-1320", "3-1322" } }, + new object[] { new[] { CategoryFilter.NoCategory }, new[] { OutcomeFilter.NotRunOutcome }, "NamespaceC", new List() { "3-1000", "3-1300", "3-1320", "3-1322" } }, }; [Test] @@ -379,10 +380,10 @@ public void FilterByOutcomeCategoryAndText_TestNodesAreVisible(IList cat private static object[] FilterByCategoryCategoryAndTextAllInvisibleTestCases = { - new object[] { new[] { "Category_XY" }, new[] { TestCentricTestFilter.AllOutcome }, ""}, + new object[] { new[] { "Category_XY" }, new[] { OutcomeFilter.AllOutcome }, ""}, new object[] { new[] { "Category_1" }, new[] { "Passed", "Failed" }, "NamespaceXY"}, - new object[] { new[] { "Category_3" }, new[] { TestCentricTestFilter.NotRunOutcome }, ""}, - new object[] { new[] { TestCentricTestFilter.NoCategory }, new[] { TestCentricTestFilter.NotRunOutcome }, "TestC"}, + new object[] { new[] { "Category_3" }, new[] { OutcomeFilter.NotRunOutcome }, ""}, + new object[] { new[] { CategoryFilter.NoCategory }, new[] { OutcomeFilter.NotRunOutcome }, "TestC"}, new object[] { new[] { "Category_2" }, new[] { "Failed" }, "TestB"}, };