Skip to content

Commit

Permalink
Refactor TestCentricTestFilter class: introduce separate TestFilter c…
Browse files Browse the repository at this point in the history
…lasses, each one dedicated to one single filter condition
  • Loading branch information
rowo360 committed Dec 6, 2024
1 parent d6d05da commit 9e061a0
Show file tree
Hide file tree
Showing 10 changed files with 374 additions and 192 deletions.
79 changes: 79 additions & 0 deletions src/TestModel/model/Filter/CategoryFilter.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Filters the TestNodes by test categories. Use item 'No category' to filter for tests without any test category.
/// </summary>
public class CategoryFilter : ITestFilter
{
public const string NoCategory = "No category";

private List<string> _condition = new List<string>();

internal CategoryFilter(ITestModel model)
{
TestModel = model;
}

private ITestModel TestModel { get; }

public string FilterId => "CategoryFilter";

public IEnumerable<string> Condition
{
get { return _condition; }
set { _condition = value.ToList(); }
}

public IEnumerable<string> 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<string> categories = new List<string>();
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<string> GetAllCategories()
{
var items = TestModel.AvailableCategories;
var allCategories = items.Concat(new[] { NoCategory });
return allCategories.ToList();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

using System.Collections.Generic;

namespace TestCentric.Gui.Model
namespace TestCentric.Gui.Model.Filter
{
/// <summary>
/// Provides filter functionality: by outcome, by duration, by category...
Expand Down
40 changes: 40 additions & 0 deletions src/TestModel/model/Filter/ITestFilter.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Interface for all test filters
/// </summary>
internal interface ITestFilter
{
/// <summary>
/// Unqiue identifier of the filter
/// </summary>
string FilterId { get; }

/// <summary>
/// The filter condition
/// </summary>
IEnumerable<string> Condition { get; set; }

/// <summary>
/// Reset the filter condition to its default state
/// </summary>
void Reset();

/// <summary>
/// Init filter after a project is loaded
/// </summary>
void Init();

/// <summary>
/// Checks if the testNode matches the filter condition
/// </summary>
bool IsMatching(TestNode testNode);
}
}
73 changes: 73 additions & 0 deletions src/TestModel/model/Filter/OutcomeFilter.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Filters the TestNodes by outcome (for example: 'Passed', 'Failed' or 'Not run')
/// </summary>
public class OutcomeFilter : ITestFilter
{
public const string AllOutcome = "All";
public const string NotRunOutcome = "Not Run";

private List<string> _condition = new List<string>() { AllOutcome };

internal OutcomeFilter(ITestModel model)
{
TestModel = model;
}

public string FilterId => "OutcomeFilter";

private ITestModel TestModel { get; }

public IEnumerable<string> 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<string>() { AllOutcome };
}

public void Init()
{
_condition = new List<string>() { AllOutcome };
}
}
}
110 changes: 110 additions & 0 deletions src/TestModel/model/Filter/TestCentricTestFilter.cs
Original file line number Diff line number Diff line change
@@ -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<ITestFilter> _filters = new List<ITestFilter>();

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<string> OutcomeFilter
{
get => GetFilterCondition("OutcomeFilter");
set => SetFilterCondition("OutcomeFilter", value);
}

public IEnumerable<string> CategoryFilter
{
get => GetFilterCondition("CategoryFilter");

set => SetFilterCondition("CategoryFilter", value);
}

public string TextFilter
{
get => GetFilterCondition("TextFilter").First();
set => SetFilterCondition("TextFilter", new string[] { value });
}

public IEnumerable<string> AllCategories
{
get
{
var categoryFilter = _filters.FirstOrDefault(f => f.FilterId == "CategoryFilter") as CategoryFilter;
return categoryFilter?.AllCategories ?? Enumerable.Empty<string>();
}
}

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<string> GetFilterCondition(string filterId)
{
var testFilter = _filters.FirstOrDefault(f => f.FilterId == filterId);
return testFilter.Condition ?? Enumerable.Empty<string>();
}

private void SetFilterCondition(string filterId, IEnumerable<string> 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();
}
}
}
47 changes: 47 additions & 0 deletions src/TestModel/model/Filter/TextFilter.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Filters the TestNodes by matching a text (for example: Namespace, Class name or test method name - filter is case insensitive)
/// </summary>
internal class TextFilter : ITestFilter
{
private string _condition = string.Empty;

public string FilterId => "TextFilter";

public IEnumerable<string> Condition
{
get { return new List<string>() { _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;
}
}
}
1 change: 1 addition & 0 deletions src/TestModel/model/ITestModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace TestCentric.Gui.Model
using TestCentric.Engine;
using Services;
using Settings;
using TestCentric.Gui.Model.Filter;

public interface ITestModel : IDisposable
{
Expand Down
Loading

0 comments on commit 9e061a0

Please sign in to comment.