FuzzyMatcherTest.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Collections.Generic; 7 using System.Diagnostics; 8 using System.Globalization; 9 using System.Linq; 10 11 using Microsoft.VisualStudio.TestTools.UnitTesting; 12 using Wox.Infrastructure; 13 using Wox.Plugin; 14 15 namespace Wox.Test 16 { 17 [TestClass] 18 public class FuzzyMatcherTest 19 { 20 private const string Chrome = "Chrome"; 21 private const string CandyCrushSagaFromKing = "Candy Crush Saga from King"; 22 private const string HelpCureHopeRaiseOnMindEntityChrome = "Help cure hope raise on mind entity Chrome"; 23 private const string UninstallOrChangeProgramsOnYourComputer = "Uninstall or change programs on your computer"; 24 private const string LastIsChrome = "Last is chrome"; 25 private const string OneOneOneOne = "1111"; 26 private const string MicrosoftSqlServerManagementStudio = "Microsoft SQL Server Management Studio"; 27 28 public static List<string> GetSearchStrings() 29 => new List<string> 30 { 31 Chrome, 32 "Choose which programs you want Windows to use for activities like web browsing, editing photos, sending e-mail, and playing music.", 33 HelpCureHopeRaiseOnMindEntityChrome, 34 CandyCrushSagaFromKing, 35 UninstallOrChangeProgramsOnYourComputer, 36 "Add, change, and manage fonts on your computer", 37 LastIsChrome, 38 OneOneOneOne, 39 }; 40 41 public static List<int> GetPrecisionScores() 42 { 43 var listToReturn = new List<int>(); 44 45 Enum.GetValues(typeof(StringMatcher.SearchPrecisionScore)) 46 .Cast<StringMatcher.SearchPrecisionScore>() 47 .ToList() 48 .ForEach(x => listToReturn.Add((int)x)); 49 50 return listToReturn; 51 } 52 53 [TestMethod] 54 public void MatchTest() 55 { 56 var sources = new List<string> 57 { 58 "file open in browser-test", 59 "Install Package", 60 "add new bsd", 61 "Inste", 62 "aac", 63 }; 64 65 var results = new List<Result>(); 66 var matcher = new StringMatcher(); 67 foreach (var str in sources) 68 { 69 results.Add(new Result 70 { 71 Title = str, 72 Score = matcher.FuzzyMatch("inst", str).RawScore, 73 }); 74 } 75 76 results = results.Where(x => x.Score > 0).OrderByDescending(x => x.Score).ToList(); 77 78 Assert.IsTrue(results.Count == 3); 79 Assert.IsTrue(results[0].Title == "Inste"); 80 Assert.IsTrue(results[1].Title == "Install Package"); 81 Assert.IsTrue(results[2].Title == "file open in browser-test"); 82 } 83 84 [DataTestMethod] 85 [DataRow("Chrome")] 86 public void WhenGivenNotAllCharactersFoundInSearchStringThenShouldReturnZeroScore(string searchString) 87 { 88 var compareString = "Can have rum only in my glass"; 89 var matcher = new StringMatcher(); 90 var scoreResult = matcher.FuzzyMatch(searchString, compareString).RawScore; 91 92 Assert.IsTrue(scoreResult == 0); 93 } 94 95 [DataTestMethod] 96 [DataRow("chr")] 97 [DataRow("chrom")] 98 [DataRow("chrome")] 99 [DataRow("cand")] 100 [DataRow("cpywa")] 101 [DataRow("ccs")] 102 public void WhenGivenStringsAndAppliedPrecisionFilteringThenShouldReturnGreaterThanPrecisionScoreResults(string searchTerm) 103 { 104 var results = new List<Result>(); 105 var matcher = new StringMatcher(); 106 foreach (var str in GetSearchStrings()) 107 { 108 results.Add(new Result 109 { 110 Title = str, 111 Score = matcher.FuzzyMatch(searchTerm, str).Score, 112 }); 113 } 114 115 foreach (var precisionScore in GetPrecisionScores()) 116 { 117 var filteredResult = results.Where(result => result.Score >= precisionScore).Select(result => result).OrderByDescending(x => x.Score).ToList(); 118 119 Debug.WriteLine(string.Empty); 120 Debug.WriteLine("###############################################"); 121 Debug.WriteLine("SEARCHTERM: " + searchTerm + ", GreaterThanSearchPrecisionScore: " + precisionScore); 122 foreach (var item in filteredResult) 123 { 124 // Using InvariantCulture since this is used for testing 125 Debug.WriteLine("SCORE: " + item.Score.ToString(CultureInfo.InvariantCulture) + ", FoundString: " + item.Title); 126 } 127 128 Debug.WriteLine("###############################################"); 129 Debug.WriteLine(string.Empty); 130 131 Assert.IsFalse(filteredResult.Any(x => x.Score < precisionScore)); 132 } 133 } 134 135 [DataTestMethod] 136 [DataRow("vim", "Vim", "ignoreDescription", "ignore.exe", "Vim Diff", "ignoreDescription", "ignore.exe")] 137 public void WhenMultipleResultsExactMatchingResultShouldHaveGreatestScore(string queryString, string firstName, string firstDescription, string firstExecutableName, string secondName, string secondDescription, string secondExecutableName) 138 { 139 // Act 140 var matcher = new StringMatcher(); 141 var firstNameMatch = matcher.FuzzyMatch(queryString, firstName).RawScore; 142 var firstDescriptionMatch = matcher.FuzzyMatch(queryString, firstDescription).RawScore; 143 var firstExecutableNameMatch = matcher.FuzzyMatch(queryString, firstExecutableName).RawScore; 144 145 var secondNameMatch = matcher.FuzzyMatch(queryString, secondName).RawScore; 146 var secondDescriptionMatch = matcher.FuzzyMatch(queryString, secondDescription).RawScore; 147 var secondExecutableNameMatch = matcher.FuzzyMatch(queryString, secondExecutableName).RawScore; 148 149 var firstScore = new[] { firstNameMatch, firstDescriptionMatch, firstExecutableNameMatch }.Max(); 150 var secondScore = new[] { secondNameMatch, secondDescriptionMatch, secondExecutableNameMatch }.Max(); 151 152 // Assert 153 Assert.IsTrue(firstScore > secondScore); 154 } 155 156 [DataTestMethod] 157 [DataRow("goo", "Google Chrome", StringMatcher.SearchPrecisionScore.Regular, true)] 158 [DataRow("chr", "Google Chrome", StringMatcher.SearchPrecisionScore.Low, true)] 159 [DataRow("chr", "Chrome", StringMatcher.SearchPrecisionScore.Regular, true)] 160 [DataRow("chr", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular, true)] 161 [DataRow("chr", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Low, true)] 162 [DataRow("chr", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Regular, false)] 163 [DataRow("chr", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.None, true)] 164 [DataRow("ccs", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Low, true)] 165 [DataRow("cand", "Candy Crush Saga from King", StringMatcher.SearchPrecisionScore.Regular, true)] 166 [DataRow("cand", "Help cure hope raise on mind entity Chrome", StringMatcher.SearchPrecisionScore.Regular, false)] 167 public void WhenGivenDesiredPrecisionThenShouldReturnAllResultsGreaterOrEqual( 168 string queryString, 169 string compareString, 170 StringMatcher.SearchPrecisionScore expectedPrecisionScore, 171 bool expectedPrecisionResult) 172 { 173 // When 174 var matcher = new StringMatcher { UserSettingSearchPrecision = expectedPrecisionScore }; 175 176 // Given 177 var matchResult = matcher.FuzzyMatch(queryString, compareString); 178 179 Debug.WriteLine(string.Empty); 180 Debug.WriteLine("###############################################"); 181 Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}"); 182 Debug.WriteLine($"RAW SCORE: {matchResult.RawScore}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})"); 183 Debug.WriteLine("###############################################"); 184 Debug.WriteLine(string.Empty); 185 186 // Should 187 Assert.AreEqual( 188 expectedPrecisionResult, 189 matchResult.IsSearchPrecisionScoreMet(), 190 $"{$"Query:{queryString}{Environment.NewLine} "}{$"Compare:{compareString}{Environment.NewLine}"}{$"Raw Score: {matchResult.RawScore}{Environment.NewLine}"}{$"Precision Score: {(int)expectedPrecisionScore}"}"); 191 } 192 193 [DataTestMethod] 194 [DataRow("exce", "OverLeaf-Latex: An online LaTeX editor", StringMatcher.SearchPrecisionScore.Regular, false)] 195 [DataRow("term", "Windows Terminal (Preview)", StringMatcher.SearchPrecisionScore.Regular, true)] 196 [DataRow("sql s managa", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)] 197 [DataRow("sql' s manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)] 198 [DataRow("sql s manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)] 199 [DataRow("sql manag", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)] 200 [DataRow("sql", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)] 201 [DataRow("sql serv", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)] 202 [DataRow("sql serv man", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)] 203 [DataRow("sql studio", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)] 204 [DataRow("mic", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, true)] 205 [DataRow("chr", "Shutdown", StringMatcher.SearchPrecisionScore.Regular, false)] 206 [DataRow("mssms", MicrosoftSqlServerManagementStudio, StringMatcher.SearchPrecisionScore.Regular, false)] 207 [DataRow("chr", "Change settings for text-to-speech and for speech recognition (if installed).", StringMatcher.SearchPrecisionScore.Regular, true)] 208 [DataRow("ch r", "Change settings for text-to-speech and for speech recognition (if installed).", StringMatcher.SearchPrecisionScore.Regular, true)] 209 [DataRow("a test", "This is a test", StringMatcher.SearchPrecisionScore.Regular, true)] 210 [DataRow("test", "This is a test", StringMatcher.SearchPrecisionScore.Regular, true)] 211 public void WhenGivenQueryShouldReturnResultsContainingAllQuerySubstrings( 212 string queryString, 213 string compareString, 214 StringMatcher.SearchPrecisionScore expectedPrecisionScore, 215 bool expectedPrecisionResult) 216 { 217 // When 218 var matcher = new StringMatcher { UserSettingSearchPrecision = expectedPrecisionScore }; 219 220 // Given 221 var matchResult = matcher.FuzzyMatch(queryString, compareString); 222 223 Debug.WriteLine(string.Empty); 224 Debug.WriteLine("###############################################"); 225 Debug.WriteLine($"QueryString: {queryString} CompareString: {compareString}"); 226 Debug.WriteLine($"RAW SCORE: {matchResult.RawScore}, PrecisionLevelSetAt: {expectedPrecisionScore} ({(int)expectedPrecisionScore})"); 227 Debug.WriteLine("###############################################"); 228 Debug.WriteLine(string.Empty); 229 230 // Should 231 Assert.AreEqual( 232 expectedPrecisionResult, 233 matchResult.IsSearchPrecisionScoreMet(), 234 $"{$"Query:{queryString}{Environment.NewLine} "}{$"Compare:{compareString}{Environment.NewLine}"}{$"Raw Score: {matchResult.RawScore}{Environment.NewLine}"}{$"Precision Score: {(int)expectedPrecisionScore}"}"); 235 } 236 237 [DataTestMethod] 238 [DataRow("Windows Terminal", "Windows_Terminal", "term")] 239 [DataRow("Windows Terminal", "WindowsTerminal", "term")] 240 public void FuzzyMatchingScoreShouldBeHigherWhenPreceedingCharacterIsSpace(string firstCompareStr, string secondCompareStr, string query) 241 { 242 // Arrange 243 var matcher = new StringMatcher(); 244 245 // Act 246 var firstScore = matcher.FuzzyMatch(query, firstCompareStr).Score; 247 var secondScore = matcher.FuzzyMatch(query, secondCompareStr).Score; 248 249 // Assert 250 Assert.IsTrue(firstScore > secondScore); 251 } 252 } 253 }