Add TeamClipboardMatcher utility and corresponding tests for fuzzy team name matching

This commit introduces the TeamClipboardMatcher class, which provides functionality to match team names from clipboard text using fuzzy matching techniques. The class includes methods for extracting team names and finding the best match based on a specified threshold. Additionally, comprehensive unit tests are added in TeamClipboardMatcher_Tests to validate various matching scenarios, including exact matches, fuzzy matches, and handling of different clipboard formats. This enhancement improves the application's ability to efficiently match teams from user input.
This commit is contained in:
2026-01-20 22:49:09 -05:00
parent 455be30821
commit 48861eb6a6
4 changed files with 597 additions and 24 deletions
+313
View File
@@ -0,0 +1,313 @@
using Core.Entities;
using Core.Utility;
using Tests.Builders;
namespace Tests.Utility;
[TestFixture]
public class TeamClipboardMatcher_Tests
{
private List<Team> _availableTeams = [];
[SetUp]
public void SetUp()
{
BuilderExtensions.ResetAllBuilders();
// Create test teams
var construction = EventDefinitionBuilder.Team("Construction Challenge", 2, 4).Build();
var flight = EventDefinitionBuilder.Individual("Flight").Build();
var robotics = EventDefinitionBuilder.Team("Robotics", 2, 5).Build();
var coding = EventDefinitionBuilder.Individual("Coding").Build();
var medicalTech = EventDefinitionBuilder.Team("Medical Technology", 2, 3).Build();
_availableTeams =
[
TeamBuilder.Create(construction).WithIdentifier("2").Build(),
TeamBuilder.Create(flight).Build(),
TeamBuilder.Create(robotics).Build(),
TeamBuilder.Create(coding).Build(),
TeamBuilder.Create(medicalTech).Build()
];
}
[Test]
public void MatchTeamsFromClipboard_ExactMatch_ReturnsMatchingTeam()
{
// Arrange
var clipboardText = "Construction Challenge (2)";
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams);
// Assert
Assert.That(result, Has.Count.EqualTo(1));
Assert.That(result[0].Event.Name, Is.EqualTo("Construction Challenge"));
Assert.That(result[0].Identifier, Is.EqualTo("2"));
}
[Test]
public void MatchTeamsFromClipboard_ExactMatchWithoutIdentifier_ReturnsMatchingTeam()
{
// Arrange
var clipboardText = "Flight";
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams);
// Assert
Assert.That(result, Has.Count.EqualTo(1));
Assert.That(result[0].Event.Name, Is.EqualTo("Flight"));
}
[Test]
public void MatchTeamsFromClipboard_ClipboardFormatWithStudentList_ExtractsTeamName()
{
// Arrange
var clipboardText = "Construction Challenge (2) - John Doe, Jane Smith";
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams);
// Assert
Assert.That(result, Has.Count.EqualTo(1));
Assert.That(result[0].Event.Name, Is.EqualTo("Construction Challenge"));
}
[Test]
public void MatchTeamsFromClipboard_MultipleTeams_ReturnsAllMatches()
{
// Arrange
var clipboardText = "Flight\r\nRobotics\r\nCoding";
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams);
// Assert
Assert.That(result, Has.Count.EqualTo(3));
Assert.That(result.Select(t => t.Event.Name), Contains.Item("Flight"));
Assert.That(result.Select(t => t.Event.Name), Contains.Item("Robotics"));
Assert.That(result.Select(t => t.Event.Name), Contains.Item("Coding"));
}
[Test]
public void MatchTeamsFromClipboard_FuzzyMatch_ReturnsBestMatch()
{
// Arrange
var clipboardText = "Construcion Challange"; // Intentional typos
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams);
// Assert
Assert.That(result, Has.Count.EqualTo(1));
Assert.That(result[0].Event.Name, Is.EqualTo("Construction Challenge"));
}
[Test]
public void MatchTeamsFromClipboard_CaseInsensitive_MatchesCorrectly()
{
// Arrange
var clipboardText = "FLIGHT\r\nconstruction challenge (2)";
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams);
// Assert
Assert.That(result, Has.Count.EqualTo(2));
Assert.That(result.Select(t => t.Event.Name), Contains.Item("Flight"));
Assert.That(result.Select(t => t.Event.Name), Contains.Item("Construction Challenge"));
}
[Test]
public void MatchTeamsFromClipboard_SkipsEmptyLines_IgnoresEmptyEntries()
{
// Arrange
var clipboardText = "Flight\r\n\r\nRobotics\r\n \r\nCoding";
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams);
// Assert
Assert.That(result, Has.Count.EqualTo(3));
}
[Test]
public void MatchTeamsFromClipboard_SkipsMetadataHeaders_IgnoresSpecialMarkers()
{
// Arrange
var clipboardText = "--Unscheduled\r\nFlight\r\n--Another Header\r\nRobotics\r\nUnscheduled";
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams);
// Assert
Assert.That(result, Has.Count.EqualTo(2));
Assert.That(result.Select(t => t.Event.Name), Contains.Item("Flight"));
Assert.That(result.Select(t => t.Event.Name), Contains.Item("Robotics"));
}
[Test]
public void MatchTeamsFromClipboard_NoMatches_ReturnsEmptyList()
{
// Arrange
var clipboardText = "NonExistent Team Name";
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams);
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public void MatchTeamsFromClipboard_BelowThreshold_ReturnsEmptyList()
{
// Arrange
var clipboardText = "XYZ"; // Very different from any team name
var highThreshold = 95; // Very high threshold
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams, highThreshold);
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public void MatchTeamsFromClipboard_CustomThreshold_RespectsThreshold()
{
// Arrange
var clipboardText = "Construction Challeng"; // Close match, should work with lower threshold
var lowThreshold = 70;
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams, lowThreshold);
// Assert
Assert.That(result, Has.Count.EqualTo(1));
Assert.That(result[0].Event.Name, Is.EqualTo("Construction Challenge"));
}
[Test]
public void MatchTeamsFromClipboard_EmptyClipboard_ReturnsEmptyList()
{
// Arrange
var clipboardText = "";
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams);
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public void MatchTeamsFromClipboard_NullClipboard_ReturnsEmptyList()
{
// Arrange
string? clipboardText = null;
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText!, _availableTeams);
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public void MatchTeamsFromClipboard_EmptyTeamsList_ReturnsEmptyList()
{
// Arrange
var clipboardText = "Flight";
var emptyTeams = new List<Team>();
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, emptyTeams);
// Assert
Assert.That(result, Is.Empty);
}
[Test]
public void MatchTeamsFromClipboard_DuplicateTeamNames_ReturnsSingleInstance()
{
// Arrange
var clipboardText = "Flight\r\nFlight\r\nFlight";
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams);
// Assert
Assert.That(result, Has.Count.EqualTo(1));
Assert.That(result[0].Event.Name, Is.EqualTo("Flight"));
}
[Test]
public void MatchTeamsFromClipboard_MultipleLinesWithDifferentFormats_HandlesAllFormats()
{
// Arrange
var clipboardText = "Flight\r\nRobotics - Student List\r\nMedical Technology (1) - More Students";
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams);
// Assert
Assert.That(result, Has.Count.EqualTo(3));
Assert.That(result.Select(t => t.Event.Name), Contains.Item("Flight"));
Assert.That(result.Select(t => t.Event.Name), Contains.Item("Robotics"));
Assert.That(result.Select(t => t.Event.Name), Contains.Item("Medical Technology"));
}
[Test]
public void MatchTeamsFromClipboard_WhitespaceAroundTeamName_TrimsCorrectly()
{
// Arrange
var clipboardText = " Flight \r\n Robotics ";
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams);
// Assert
Assert.That(result, Has.Count.EqualTo(2));
Assert.That(result.Select(t => t.Event.Name), Contains.Item("Flight"));
Assert.That(result.Select(t => t.Event.Name), Contains.Item("Robotics"));
}
[Test]
public void MatchTeamsFromClipboard_ReturnsTeamsFromInputCollection_MaintainsReferenceEquality()
{
// Arrange
var clipboardText = "Flight";
var inputTeam = _availableTeams.First(t => t.Event.Name == "Flight");
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, _availableTeams);
// Assert
Assert.That(result[0], Is.SameAs(inputTeam));
}
[Test]
public void MatchTeamsFromClipboard_BestMatchSelected_ReturnsHighestScoringMatch()
{
// Arrange
// Create teams with similar names
var construction1 = EventDefinitionBuilder.Team("Construction Challenge", 2, 4).Build();
var construction2 = EventDefinitionBuilder.Team("Construction Challenge Advanced", 2, 4).Build();
var teams = new[]
{
TeamBuilder.Create(construction1).Build(),
TeamBuilder.Create(construction2).Build()
};
var clipboardText = "Construction Challenge"; // Should match the first one exactly
// Act
var result = TeamClipboardMatcher.MatchTeamsFromClipboard(clipboardText, teams);
// Assert
Assert.That(result, Has.Count.EqualTo(1));
// The exact match should be selected (higher score)
Assert.That(result[0].Event.Name, Is.EqualTo("Construction Challenge"));
}
}