/ src / modules / launcher / Wox.Test / FuzzyMatcherTest.cs
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  }