Files
PowerToys/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest/QueryTests.cs

287 lines
11 KiB
C#
Raw Normal View History

// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Globalization;
using System.Linq;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Wox.Plugin;
namespace Microsoft.PowerToys.Run.Plugin.Calculator.UnitTests
{
[TestClass]
public class QueryTests
{
private static Mock<Main> _main;
private static Mock<IPublicAPI> _api;
[TestInitialize]
public void Initialize()
{
_api = new Mock<IPublicAPI>();
_api.Setup(api => api.ChangeQuery(It.IsAny<string>(), It.IsAny<bool>())).Verifiable();
_main = new Mock<Main>();
_main.Object.Init(new PluginInitContext() { API = _api.Object });
}
[DataTestMethod]
[DataRow("=pi(9+)", "Expression wrong or incomplete (Did you forget some parentheses?)")]
[DataRow("=pi,", "Expression wrong or incomplete (Did you forget some parentheses?)")]
[DataRow("=log()", "Expression wrong or incomplete (Did you forget some parentheses?)")]
[DataRow("=0xf0x6", "Expression wrong or incomplete (Did you forget some parentheses?)")]
[DataRow("=0xf,0x6", "Expression wrong or incomplete (Did you forget some parentheses?)")]
[DataRow("=2^96", "Result value was either too large or too small for a decimal number")]
[DataRow("=+()", "Calculation result is not a valid number (NaN)")]
[DataRow("=[10,10]", "Unsupported use of square brackets")]
[DataRow("=5/0", "Expression contains division by zero")]
[DataRow("=5 / 0", "Expression contains division by zero")]
[DataRow("10+(8*9)/0+7", "Expression contains division by zero")]
[DataRow("10+(8*9)/0*7", "Expression contains division by zero")]
[DataRow("10+(8*9)/0x00", "Expression contains division by zero")]
[DataRow("10+(8*9)/0b0", "Expression contains division by zero")]
public void ErrorResultOnInvalidKeywordQuery(string typedString, string expectedResult)
{
Query expectedQuery = new(typedString, "=");
// Act
var result = _main.Object.Query(expectedQuery).FirstOrDefault().SubTitle;
// Assert
Assert.AreEqual(expectedResult, result);
}
[DataTestMethod]
[DataRow("pi(9+)")]
[DataRow("pi,")]
[DataRow("log()")]
[DataRow("0xf0x6")]
[DataRow("0xf,0x6")]
[DataRow("2^96")]
[DataRow("+()")]
[DataRow("[10,10]")]
[DataRow("5/0")]
[DataRow("5 / 0")]
[DataRow("10+(8*9)/0+7")]
[DataRow("10+(8*9)/0*7")]
public void NoResultOnInvalidGlobalQuery(string typedString)
{
// Setup
Query expectedQuery = new(typedString);
// Act
var result = _main.Object.Query(expectedQuery).Count;
// Assert
Assert.AreEqual(0, result);
}
[DataTestMethod]
[DataRow("9+")]
[DataRow("9-")]
[DataRow("9*")]
[DataRow("9|")]
[DataRow("9\\")]
[DataRow("9^")]
[DataRow("9=")]
[DataRow("9&")]
[DataRow("9/")]
[DataRow("9%")]
public void NoResultIfQueryEndsWithBinaryOperator(string typedString)
{
// Setup
Query expectedQuery = new(typedString);
Query expectedQueryWithKeyword = new("=" + typedString, "=");
// Act
var result = _main.Object.Query(expectedQuery).Count;
var resultWithKeyword = _main.Object.Query(expectedQueryWithKeyword).Count;
// Assert
Assert.AreEqual(0, result);
Assert.AreEqual(0, resultWithKeyword);
}
[DataTestMethod]
[DataRow("10+(8*9)/0,5")]// German decimal digit separator
[DataRow("10+(8*9)/0.5")]
[DataRow("10+(8*9)/1,5")] // German decimal digit separator
[DataRow("10+(8*9)/1.5")]
public void NoErrorForDivisionByNumberWithDecimalDigits(string typedString)
{
// Setup
Query expectedQuery = new(typedString);
Query expectedQueryWithKeyword = new("=" + typedString, "=");
// Act
var result = _main.Object.Query(expectedQuery).FirstOrDefault().SubTitle;
var resultWithKeyword = _main.Object.Query(expectedQueryWithKeyword).FirstOrDefault().SubTitle;
// Assert
Assert.AreEqual("Copy this number to the clipboard", result);
Assert.AreEqual("Copy this number to the clipboard", resultWithKeyword);
}
[DataTestMethod]
[DataRow("pie", "pi * e")]
[DataRow("eln(100)", "e * ln(100)")]
[DataRow("pi(1+1)", "pi * (1+1)")]
[DataRow("2pi", "2 * pi")]
[DataRow("2log10(100)", "2 * log10(100)")]
[DataRow("2(3+4)", "2 * (3+4)")]
[DataRow("sin(pi)cos(pi)", "sin(pi) * cos(pi)")]
[DataRow("log10(100)(2+3)", "log10(100) * (2+3)")]
[DataRow("(1+1)cos(pi)", "(1+1) * cos(pi)")]
[DataRow("(1+1)(2+2)", "(1+1) * (2+2)")]
[DataRow("2(1+1)", "2 * (1+1)")]
[DataRow("pi(1+1)", "pi * (1+1)")]
[DataRow("pilog(100)", "pi * log(100)")]
[DataRow("3log(100)", "3 * log(100)")]
[DataRow("2e", "2 * e")]
[DataRow("(1+1)(3+2)", "(1+1) * (3+2)")]
[DataRow("(1+1)cos(pi)", "(1+1) * cos(pi)")]
[DataRow("sin(pi)cos(pi)", "sin(pi) * cos(pi)")]
[DataRow("2 (1+1)", "2 * (1+1)")]
[DataRow("pi (1+1)", "pi * (1+1)")]
[DataRow("pi log(100)", "pi * log(100)")]
[DataRow("3 log(100)", "3 * log(100)")]
[DataRow("2 e", "2 * e")]
[DataRow("(1+1) (3+2)", "(1+1) * (3+2)")]
[DataRow("(1+1) cos(pi)", "(1+1) * cos(pi)")]
[DataRow("sin (pi) cos(pi)", "sin (pi) * cos(pi)")]
[DataRow("2picos(pi)(1+1)", "2 * pi * cos(pi) * (1+1)")]
[DataRow("pilog(100)log(1000)", "pi * log(100) * log(1000)")]
[DataRow("pipipie", "pi * pi * pi * e")]
[DataRow("(1+1)(3+2)(1+1)(1+1)", "(1+1) * (3+2) * (1+1) * (1+1)")]
[DataRow("(1+1) (3+2) (1+1)(1+1)", "(1+1) * (3+2) * (1+1) * (1+1)")]
[Run] Fix Scientific Notation Errors in Calculator Plugin (#28884) * Parse scientific notation properly After adding logic to replace `e` as a mathematical constant, there are bugs when trying to use expressions like `5e3`. This change parses the `<number>e<number>` format into expanded form to prevent replacement with the constant. Regex explanation: `(\d+\.*\d*)[eE](-*\d+) `(\d+\.*\d*)`: Match any number of digits, followed by an optional `.` and more optional digits. The expression is used to capture things like: `5.0`, `1.`, `1` before the `e` `[eE]`: Match either upper or lowercase `e` `(-*\d+)`: Capture an optional `-` sign as well as a number of digits * Update regex to be more tolerant of weird entries The new regex captures a wider variety of numbers. See this post for details on the regex used: https://stackoverflow.com/a/23872060 * Fix regular expression failing unit tests Using `[` didn't capture the expression properly. Had to use `(` instead. * Allow only for uppercase E * Only allow one decimal in second grouping * Support various decimal separator types Previous regular expression did not allow decimal separators that were not ".". The new expression uses the culture info decimal separator instead, which should work better. * Only allow integers after `E` * Remove single use variable * Update regex to only accept integers after `E` Missed this expression in my last update. Whoops * Update src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateHelper.cs * Update NumberTranslator to parse hex as a whole * Remove `hexRegex` as object member The hex regex stuff is only used once, there's no need to keep track of it in the object's state. Just create it during the translation process. * Add unit tests for sci notation in other cultures --------- Co-authored-by: Jaime Bernardo <jaime@janeasystems.com> Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com>
2024-01-03 06:22:09 -05:00
[DataRow("1.0E2", "(1.0 * 10^(2))")]
[DataRow("-1.0E-2", "(-1.0 * 10^(-2))")]
[DataRow("1.2E2", "(1.2 * 10^(2))")]
[DataRow("5/1.0E2", "5/(1.0 * 10^(2))")]
[DataRow("0.1E2", "(0.1 * 10^(2))")]
[DataRow(".1E2", "(.1 * 10^(2))")]
[DataRow(".1E2", "(.1 * 10^(2))")]
[DataRow("5/5E3", "5/(5 * 10^(3))")]
[DataRow("1.E2", "(1. * 10^(2))")]
public void RightHumanMultiplicationExpressionTransformation(string typedString, string expectedQuery)
{
// Setup
// Act
var result = CalculateHelper.FixHumanMultiplicationExpressions(typedString);
// Assert
Assert.AreEqual(expectedQuery, result);
}
[DataTestMethod]
[DataRow("2(1+1)")]
[DataRow("pi(1+1)")]
[DataRow("pilog(100)")]
[DataRow("3log(100)")]
[DataRow("2e")]
[DataRow("(1+1)(3+2)")]
[DataRow("(1+1)cos(pi)")]
[DataRow("sin(pi)cos(pi)")]
[DataRow("2 (1+1)")]
[DataRow("pi (1+1)")]
[DataRow("pi log(100)")]
[DataRow("3 log(100)")]
[DataRow("2 e")]
[DataRow("(1+1) (3+2)")]
[DataRow("(1+1) cos(pi)")]
[DataRow("sin (pi) cos(pi)")]
[DataRow("2picos(pi)(1+1)")]
[DataRow("pilog(100)log(1000)")]
[DataRow("pipipie")]
[DataRow("(1+1)(3+2)(1+1)(1+1)")]
[DataRow("(1+1) (3+2) (1+1)(1+1)")]
public void NoErrorForHumanMultiplicationExpressions(string typedString)
{
// Setup
Query expectedQuery = new(typedString);
Query expectedQueryWithKeyword = new("=" + typedString, "=");
// Act
var result = _main.Object.Query(expectedQuery).FirstOrDefault()?.SubTitle;
var resultWithKeyword = _main.Object.Query(expectedQueryWithKeyword).FirstOrDefault()?.SubTitle;
// Assert
Assert.AreEqual("Copy this number to the clipboard", result);
Assert.AreEqual("Copy this number to the clipboard", resultWithKeyword);
}
[DataTestMethod]
[DataRow("2(1+1)", 4)]
[DataRow("pi(1+1)", 6.2831853072)]
[DataRow("pilog(100)", 6.2831853072)]
[DataRow("3log(100)", 6)]
[DataRow("2e", 5.4365636569)]
[Run] Fix Scientific Notation Errors in Calculator Plugin (#28884) * Parse scientific notation properly After adding logic to replace `e` as a mathematical constant, there are bugs when trying to use expressions like `5e3`. This change parses the `<number>e<number>` format into expanded form to prevent replacement with the constant. Regex explanation: `(\d+\.*\d*)[eE](-*\d+) `(\d+\.*\d*)`: Match any number of digits, followed by an optional `.` and more optional digits. The expression is used to capture things like: `5.0`, `1.`, `1` before the `e` `[eE]`: Match either upper or lowercase `e` `(-*\d+)`: Capture an optional `-` sign as well as a number of digits * Update regex to be more tolerant of weird entries The new regex captures a wider variety of numbers. See this post for details on the regex used: https://stackoverflow.com/a/23872060 * Fix regular expression failing unit tests Using `[` didn't capture the expression properly. Had to use `(` instead. * Allow only for uppercase E * Only allow one decimal in second grouping * Support various decimal separator types Previous regular expression did not allow decimal separators that were not ".". The new expression uses the culture info decimal separator instead, which should work better. * Only allow integers after `E` * Remove single use variable * Update regex to only accept integers after `E` Missed this expression in my last update. Whoops * Update src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/CalculateHelper.cs * Update NumberTranslator to parse hex as a whole * Remove `hexRegex` as object member The hex regex stuff is only used once, there's no need to keep track of it in the object's state. Just create it during the translation process. * Add unit tests for sci notation in other cultures --------- Co-authored-by: Jaime Bernardo <jaime@janeasystems.com> Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com>
2024-01-03 06:22:09 -05:00
[DataRow("2E2", 200)]
[DataRow("2E-2", 0.02)]
[DataRow("1.2E2", 120)]
[DataRow("1.2E-1", 0.12)]
[DataRow("5/5E3", 0.001)]
[DataRow("-5/5E3", -0.001)]
[DataRow("(1+1)(3+2)", 10)]
[DataRow("(1+1)cos(pi)", -2)]
[DataRow("log(100)cos(pi)", -2)]
public void RightAnswerForHumanMultiplicationExpressions(string typedString, double answer)
{
// Setup
typedString = typedString.Replace(".", CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator); // Workaround to get correct results when the tests are running on a non-english systems.
Query expectedQuery = new(typedString);
Query expectedQueryWithKeyword = new("=" + typedString, "=");
// Act
var result = _main.Object.Query(expectedQuery).FirstOrDefault()?.Title;
var resultWithKeyword = _main.Object.Query(expectedQueryWithKeyword).FirstOrDefault()?.Title;
// Assert
Assert.AreEqual(answer.ToString(CultureInfo.CurrentCulture), result);
Assert.AreEqual(answer.ToString(CultureInfo.CurrentCulture), resultWithKeyword);
}
[DataTestMethod]
[DataRow("0x1234+0x1234", 9320)]
[DataRow("0x1234-0x1234", 0)]
[DataRow("0x12345678+0x12345678", 610839792)]
[DataRow("0xf4572220-0xf4572410", -496)]
public void RightAnswerForLargeHexadecimalNumbers(string typedString, double answer)
{
// Setup
Query expectedQuery = new(typedString);
Query expectedQueryWithKeyword = new("=" + typedString, "=");
// Act
var result = _main.Object.Query(expectedQuery).FirstOrDefault()?.Title;
var resultWithKeyword = _main.Object.Query(expectedQueryWithKeyword).FirstOrDefault()?.Title;
// Assert
Assert.AreEqual(answer.ToString(CultureInfo.CurrentCulture), result);
Assert.AreEqual(answer.ToString(CultureInfo.CurrentCulture), resultWithKeyword);
}
[DataTestMethod]
[DataRow("#", "#1+1=", true)]
[DataRow("#", "#1+1", false)]
[DataRow("#", "#1/0=", false)]
[DataRow("", "1+1=", false)]
public void HandleInputReplace(string actionKeyword, string query, bool shouldChangeQuery)
{
// Setup
Query expectedQuery = new(query, actionKeyword);
_main.Object.UpdateSettings(new PowerLauncherPluginSettings()); // Default settings
// Act
_main.Object.Query(expectedQuery);
// Assert
_api.Verify(api => api.ChangeQuery(It.IsAny<string>(), It.IsAny<bool>()), shouldChangeQuery ? Times.Once : Times.Never);
}
}
}