From 8569725e35942e080f5dae7233b9c1b57d7a923a Mon Sep 17 00:00:00 2001 From: Leilei Zhang Date: Wed, 23 Jul 2025 22:16:25 +0800 Subject: [PATCH] add unit tests for cmdpal calc --- PowerToys.sln | 11 + .../BracketHelperTests.cs | 51 +++ .../ExtendedCalculatorParserTests.cs | 389 ++++++++++++++++++ ...Microsoft.CmdPal.Ext.Calc.UnitTests.csproj | 18 + .../NumberTranslatorTests.cs | 184 +++++++++ .../Helper/CalculateEngine.cs | 2 +- 6 files changed, 654 insertions(+), 1 deletion(-) create mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/BracketHelperTests.cs create mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/ExtendedCalculatorParserTests.cs create mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/Microsoft.CmdPal.Ext.Calc.UnitTests.csproj create mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/NumberTranslatorTests.cs diff --git a/PowerToys.sln b/PowerToys.sln index 4aa1b87144..9489c7de56 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -759,6 +759,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdvancedPaste-UITests", "sr EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9B3962F4-AB69-4C2A-8917-2C8448AC6960}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Calc.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Calc.UnitTests\Microsoft.CmdPal.Ext.Calc.UnitTests.csproj", "{E816D7AC-4688-4ECB-97CC-3D8E798F3825}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -2829,6 +2831,14 @@ Global {2B1505FA-132A-460B-B22B-7CC3FFAB0C5D}.Release|ARM64.Build.0 = Release|ARM64 {2B1505FA-132A-460B-B22B-7CC3FFAB0C5D}.Release|x64.ActiveCfg = Release|x64 {2B1505FA-132A-460B-B22B-7CC3FFAB0C5D}.Release|x64.Build.0 = Release|x64 + {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Debug|ARM64.Build.0 = Debug|ARM64 + {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Debug|x64.ActiveCfg = Debug|x64 + {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Debug|x64.Build.0 = Debug|x64 + {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Release|ARM64.ActiveCfg = Release|ARM64 + {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Release|ARM64.Build.0 = Release|ARM64 + {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Release|x64.ActiveCfg = Release|x64 + {E816D7AC-4688-4ECB-97CC-3D8E798F3825}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -3127,6 +3137,7 @@ Global {E4BAAD93-A499-42FD-A741-7E9591594B61} = {1D78B84B-CA39-406C-98F4-71F7EC266CC0} {2B1505FA-132A-460B-B22B-7CC3FFAB0C5D} = {9B3962F4-AB69-4C2A-8917-2C8448AC6960} {9B3962F4-AB69-4C2A-8917-2C8448AC6960} = {9873BA05-4C41-4819-9283-CF45D795431B} + {E816D7AC-4688-4ECB-97CC-3D8E798F3825} = {15EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/BracketHelperTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/BracketHelperTests.cs new file mode 100644 index 0000000000..0209f37ec9 --- /dev/null +++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/BracketHelperTests.cs @@ -0,0 +1,51 @@ +// 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 Microsoft.CmdPal.Ext.Calc.Helper; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.CmdPal.Ext.Calc.UnitTests; + +[TestClass] +public class BracketHelperTests +{ + [DataTestMethod] + [DataRow(null)] + [DataRow("")] + [DataRow("\t \r\n")] + [DataRow("none")] + [DataRow("()")] + [DataRow("(())")] + [DataRow("()()")] + [DataRow("(()())")] + [DataRow("([][])")] + [DataRow("([(()[])[](([]()))])")] + public void IsBracketComplete_TestValid_WhenCalled(string input) + { + // Arrange + + // Act + var result = BracketHelper.IsBracketComplete(input); + + // Assert + Assert.IsTrue(result); + } + + [DataTestMethod] + [DataRow("((((", "only opening brackets")] + [DataRow("]]]", "only closing brackets")] + [DataRow("([)(])", "inner bracket mismatch")] + [DataRow(")(", "opening and closing reversed")] + [DataRow("(]", "mismatch in bracket type")] + public void IsBracketComplete_TestInvalid_WhenCalled(string input, string invalidReason) + { + // Arrange + + // Act + var result = BracketHelper.IsBracketComplete(input); + + // Assert + Assert.IsFalse(result, invalidReason); + } +} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/ExtendedCalculatorParserTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/ExtendedCalculatorParserTests.cs new file mode 100644 index 0000000000..e5929de717 --- /dev/null +++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/ExtendedCalculatorParserTests.cs @@ -0,0 +1,389 @@ +// 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; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.CmdPal.Ext.Calc.Helper; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.CmdPal.Ext.Calc.UnitTests; + +[TestClass] +public class ExtendedCalculatorParserTests +{ + [DataTestMethod] + [DataRow(null)] + [DataRow("")] + [DataRow(" ")] + public void InputValid_ThrowError_WhenCalledNullOrEmpty(string input) + { + // Act + Assert.IsTrue(!CalculateHelper.InputValid(input)); + } + + [DataTestMethod] + [DataRow("test")] + [DataRow("[10,10]")] // '[10,10]' is interpreted as array by mages engine + public void Interpret_NoResult_WhenCalled(string input) + { + var settings = new SettingsManager(); + + var result = CalculateEngine.Interpret(settings, input, CultureInfo.CurrentCulture, out _); + + // Assert + Assert.AreEqual(default(CalculateResult), result); + } + + private static IEnumerable Interpret_NoErrors_WhenCalledWithRounding_Data => + [ + ["2 * 2", 4M], + ["-2 ^ 2", -4M], + ["-(2 ^ 2)", -4M], + ["2 * pi", 6.28318530717959M], + ["round(2 * pi)", 6M], + + // ["1 == 2", default(decimal)], + ["pi * ( sin ( cos ( 2)))", -1.26995475603563M], + ["5.6/2", 2.8M], + ["123 * 4.56", 560.88M], + ["1 - 9.0 / 10", 0.1M], + ["0.5 * ((2*-395.2)+198.2)", -296.1M], + ["2+2.11", 4.11M], + ["8.43 + 4.43 - 12.86", 0M], + ["8.43 + 4.43 - 12.8", 0.06M], + ["exp(5)", 148.413159102577M], + ["e^5", 148.413159102577M], + ["e*2", 5.43656365691809M], + ["ln(3)", 1.09861228866811M], + ["log(3)", 0.47712125471966M], + ["log2(3)", 1.58496250072116M], + ["log10(3)", 0.47712125471966M], + ["ln(e)", 1M], + ["cosh(0)", 1M], + ]; + + [DataTestMethod] + [DynamicData(nameof(Interpret_NoErrors_WhenCalledWithRounding_Data))] + public void Interpret_NoErrors_WhenCalledWithRounding(string input, decimal expectedResult) + { + var settings = new SettingsManager(); + + // Act + // Using InvariantCulture since this is internal + var result = CalculateEngine.Interpret(settings, input, CultureInfo.InvariantCulture, out _); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(CalculateEngine.FormatMax15Digits(expectedResult, new CultureInfo("en-US")), result.RoundedResult); + } + + private static IEnumerable Interpret_GreaterPrecision_WhenCalled_Data => + [ + ["0.100000000000000000000", 0.1M], + ["0.200000000000000000000000", 0.2M], + ]; + + [DynamicData(nameof(Interpret_GreaterPrecision_WhenCalled_Data))] + [DataTestMethod] + public void Interpret_GreaterPrecision_WhenCalled(string input, decimal expectedResult) + { + // Arrange + var settings = new SettingsManager(); + + // Act + // Using InvariantCulture since this is internal + var result = CalculateEngine.Interpret(settings, input, CultureInfo.InvariantCulture, out _); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedResult, result.Result); + } + + private static IEnumerable Interpret_DifferentCulture_WhenCalled_Data => + [ + ["4.5/3", 1.5M, "nl-NL"], + ["4.5/3", 1.5M, "en-EN"], + ["4.5/3", 1.5M, "de-DE"], + ]; + + [DataTestMethod] + [DynamicData(nameof(Interpret_DifferentCulture_WhenCalled_Data))] + public void Interpret_DifferentCulture_WhenCalled(string input, decimal expectedResult, string cultureName) + { + // Arrange + var cultureInfo = CultureInfo.GetCultureInfo(cultureName); + var settings = new SettingsManager(); + + // Act + var result = CalculateEngine.Interpret(settings, input, cultureInfo, out _); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(CalculateEngine.Round(expectedResult), result.RoundedResult); + } + + [DataTestMethod] + [DataRow("log(3)", true)] + [DataRow("ln(3)", true)] + [DataRow("log2(3)", true)] + [DataRow("log10(3)", true)] + [DataRow("log2", false)] + [DataRow("log10", false)] + [DataRow("log", false)] + [DataRow("ln", false)] + [DataRow("ceil(2 * (pi ^ 2))", true)] + [DataRow("((1 * 2)", false)] + [DataRow("(1 * 2)))", false)] + [DataRow("abcde", false)] + [DataRow("1 + 2 +", false)] + [DataRow("1+2*", false)] + [DataRow("1+2/", false)] + [DataRow("1+2%", false)] + [DataRow("1 && 3 &&", false)] + [DataRow("sqrt( 36)", true)] + [DataRow("max 4", false)] + [DataRow("sin(0)", true)] + [DataRow("sinh(1)", true)] + [DataRow("tanh(0)", true)] + [DataRow("artanh(pi/2)", true)] + [DataRow("cosh", false)] + [DataRow("cos", false)] + [DataRow("abs", false)] + [DataRow("1+1.1e3", true)] + [DataRow("randi(8)", true)] + [DataRow("randi()", false)] + [DataRow("randi(0.5)", true)] + [DataRow("rand()", true)] + [DataRow("rand(0.5)", false)] + [DataRow("0X78AD+0o123", true)] + [DataRow("0o9", false)] + public void InputValid_TestValid_WhenCalled(string input, bool valid) + { + // Act + var result = CalculateHelper.InputValid(input); + + // Assert + Assert.AreEqual(valid, result); + } + + [DataTestMethod] + [DataRow("1-1")] + [DataRow("sin(0)")] + [DataRow("sinh(0)")] + public void Interpret_MustReturnResult_WhenResultIsZero(string input) + { + // Arrange + var settings = new SettingsManager(); + + // Act + // Using InvariantCulture since this is internal + var result = CalculateEngine.Interpret(settings, input, CultureInfo.InvariantCulture, out _); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(0.0M, result.Result); + } + + private static IEnumerable Interpret_MustReturnExpectedResult_WhenCalled_Data => + [ + + // ["factorial(5)", 120M], ToDo: this don't support now + // ["sign(-2)", -1M], + // ["sign(2)", +1M], + ["abs(-2)", 2M], + ["abs(2)", 2M], + ["0+(1*2)/(0+1)", 2M], // Validate that division by "(0+1)" is not interpret as division by zero. + ["0+(1*2)/0.5", 4M], // Validate that division by number with decimal digits is not interpret as division by zero. + ]; + + [DataTestMethod] + [DynamicData(nameof(Interpret_MustReturnExpectedResult_WhenCalled_Data))] + public void Interpret_MustReturnExpectedResult_WhenCalled(string input, decimal expectedResult) + { + // Arrange + var settings = new SettingsManager(); + + // Act + // Using en-us culture to have a fixed number style + var result = CalculateEngine.Interpret(settings, input, new CultureInfo("en-us", false), out _); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedResult, result.Result); + } + + private static IEnumerable Interpret_TestScientificNotation_WhenCalled_Data => + [ + ["0.2E1", "en-US", 2M], + ["0,2E1", "pt-PT", 2M], + ]; + + [DataTestMethod] + [DynamicData(nameof(Interpret_TestScientificNotation_WhenCalled_Data))] + public void Interpret_TestScientificNotation_WhenCalled(string input, string sourceCultureName, decimal expectedResult) + { + // Arrange + var translator = NumberTranslator.Create(new CultureInfo(sourceCultureName, false), new CultureInfo("en-US", false)); + var settings = new SettingsManager(); + + // Act + // Using en-us culture to have a fixed number style + var translatedInput = translator.Translate(input); + var result = CalculateEngine.Interpret(settings, translatedInput, new CultureInfo("en-US", false), out _); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedResult, result.Result); + } + + [DataTestMethod] + [DataRow("sin(90)", "sin((pi / 180) * (90))")] + [DataRow("arcsin(0.5)", "(180 / pi) * (arcsin(0.5))")] + [DataRow("sin(sin(30))", "sin((pi / 180) * (sin((pi / 180) * (30))))")] + [DataRow("cos(tan(45))", "cos((pi / 180) * (tan((pi / 180) * (45))))")] + [DataRow("arctan(sin(30))", "(180 / pi) * (arctan(sin((pi / 180) * (30))))")] + [DataRow("sin(cos(tan(30)))", "sin((pi / 180) * (cos((pi / 180) * (tan((pi / 180) * (30))))))")] + [DataRow("sin(arcsin(0.5))", "sin((pi / 180) * ((180 / pi) * (arcsin(0.5))))")] + [DataRow("sin(30) + cos(60)", "sin((pi / 180) * (30)) + cos((pi / 180) * (60))")] + [DataRow("sin(30 + 15)", "sin((pi / 180) * (30 + 15))")] + [DataRow("sin(45) * cos(45) - tan(30)", "sin((pi / 180) * (45)) * cos((pi / 180) * (45)) - tan((pi / 180) * (30))")] + [DataRow("arcsin(arccos(0.5))", "(180 / pi) * (arcsin((180 / pi) * (arccos(0.5))))")] + [DataRow("sin(sin(sin(30)))", "sin((pi / 180) * (sin((pi / 180) * (sin((pi / 180) * (30))))))")] + [DataRow("log(10)", "log(10)")] + [DataRow("sin(30) + pi", "sin((pi / 180) * (30)) + pi")] + [DataRow("sin(-30)", "sin((pi / 180) * (-30))")] + [DataRow("sin((30))", "sin((pi / 180) * ((30)))")] + [DataRow("arcsin(1) * 2", "(180 / pi) * (arcsin(1)) * 2")] + [DataRow("cos(1/2)", "cos((pi / 180) * (1/2))")] + [DataRow("sin ( 90 )", "sin ((pi / 180) * ( 90 ))")] + [DataRow("cos(arcsin(sin(45)))", "cos((pi / 180) * ((180 / pi) * (arcsin(sin((pi / 180) * (45))))))")] + public void UpdateTrigFunctions_Degrees(string input, string expectedResult) + { + // Call UpdateTrigFunctions in degrees mode + var result = CalculateHelper.UpdateTrigFunctions(input, CalculateEngine.TrigMode.Degrees); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedResult, result); + } + + [DataTestMethod] + [DataRow("sin(90)", "sin((pi / 200) * (90))")] + [DataRow("arcsin(0.5)", "(200 / pi) * (arcsin(0.5))")] + [DataRow("sin(sin(30))", "sin((pi / 200) * (sin((pi / 200) * (30))))")] + [DataRow("cos(tan(45))", "cos((pi / 200) * (tan((pi / 200) * (45))))")] + [DataRow("arctan(sin(30))", "(200 / pi) * (arctan(sin((pi / 200) * (30))))")] + [DataRow("sin(cos(tan(30)))", "sin((pi / 200) * (cos((pi / 200) * (tan((pi / 200) * (30))))))")] + [DataRow("sin(arcsin(0.5))", "sin((pi / 200) * ((200 / pi) * (arcsin(0.5))))")] + [DataRow("sin(30) + cos(60)", "sin((pi / 200) * (30)) + cos((pi / 200) * (60))")] + [DataRow("sin(30 + 15)", "sin((pi / 200) * (30 + 15))")] + [DataRow("sin(45) * cos(45) - tan(30)", "sin((pi / 200) * (45)) * cos((pi / 200) * (45)) - tan((pi / 200) * (30))")] + [DataRow("arcsin(arccos(0.5))", "(200 / pi) * (arcsin((200 / pi) * (arccos(0.5))))")] + [DataRow("sin(sin(sin(30)))", "sin((pi / 200) * (sin((pi / 200) * (sin((pi / 200) * (30))))))")] + [DataRow("log(10)", "log(10)")] + [DataRow("sin(30) + pi", "sin((pi / 200) * (30)) + pi")] + [DataRow("sin(-30)", "sin((pi / 200) * (-30))")] + [DataRow("sin((30))", "sin((pi / 200) * ((30)))")] + [DataRow("arcsin(1) * 2", "(200 / pi) * (arcsin(1)) * 2")] + [DataRow("cos(1/2)", "cos((pi / 200) * (1/2))")] + [DataRow("sin ( 90 )", "sin ((pi / 200) * ( 90 ))")] + [DataRow("cos(arcsin(sin(45)))", "cos((pi / 200) * ((200 / pi) * (arcsin(sin((pi / 200) * (45))))))")] + public void UpdateTrigFunctions_Gradians(string input, string expectedResult) + { + // Call UpdateTrigFunctions in gradians mode + var result = CalculateHelper.UpdateTrigFunctions(input, CalculateEngine.TrigMode.Gradians); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedResult, result); + } + + [DataTestMethod] + [DataRow("rad(30)", "(180 / pi) * (30)")] + [DataRow("rad( 30 )", "(180 / pi) * ( 30 )")] + [DataRow("deg(30)", "(30)")] + [DataRow("grad(30)", "(9 / 10) * (30)")] + [DataRow("rad( 30)", "(180 / pi) * ( 30)")] + [DataRow("rad(30 )", "(180 / pi) * (30 )")] + [DataRow("rad( 30 )", "(180 / pi) * ( 30 )")] + [DataRow("rad(deg(30))", "(180 / pi) * ((30))")] + [DataRow("deg(rad(30))", "((180 / pi) * (30))")] + [DataRow("grad(rad(30))", "(9 / 10) * ((180 / pi) * (30))")] + [DataRow("rad(grad(30))", "(180 / pi) * ((9 / 10) * (30))")] + [DataRow("rad(30) + deg(45)", "(180 / pi) * (30) + (45)")] + [DataRow("sin(rad(30))", "sin((180 / pi) * (30))")] + [DataRow("cos( rad( 45 ) )", "cos( (180 / pi) * ( 45 ) )")] + [DataRow("tan(rad(grad(90)))", "tan((180 / pi) * ((9 / 10) * (90)))")] + [DataRow("rad(30) + rad(45)", "(180 / pi) * (30) + (180 / pi) * (45)")] + [DataRow("rad(30) * grad(90)", "(180 / pi) * (30) * (9 / 10) * (90)")] + [DataRow("rad(30)/rad(45)", "(180 / pi) * (30)/(180 / pi) * (45)")] + public void ExpandTrigConversions_Degrees(string input, string expectedResult) + { + // Call ExpandTrigConversions in degrees mode + var result = CalculateHelper.ExpandTrigConversions(input, CalculateEngine.TrigMode.Degrees); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedResult, result); + } + + [DataTestMethod] + [DataRow("rad(30)", "(30)")] + [DataRow("rad( 30 )", "( 30 )")] + [DataRow("deg(30)", "(pi / 180) * (30)")] + [DataRow("grad(30)", "(pi / 200) * (30)")] + [DataRow("rad( 30)", "( 30)")] + [DataRow("rad(30 )", "(30 )")] + [DataRow("rad( 30 )", "( 30 )")] + [DataRow("rad(deg(30))", "((pi / 180) * (30))")] + [DataRow("deg(rad(30))", "(pi / 180) * ((30))")] + [DataRow("grad(rad(30))", "(pi / 200) * ((30))")] + [DataRow("rad(grad(30))", "((pi / 200) * (30))")] + [DataRow("rad(30) + deg(45)", "(30) + (pi / 180) * (45)")] + [DataRow("sin(rad(30))", "sin((30))")] + [DataRow("cos( rad( 45 ) )", "cos( ( 45 ) )")] + [DataRow("tan(rad(grad(90)))", "tan(((pi / 200) * (90)))")] + [DataRow("rad(30) + rad(45)", "(30) + (45)")] + [DataRow("rad(30) * grad(90)", "(30) * (pi / 200) * (90)")] + [DataRow("rad(30)/rad(45)", "(30)/(45)")] + public void ExpandTrigConversions_Radians(string input, string expectedResult) + { + // Call ExpandTrigConversions in radians mode + var result = CalculateHelper.ExpandTrigConversions(input, CalculateEngine.TrigMode.Radians); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedResult, result); + } + + [DataTestMethod] + [DataRow("rad(30)", "(200 / pi) * (30)")] + [DataRow("rad( 30 )", "(200 / pi) * ( 30 )")] + [DataRow("deg(30)", "(10 / 9) * (30)")] + [DataRow("grad(30)", "(30)")] + [DataRow("rad( 30)", "(200 / pi) * ( 30)")] + [DataRow("rad(30 )", "(200 / pi) * (30 )")] + [DataRow("rad( 30 )", "(200 / pi) * ( 30 )")] + [DataRow("rad(deg(30))", "(200 / pi) * ((10 / 9) * (30))")] + [DataRow("deg(rad(30))", "(10 / 9) * ((200 / pi) * (30))")] + [DataRow("grad(rad(30))", "((200 / pi) * (30))")] + [DataRow("rad(grad(30))", "(200 / pi) * ((30))")] + [DataRow("rad(30) + deg(45)", "(200 / pi) * (30) + (10 / 9) * (45)")] + [DataRow("sin(rad(30))", "sin((200 / pi) * (30))")] + [DataRow("cos( rad( 45 ) )", "cos( (200 / pi) * ( 45 ) )")] + [DataRow("tan(rad(grad(90)))", "tan((200 / pi) * ((90)))")] + [DataRow("rad(30) + rad(45)", "(200 / pi) * (30) + (200 / pi) * (45)")] + [DataRow("rad(30) * grad(90)", "(200 / pi) * (30) * (90)")] + [DataRow("rad(30)/rad(45)", "(200 / pi) * (30)/(200 / pi) * (45)")] + public void ExpandTrigConversions_Gradians(string input, string expectedResult) + { + // Call ExpandTrigConversions in gradians mode + var result = CalculateHelper.ExpandTrigConversions(input, CalculateEngine.TrigMode.Gradians); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedResult, result); + } +} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/Microsoft.CmdPal.Ext.Calc.UnitTests.csproj b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/Microsoft.CmdPal.Ext.Calc.UnitTests.csproj new file mode 100644 index 0000000000..7144678183 --- /dev/null +++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/Microsoft.CmdPal.Ext.Calc.UnitTests.csproj @@ -0,0 +1,18 @@ + + + + + + false + Microsoft.CmdPal.Ext.Calc.UnitTests + true + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/NumberTranslatorTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/NumberTranslatorTests.cs new file mode 100644 index 0000000000..0dbd8f4399 --- /dev/null +++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Calc.UnitTests/NumberTranslatorTests.cs @@ -0,0 +1,184 @@ +// 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; +using System.Globalization; +using Microsoft.CmdPal.Ext.Calc.Helper; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.CmdPal.Ext.Calc.UnitTests; + +[TestClass] +public class NumberTranslatorTests +{ + [DataTestMethod] + [DataRow(null, "en-US")] + [DataRow("de-DE", null)] + public void Create_ThrowError_WhenCalledNullOrEmpty(string sourceCultureName, string targetCultureName) + { + // Arrange + CultureInfo sourceCulture = sourceCultureName != null ? new CultureInfo(sourceCultureName) : null; + CultureInfo targetCulture = targetCultureName != null ? new CultureInfo(targetCultureName) : null; + + // Act + Assert.ThrowsException(() => NumberTranslator.Create(sourceCulture, targetCulture)); + } + + [DataTestMethod] + [DataRow("en-US", "en-US")] + [DataRow("en-EN", "en-US")] + [DataRow("de-DE", "en-US")] + public void Create_WhenCalled(string sourceCultureName, string targetCultureName) + { + // Arrange + CultureInfo sourceCulture = new CultureInfo(sourceCultureName); + CultureInfo targetCulture = new CultureInfo(targetCultureName); + + // Act + var translator = NumberTranslator.Create(sourceCulture, targetCulture); + + // Assert + Assert.IsNotNull(translator); + } + + [DataTestMethod] + [DataRow(null)] + public void Translate_ThrowError_WhenCalledNull(string input) + { + // Arrange + var translator = NumberTranslator.Create(new CultureInfo("de-DE", false), new CultureInfo("en-US", false)); + + // Act + Assert.ThrowsException(() => translator.Translate(input)); + } + + [DataTestMethod] + [DataRow("")] + [DataRow(" ")] + public void Translate_WhenCalledEmpty(string input) + { + // Arrange + var translator = NumberTranslator.Create(new CultureInfo("de-DE", false), new CultureInfo("en-US", false)); + + // Act + var result = translator.Translate(input); + + // Assert + Assert.AreEqual(input, result); + } + + [DataTestMethod] + [DataRow("2,0 * 2", "2.0 * 2")] + [DataRow("4 * 3,6 + 9", "4 * 3.6 + 9")] + [DataRow("5,2+6", "5.2+6")] + [DataRow("round(2,5)", "round(2.5)")] + [DataRow("3,3333", "3.3333")] + [DataRow("max(2;3)", "max(2,3)")] + public void Translate_NoErrors_WhenCalled(string input, string expectedResult) + { + // Arrange + var translator = NumberTranslator.Create(new CultureInfo("de-DE", false), new CultureInfo("en-US", false)); + + // Act + var result = translator.Translate(input); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedResult, result); + } + + [DataTestMethod] + [DataRow("2.0 * 2", "2,0 * 2")] + [DataRow("4 * 3.6 + 9", "4 * 3,6 + 9")] + [DataRow("5.2+6", "5,2+6")] + [DataRow("round(2.5)", "round(2,5)")] + [DataRow("3.3333", "3,3333")] + public void TranslateBack_NoErrors_WhenCalled(string input, string expectedResult) + { + // Arrange + var translator = NumberTranslator.Create(new CultureInfo("de-DE", false), new CultureInfo("en-US", false)); + + // Act + var result = translator.TranslateBack(input); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedResult, result); + } + + [DataTestMethod] + [DataRow(".", ",", "2,000,000", "2000000")] + [DataRow(".", ",", "2,000,000.6", "2000000.6")] + [DataRow(",", ".", "2.000.000", "2000000")] + [DataRow(",", ".", "2.000.000,6", "2000000.6")] + public void Translate_RemoveNumberGroupSeparator_WhenCalled(string decimalSeparator, string groupSeparator, string input, string expectedResult) + { + // Arrange + var sourceCulture = new CultureInfo("en-US", false) + { + NumberFormat = + { + NumberDecimalSeparator = decimalSeparator, + NumberGroupSeparator = groupSeparator, + }, + }; + var translator = NumberTranslator.Create(sourceCulture, new CultureInfo("en-US", false)); + + // Act + var result = translator.Translate(input); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedResult, result); + } + + [DataTestMethod] + [DataRow("de-DE", "12,0004", "12.0004")] + [DataRow("de-DE", "0xF000", "61440")] + [DataRow("de-DE", "0", "0")] + [DataRow("de-DE", "00", "0")] + [DataRow("de-DE", "12.004", "12004")] // . is the group separator in de-DE + [DataRow("de-DE", "12.04", "1204")] + [DataRow("de-DE", "12.4", "124")] + [DataRow("de-DE", "3.004.044.444,05", "3004044444.05")] + [DataRow("de-DE", "123.01 + 52.30", "12301 + 5230")] + [DataRow("de-DE", "123.001 + 52.30", "123001 + 5230")] + [DataRow("fr-FR", "0", "0")] + [DataRow("fr-FR", "00", "0")] + [DataRow("fr-FR", "12.004", "12.004")] // . is not decimal or group separator in fr-FR + [DataRow("fr-FR", "12.04", "12.04")] + [DataRow("fr-FR", "12.4", "12.4")] + [DataRow("fr-FR", "12.0004", "12.0004")] + [DataRow("fr-FR", "123.01 + 52.30", "123.01 + 52.30")] + [DataRow("fr-FR", "123.001 + 52.30", "123.001 + 52.30")] + public void Translate_NoRemovalOfLeadingZeroesOnEdgeCases(string sourceCultureName, string input, string expectedResult) + { + // Arrange + var translator = NumberTranslator.Create(new CultureInfo(sourceCultureName, false), new CultureInfo("en-US", false)); + + // Act + var result = translator.Translate(input); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedResult, result); + } + + [DataTestMethod] + [DataRow("en-US", "0xF000", "61440")] + [DataRow("en-US", "0xf4572220", "4099351072")] + [DataRow("en-US", "0x12345678", "305419896")] + public void Translate_LargeHexadecimalNumbersToDecimal(string sourceCultureName, string input, string expectedResult) + { + // Arrange + var translator = NumberTranslator.Create(new CultureInfo(sourceCultureName, false), new CultureInfo("en-US", false)); + + // Act + var result = translator.Translate(input); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedResult, result); + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateEngine.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateEngine.cs index 2b375bfe3a..ca2e1d1ea8 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateEngine.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Calc/Helper/CalculateEngine.cs @@ -108,7 +108,7 @@ public static class CalculateEngine /// 100000.9999999999 → "100001" /// 1234567890123.45 → "1234567890123.45" /// - private static decimal FormatMax15Digits(decimal value, CultureInfo cultureInfo) + public static decimal FormatMax15Digits(decimal value, CultureInfo cultureInfo) { var absValue = Math.Abs(value); var integerDigits = absValue >= 1 ? (int)Math.Floor(Math.Log10((double)absValue)) + 1 : 1;