diff --git a/samples/calculator/testPlanWithCommaForDecimal.fx.yaml b/samples/calculator/testPlanWithCommaForDecimal.fx.yaml index 9897679fb..b5558377a 100644 --- a/samples/calculator/testPlanWithCommaForDecimal.fx.yaml +++ b/samples/calculator/testPlanWithCommaForDecimal.fx.yaml @@ -3,7 +3,7 @@ testSuite: testSuiteDescription: Verifies that the calculator app works. The calculator is a component. persona: User1 # appId: fd32a609-eaca-4d91-b5b4-31651d265133 - appLogicalName: new_calculator_a3613 + appLogicalName: new_calculator2_11d82 # This sample has been written to demonstrate tests written in regions that use commas ',' instead of # periods '.' for a decimal separator. E.g., "10,22" instead of "10.22". However since all of our tests @@ -13,29 +13,29 @@ testSuite: - testCaseName: Default Check testSteps: | = Screenshot("calculator_loaded.png");; - Assert(Calculator_1.Number1Label.Text = "10.12"; "Validate default value for number 1 label");; - Assert(Calculator_1.Number2Label.Text = "2.2"; "Validate default value for number 2 label");; + Assert(Calculator_1.Number1Label.Text = "4.42"; "Validate default value for number 1 label");; + Assert(Calculator_1.Number2Label.Text = "2.5"; "Validate default value for number 2 label");; - testCaseName: Check Addition testSteps: | = Select(Calculator_1.Add);; - Assert(Calculator_1.ResultLabel.Text = "12.32"; "Validate result label has the results of addition");; - Assert(Calculator_1.CalculatorResult = 12,32; "Validate component output calculator result has the results of addition");; + Assert(Calculator_1.ResultLabel.Text = "6.92"; "Validate result label has the results of addition");; + Assert(Calculator_1.CalculatorResult = 6,92; "Validate component output calculator result has the results of addition");; - testCaseName: Check subtraction testSteps: | = Select(Calculator_1.Subtract);; - Assert(Calculator_1.ResultLabel.Text = "7.92"; "Validate result label has the results of subtraction");; - Assert(Calculator_1.CalculatorResult = 7,92; "Validate component output calculator result has the results of subtraction");; + Assert(Calculator_1.ResultLabel.Text = "1.92"; "Validate result label has the results of subtraction");; + Assert(Calculator_1.CalculatorResult = 1,92; "Validate component output calculator result has the results of subtraction");; - testCaseName: Check multiplication testSteps: | = Select(Calculator_1.Multiply);; - Assert(Calculator_1.ResultLabel.Text = "22.264"; "Validate result label has the results of multiplication");; - Assert(Calculator_1.CalculatorResult = 22,264; "Validate component output calculator result has the results of multiplication");; + Assert(Calculator_1.ResultLabel.Text = "11.05"; "Validate result label has the results of multiplication");; + Assert(Calculator_1.CalculatorResult = 11,05; "Validate component output calculator result has the results of multiplication");; - testCaseName: Check division testSteps: | = Select(Calculator_1.Divide);; - Assert(Calculator_1.ResultLabel.Text = "4.6"; "Validate result label has the results of division");; - Assert(Calculator_1.CalculatorResult = 4,6; "Validate component output calculator result has the results of division");; + Assert(Calculator_1.ResultLabel.Text = "1.768"; "Validate result label has the results of division");; + Assert(Calculator_1.CalculatorResult = 1,768; "Validate component output calculator result has the results of division");; Screenshot("calculator_end.png");; testSettings: diff --git a/src/Microsoft.PowerApps.TestEngine.Tests/PowerApps/PowerAppFunctionsTest.cs b/src/Microsoft.PowerApps.TestEngine.Tests/PowerApps/PowerAppFunctionsTest.cs index ab158b693..de65cc241 100644 --- a/src/Microsoft.PowerApps.TestEngine.Tests/PowerApps/PowerAppFunctionsTest.cs +++ b/src/Microsoft.PowerApps.TestEngine.Tests/PowerApps/PowerAppFunctionsTest.cs @@ -158,7 +158,7 @@ public async Task SetPropertyDateAsyncTest() var result = await powerAppFunctions.SetPropertyAsync(itemPath, DateValue.NewDateOnly(dt.Date)); Assert.True(result); - MockTestInfraFunctions.Verify(x => x.RunJavascriptAsync($"PowerAppsTestEngine.setPropertyValue({itemPathString},{{\"SelectedDate\":Date.parse(\"{((DateValue)DateValue.NewDateOnly(dt.Date)).Value}\")}})"), Times.Once()); + MockTestInfraFunctions.Verify(x => x.RunJavascriptAsync($"PowerAppsTestEngine.setPropertyValue({itemPathString},{{\"SelectedDate\":Date.parse(\"{((DateValue)DateValue.NewDateOnly(dt.Date)).GetConvertedValue(null)}\")}})"), Times.Once()); } [Fact] @@ -542,8 +542,8 @@ public async Task LoadPowerAppsObjectModelAsyncWaitsForAppToLoadWithExceptions() { MockSingleTestInstanceState.Setup(x => x.GetLogger()).Returns(MockLogger.Object); MockTestInfraFunctions.Setup(x => x.AddScriptTagAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); - MockTestInfraFunctions.SetupSequence(x => x.RunJavascriptAsync("typeof PowerAppsTestEngine")) - .Returns(Task.FromResult("object")); + MockTestInfraFunctions.SetupSequence(x => x.RunJavascriptAsync("typeof PowerAppsTestEngine")) + .Returns(Task.FromResult("object")); MockTestInfraFunctions.Setup(x => x.RunJavascriptAsync("PowerAppsTestEngine.buildObjectModel().then((objectModel) => JSON.stringify(objectModel))")).Returns(Task.FromResult("{}")); var testSettings = new TestSettings() { Timeout = 12000 }; @@ -570,7 +570,7 @@ public async Task LoadPowerAppsObjectModelAsyncEmbedJSUndefined() var powerAppFunctions = new PowerAppFunctions(MockTestInfraFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object); await Assert.ThrowsAsync(async () => { await powerAppFunctions.LoadPowerAppsObjectModelAsync(); }); - + MockTestInfraFunctions.Verify(x => x.RunJavascriptAsync("PowerAppsTestEngine.buildObjectModel().then((objectModel) => JSON.stringify(objectModel))"), Times.AtLeastOnce()); LoggingTestHelper.VerifyLogging(MockLogger, "Start to load power apps object model", LogLevel.Debug, Times.Once()); } @@ -820,7 +820,7 @@ public async Task SetPropertyDateAsyncNoPublishedAppFunction() var powerAppFunctions = new PowerAppFunctions(MockTestInfraFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object); await Assert.ThrowsAsync(async () => { await powerAppFunctions.SetPropertyDateAsync(itemPath, DateValue.NewDateOnly(dt.Date)); }); - MockTestInfraFunctions.Verify(x => x.RunJavascriptAsync($"PowerAppsTestEngine.setPropertyValue({itemPathString},{{\"SelectedDate\":Date.parse(\"{((DateValue)DateValue.NewDateOnly(dt.Date)).Value}\")}})"), Times.Once()); + MockTestInfraFunctions.Verify(x => x.RunJavascriptAsync($"PowerAppsTestEngine.setPropertyValue({itemPathString},{{\"SelectedDate\":Date.parse(\"{((DateValue)DateValue.NewDateOnly(dt.Date)).GetConvertedValue(null)}\")}})"), Times.Once()); LoggingTestHelper.VerifyLogging(MockLogger, ExceptionHandlingHelper.PublishedAppWithoutJSSDKMessage, LogLevel.Error, Times.Once()); } diff --git a/src/Microsoft.PowerApps.TestEngine.Tests/PowerApps/PowerFXModel/ControlRecordValueTests.cs b/src/Microsoft.PowerApps.TestEngine.Tests/PowerApps/PowerFXModel/ControlRecordValueTests.cs index 95d13634d..edc6205fc 100644 --- a/src/Microsoft.PowerApps.TestEngine.Tests/PowerApps/PowerFXModel/ControlRecordValueTests.cs +++ b/src/Microsoft.PowerApps.TestEngine.Tests/PowerApps/PowerFXModel/ControlRecordValueTests.cs @@ -9,7 +9,6 @@ using Moq; using Newtonsoft.Json; using Xunit; -using YamlDotNet.Core.Tokens; namespace Microsoft.PowerApps.TestEngine.Tests.PowerApps.PowerFXModel { @@ -47,8 +46,8 @@ public void SimpleControlRecordValueTest() Assert.Equal(propertyValue, (controlRecordValue.GetField("Text") as StringValue).Value); Assert.Equal(numberPropertyValue, (controlRecordValue.GetField("X") as NumberValue).Value); - Assert.Equal(datePropertyValue.ToString(), (controlRecordValue.GetField("SelectedDate") as DateValue).Value.ToString()); - Assert.Equal(dateTimePropertyValue.ToString(), (controlRecordValue.GetField("DefaultDate") as DateTimeValue).Value.ToString()); + Assert.Equal(datePropertyValue.ToString(), (controlRecordValue.GetField("SelectedDate") as DateValue).GetConvertedValue(null).ToString()); + Assert.Equal(dateTimePropertyValue.ToString(), (controlRecordValue.GetField("DefaultDate") as DateTimeValue).GetConvertedValue(null).ToString()); mockPowerAppFunctions.Verify(x => x.GetPropertyValueFromControl(It.Is((x) => x.PropertyName == "Text" && x.ControlName == controlName)), Times.Once()); mockPowerAppFunctions.Verify(x => x.GetPropertyValueFromControl(It.Is((x) => x.PropertyName == "X" && x.ControlName == controlName)), Times.Once()); diff --git a/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/Functions/SetPropertyFunctionTests.cs b/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/Functions/SetPropertyFunctionTests.cs index 4da02719b..db66c2732 100644 --- a/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/Functions/SetPropertyFunctionTests.cs +++ b/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/Functions/SetPropertyFunctionTests.cs @@ -8,7 +8,6 @@ using Microsoft.PowerApps.TestEngine.Config; using Microsoft.PowerApps.TestEngine.PowerApps; using Microsoft.PowerApps.TestEngine.PowerApps.PowerFxModel; -using Microsoft.PowerApps.TestEngine.PowerFx; using Microsoft.PowerApps.TestEngine.PowerFx.Functions; using Microsoft.PowerApps.TestEngine.Tests.Helpers; using Microsoft.PowerFx.Types; @@ -146,7 +145,7 @@ public void SetPropertyDateFunctionTest() // check to see if the value of DatePicker1's 'Value' property is the correct datetime (01/01/2030) Assert.IsType(result); - MockPowerAppFunctions.Verify(x => x.SetPropertyAsync(It.Is((item) => item.ControlName == recordValue.Name), It.Is(dateVal => dateVal.Value == dt)), Times.Once()); + MockPowerAppFunctions.Verify(x => x.SetPropertyAsync(It.Is((item) => item.ControlName == recordValue.Name), It.Is(dateVal => dateVal.GetConvertedValue(null) == dt)), Times.Once()); } [Fact] @@ -157,7 +156,7 @@ public void SetPropertyRecordFunctionTest() // Make setPropertyFunction contain a component called Dropdown1 var recordType = RecordType.Empty().Add("Selected", RecordType.Empty()); - var recordValue = new ControlRecordValue(recordType, MockPowerAppFunctions.Object,"Dropdown1"); + var recordValue = new ControlRecordValue(recordType, MockPowerAppFunctions.Object, "Dropdown1"); var setPropertyFunction = new SetPropertyFunction(MockPowerAppFunctions.Object, MockLogger.Object); // Set the value of Dropdown1's 'Selected' property to {"Value":"2"} diff --git a/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/Functions/SomeOtherUntypedObject.cs b/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/Functions/SomeOtherUntypedObject.cs index 9a7244cae..03d86e649 100644 --- a/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/Functions/SomeOtherUntypedObject.cs +++ b/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/Functions/SomeOtherUntypedObject.cs @@ -26,6 +26,11 @@ public bool GetBoolean() throw new global::System.NotImplementedException(); } + public decimal GetDecimal() + { + throw new global::System.NotImplementedException(); + } + public double GetDouble() { throw new global::System.NotImplementedException(); @@ -36,6 +41,11 @@ public string GetString() throw new global::System.NotImplementedException(); } + public string GetUntypedNumber() + { + throw new global::System.NotImplementedException(); + } + public bool TryGetProperty(string value, out IUntypedObject result) { throw new global::System.NotImplementedException(); diff --git a/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/PowerFxEngineTests.cs b/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/PowerFxEngineTests.cs index ecf2906c1..a5e1ab905 100644 --- a/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/PowerFxEngineTests.cs +++ b/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/PowerFxEngineTests.cs @@ -13,7 +13,6 @@ using Microsoft.PowerApps.TestEngine.System; using Microsoft.PowerApps.TestEngine.TestInfra; using Microsoft.PowerApps.TestEngine.Tests.Helpers; -using Microsoft.PowerFx; using Microsoft.PowerFx.Types; using Moq; using Newtonsoft.Json; @@ -47,14 +46,14 @@ public PowerFxEngineTests() public void SetupDoesNotThrow() { var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); + powerFxEngine.Setup(); } [Fact] public void ExecuteThrowsOnNoSetupTest() { var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - Assert.Throws(() => powerFxEngine.Execute("")); + Assert.Throws(() => powerFxEngine.Execute("", It.IsAny())); LoggingTestHelper.VerifyLogging(MockLogger, "Engine is null, make sure to call Setup first", LogLevel.Error, Times.Once()); } @@ -79,7 +78,7 @@ public async void UpdatePowerFxModelAsyncThrowsOnCantGetAppStatusTest() MockTestState.Setup(x => x.GetTestSettings()).Returns(testSettings); var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); + powerFxEngine.Setup(); await Assert.ThrowsAsync(() => powerFxEngine.UpdatePowerFxModelAsync()); LoggingTestHelper.VerifyLogging(MockLogger, "Something went wrong when Test Engine tried to get App status.", LogLevel.Error, Times.Once()); } @@ -91,7 +90,7 @@ public async void RunRequirementsCheckAsyncTest() MockPowerAppFunctions.Setup(x => x.TestEngineReady()).Returns(Task.FromResult(true)); var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); + powerFxEngine.Setup(); await powerFxEngine.RunRequirementsCheckAsync(); @@ -106,9 +105,9 @@ public async void RunRequirementsCheckAsyncThrowsOnCheckAndHandleIfLegacyPlayerT MockPowerAppFunctions.Setup(x => x.TestEngineReady()).Returns(Task.FromResult(true)); var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); + powerFxEngine.Setup(); - await Assert.ThrowsAsync (() => powerFxEngine.RunRequirementsCheckAsync()); + await Assert.ThrowsAsync(() => powerFxEngine.RunRequirementsCheckAsync()); MockPowerAppFunctions.Verify(x => x.CheckAndHandleIfLegacyPlayerAsync(), Times.Once()); MockPowerAppFunctions.Verify(x => x.TestEngineReady(), Times.Never()); @@ -121,7 +120,7 @@ public async void RunRequirementsCheckAsyncThrowsOnTestEngineReadyTest() MockPowerAppFunctions.Setup(x => x.TestEngineReady()).Throws(new Exception()); var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); + powerFxEngine.Setup(); await Assert.ThrowsAsync(() => powerFxEngine.RunRequirementsCheckAsync()); @@ -133,12 +132,12 @@ public async void RunRequirementsCheckAsyncThrowsOnTestEngineReadyTest() public void ExecuteOneFunctionTest() { var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); - var result = powerFxEngine.Execute("1+1"); + powerFxEngine.Setup(); + var result = powerFxEngine.Execute("1+1", new CultureInfo("en-US")); Assert.Equal(2, ((NumberValue)result).Value); LoggingTestHelper.VerifyLogging(MockLogger, "Attempting:\n\n{\n1+1}", LogLevel.Trace, Times.Once()); - result = powerFxEngine.Execute("=1+1"); + result = powerFxEngine.Execute("=1+1", new CultureInfo("en-US")); Assert.Equal(2, ((NumberValue)result).Value); LoggingTestHelper.VerifyLogging(MockLogger, "Attempting:\n\n{\n1+1}", LogLevel.Trace, Times.Exactly(2)); } @@ -148,16 +147,40 @@ public void ExecuteMultipleFunctionsTest() { var powerFxExpression = "1+1; //some comment \n 2+2;\n Concatenate(\"hello\", \"world\");"; var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); - var result = powerFxEngine.Execute(powerFxExpression); + powerFxEngine.Setup(); + var result = powerFxEngine.Execute(powerFxExpression, It.IsAny()); Assert.Equal("helloworld", ((StringValue)result).Value); LoggingTestHelper.VerifyLogging(MockLogger, $"Attempting:\n\n{{\n{powerFxExpression}}}", LogLevel.Trace, Times.Once()); - result = powerFxEngine.Execute($"={powerFxExpression}"); + result = powerFxEngine.Execute($"={powerFxExpression}", It.IsAny()); Assert.Equal("helloworld", ((StringValue)result).Value); LoggingTestHelper.VerifyLogging(MockLogger, $"Attempting:\n\n{{\n{powerFxExpression}}}", LogLevel.Trace, Times.Exactly(2)); } + [Fact] + public void ExecuteMultipleFunctionsWithDifferentLocaleTest() + { + // en-US locale + var culture = new CultureInfo("en-US"); + var enUSpowerFxExpression = "1+1;2+2;"; + var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); + powerFxEngine.Setup(); + var enUSResult = powerFxEngine.Execute(enUSpowerFxExpression, culture); + + // fr locale + culture = new CultureInfo("fr"); + var frpowerFxExpression = "1+1;;2+2;;"; + powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); + powerFxEngine.Setup(); + var frResult = powerFxEngine.Execute(frpowerFxExpression, culture); + + // Assertions + Assert.Equal(4, ((NumberValue)enUSResult).Value); + LoggingTestHelper.VerifyLogging(MockLogger, $"Attempting:\n\n{{\n{enUSpowerFxExpression}}}", LogLevel.Trace, Times.Once()); + Assert.Equal(4, ((NumberValue)frResult).Value); + LoggingTestHelper.VerifyLogging(MockLogger, $"Attempting:\n\n{{\n{frpowerFxExpression}}}", LogLevel.Trace, Times.Once()); + } + [Fact] public async Task ExecuteWithVariablesTest() { @@ -198,18 +221,18 @@ public async Task ExecuteWithVariablesTest() MockTestState.Setup(x => x.GetTestSettings()).Returns(testSettings); var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); + powerFxEngine.Setup(); await powerFxEngine.UpdatePowerFxModelAsync(); MockPowerAppFunctions.Verify(x => x.LoadPowerAppsObjectModelAsync(), Times.Once()); - var result = powerFxEngine.Execute(powerFxExpression); + var result = powerFxEngine.Execute(powerFxExpression, It.IsAny()); Assert.Equal($"{label1Text}{label2Text}", ((StringValue)result).Value); LoggingTestHelper.VerifyLogging(MockLogger, $"Attempting:\n\n{{\n{powerFxExpression}}}", LogLevel.Trace, Times.Once()); MockPowerAppFunctions.Verify(x => x.GetPropertyValueFromControl(It.Is((itemPath) => itemPath.ControlName == label1ItemPath.ControlName && itemPath.PropertyName == label1ItemPath.PropertyName)), Times.Once()); MockPowerAppFunctions.Verify(x => x.GetPropertyValueFromControl(It.Is((itemPath) => itemPath.ControlName == label2ItemPath.ControlName && itemPath.PropertyName == label2ItemPath.PropertyName)), Times.Once()); - result = powerFxEngine.Execute($"={powerFxExpression}"); + result = powerFxEngine.Execute($"={powerFxExpression}", It.IsAny()); Assert.Equal($"{label1Text}{label2Text}", ((StringValue)result).Value); LoggingTestHelper.VerifyLogging(MockLogger, $"Attempting:\n\n{{\n{powerFxExpression}}}", LogLevel.Trace, Times.Exactly(2)); MockPowerAppFunctions.Verify(x => x.GetPropertyValueFromControl(It.Is((itemPath) => itemPath.ControlName == label1ItemPath.ControlName && itemPath.PropertyName == label1ItemPath.PropertyName)), Times.Exactly(2)); @@ -222,8 +245,8 @@ public void ExecuteFailsWhenPowerFXThrowsTest() var powerFxExpression = "someNonExistentPowerFxFunction(1, 2, 3)"; MockPowerAppFunctions.Setup(x => x.LoadPowerAppsObjectModelAsync()).Returns(Task.FromResult(new Dictionary())); var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(CultureInfo.CurrentCulture); - Assert.ThrowsAsync(async () => await powerFxEngine.ExecuteWithRetryAsync(powerFxExpression)); + powerFxEngine.Setup(); + Assert.ThrowsAsync(async () => await powerFxEngine.ExecuteWithRetryAsync(powerFxExpression, It.IsAny())); } [Fact] @@ -231,8 +254,8 @@ public void ExecuteFailsWhenUsingNonExistentVariableTest() { var powerFxExpression = "Concatenate(Label1.Text, Label2.Text)"; var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); - Assert.ThrowsAsync(async () => await powerFxEngine.ExecuteWithRetryAsync(powerFxExpression)); + powerFxEngine.Setup(); + Assert.ThrowsAsync(async () => await powerFxEngine.ExecuteWithRetryAsync(powerFxExpression, It.IsAny())); } [Fact] @@ -240,12 +263,12 @@ public void ExecuteAssertFunctionTest() { var powerFxExpression = "Assert(1+1=2, \"Adding 1 + 1\")"; var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); - var result = powerFxEngine.Execute(powerFxExpression); + powerFxEngine.Setup(); + var result = powerFxEngine.Execute(powerFxExpression, It.IsAny()); Assert.IsType(result); var failingPowerFxExpression = "Assert(1+1=3, \"Supposed to fail\")"; - Assert.ThrowsAny(() => powerFxEngine.Execute(failingPowerFxExpression)); + Assert.ThrowsAny(() => powerFxEngine.Execute(failingPowerFxExpression, It.IsAny())); } [Fact] @@ -256,12 +279,12 @@ public async Task ExecuteScreenshotFunctionTest() MockTestInfraFunctions.Setup(x => x.ScreenshotAsync(It.IsAny())).Returns(Task.CompletedTask); var powerFxExpression = "Screenshot(\"1.jpg\")"; var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); - var result = powerFxEngine.Execute(powerFxExpression); + powerFxEngine.Setup(); + var result = powerFxEngine.Execute(powerFxExpression, It.IsAny()); Assert.IsType(result); var failingPowerFxExpression = "Screenshot(\"1.txt\")"; - Assert.ThrowsAny(() => powerFxEngine.Execute(failingPowerFxExpression)); + Assert.ThrowsAny(() => powerFxEngine.Execute(failingPowerFxExpression, It.IsAny())); } [Fact] @@ -279,10 +302,10 @@ public async Task ExecuteSelectFunctionTest() var powerFxExpression = "Select(Button1)"; var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); + powerFxEngine.Setup(); await powerFxEngine.UpdatePowerFxModelAsync(); - await powerFxEngine.ExecuteWithRetryAsync(powerFxExpression); - var result = powerFxEngine.Execute(powerFxExpression); + await powerFxEngine.ExecuteWithRetryAsync(powerFxExpression, It.IsAny()); + var result = powerFxEngine.Execute(powerFxExpression, It.IsAny()); Assert.IsType(result); MockPowerAppFunctions.Verify(x => x.LoadPowerAppsObjectModelAsync(), Times.Exactly(3)); } @@ -303,9 +326,9 @@ public async Task ExecuteSelectFunctionFailingTest() var powerFxExpression = "Select(Button1)"; var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); + powerFxEngine.Setup(); await powerFxEngine.UpdatePowerFxModelAsync(); - Assert.ThrowsAny(() => powerFxEngine.Execute(powerFxExpression)); + Assert.ThrowsAny(() => powerFxEngine.Execute(powerFxExpression, It.IsAny())); MockPowerAppFunctions.Verify(x => x.LoadPowerAppsObjectModelAsync(), Times.Once()); } @@ -324,9 +347,9 @@ public async Task ExecuteSelectFunctionThrowsOnDifferentRecordTypeTest() var powerFxExpression = "Select(Button1)"; var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); + powerFxEngine.Setup(); await powerFxEngine.UpdatePowerFxModelAsync(); - Assert.ThrowsAny(() => powerFxEngine.Execute(powerFxExpression)); + Assert.ThrowsAny(() => powerFxEngine.Execute(powerFxExpression, It.IsAny())); MockPowerAppFunctions.Verify(x => x.LoadPowerAppsObjectModelAsync(), Times.Once()); } @@ -347,10 +370,10 @@ public async Task ExecuteSetPropertyFunctionTest() var powerFxExpression = "SetProperty(Button1.Text, \"10\")"; var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); + powerFxEngine.Setup(); await powerFxEngine.UpdatePowerFxModelAsync(); - await powerFxEngine.ExecuteWithRetryAsync(powerFxExpression); - var result = powerFxEngine.Execute(powerFxExpression); + await powerFxEngine.ExecuteWithRetryAsync(powerFxExpression, It.IsAny()); + var result = powerFxEngine.Execute(powerFxExpression, It.IsAny()); Assert.IsType(result); MockPowerAppFunctions.Verify(x => x.LoadPowerAppsObjectModelAsync(), Times.Once()); } @@ -371,9 +394,9 @@ public async Task ExecuteSetPropertyFunctionThrowsOnDifferentRecordTypeTest() var powerFxExpression = "SetProperty(Button1.Text, \"10\")"; var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); + powerFxEngine.Setup(); await powerFxEngine.UpdatePowerFxModelAsync(); - Assert.ThrowsAny(() => powerFxEngine.Execute(powerFxExpression)); + Assert.ThrowsAny(() => powerFxEngine.Execute(powerFxExpression, It.IsAny())); MockPowerAppFunctions.Verify(x => x.LoadPowerAppsObjectModelAsync(), Times.Once()); } @@ -404,10 +427,10 @@ public async Task ExecuteWaitFunctionTest() var powerFxExpression = "Wait(Label1, \"Text\", \"1\")"; var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); + powerFxEngine.Setup(); await powerFxEngine.UpdatePowerFxModelAsync(); - await powerFxEngine.ExecuteWithRetryAsync(powerFxExpression); - var result = powerFxEngine.Execute(powerFxExpression); + await powerFxEngine.ExecuteWithRetryAsync(powerFxExpression, It.IsAny()); + var result = powerFxEngine.Execute(powerFxExpression, It.IsAny()); Assert.IsType(result); MockPowerAppFunctions.Verify(x => x.LoadPowerAppsObjectModelAsync(), Times.Once()); } @@ -427,9 +450,9 @@ public async Task ExecuteWaitFunctionThrowsOnDifferentRecordTypeTest() var powerFxExpression = "Wait(Label1, \"Text\", \"1\")"; var powerFxEngine = new PowerFxEngine(MockTestInfraFunctions.Object, MockPowerAppFunctions.Object, MockSingleTestInstanceState.Object, MockTestState.Object, MockFileSystem.Object); - powerFxEngine.Setup(It.IsAny()); + powerFxEngine.Setup(); await powerFxEngine.UpdatePowerFxModelAsync(); - Assert.ThrowsAny(() => powerFxEngine.Execute(powerFxExpression)); + Assert.ThrowsAny(() => powerFxEngine.Execute(powerFxExpression, It.IsAny())); MockPowerAppFunctions.Verify(x => x.LoadPowerAppsObjectModelAsync(), Times.Once()); } @@ -470,7 +493,7 @@ public async Task TestStepByStep() var oldUICulture = CultureInfo.CurrentUICulture; var frenchCulture = new CultureInfo("fr"); CultureInfo.CurrentUICulture = frenchCulture; - powerFxEngine.Setup(frenchCulture); + powerFxEngine.Setup(); var testSettings = new TestSettings() { Timeout = 3000 }; MockTestState.Setup(x => x.GetTestSettings()).Returns(testSettings); var expression = "Select(Label1/*Label;;22*/);;\"Just stirng \n;literal\";;Select(Label2)\n;;Select(Label3);;Assert(1=1; \"Supposed to pass;;\");;Max(1,2)"; @@ -479,9 +502,9 @@ public async Task TestStepByStep() // Engine.Eval should throw an exception when none of the used first names exist in the underlying symbol table yet. // This confirms that we would be hitting goStepByStep branch - Assert.ThrowsAny(() => powerFxEngine.Execute(expression)); + Assert.ThrowsAny(() => powerFxEngine.Execute(expression, frenchCulture)); await powerFxEngine.UpdatePowerFxModelAsync(); - var result = powerFxEngine.Execute(expression); + var result = powerFxEngine.Execute(expression, frenchCulture); try { @@ -495,7 +518,7 @@ public async Task TestStepByStep() // Assert Assert.Equal(2, updateCounter); Assert.Equal(FormulaType.Number, result.Type); - Assert.Equal(1.2, (result as NumberValue).Value); + Assert.Equal("1.2", (result as NumberValue).Value.ToString()); LoggingTestHelper.VerifyLogging(MockLogger, $"Syntax check failed. Now attempting to execute lines step by step", LogLevel.Debug, Times.Exactly(2)); } diff --git a/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/PowerFxHelperTests.cs b/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/PowerFxHelperTests.cs index 2c3db540c..5a68e4298 100644 --- a/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/PowerFxHelperTests.cs +++ b/src/Microsoft.PowerApps.TestEngine.Tests/PowerFx/PowerFxHelperTests.cs @@ -4,7 +4,6 @@ using System.Globalization; using Microsoft.PowerApps.TestEngine.PowerFx; using Microsoft.PowerFx; -using Microsoft.PowerFx.Preview; using Xunit; namespace Microsoft.PowerApps.TestEngine.Tests.PowerFx @@ -40,26 +39,25 @@ public PowerFxHelperTests() [InlineData("en-US", "Max(;;;1,2)", "Max(;;;1,2)")] [InlineData("en-US", ";;;;", "", "", "", "")] [InlineData("en-US", "")] - [InlineData("en-US", "Max(;22)", "Max(", "22)")] + [InlineData("en-US", "Max(;22)", "Max(;22)")] [InlineData("en-US", "Select(Button1/*Selecting button 1;;;*/);Assert(Button1.Text = Text(\"Ab\";\"Abcd\"))", "Select(Button1/*Selecting button 1;;;*/)", "Assert(Button1.Text = Text(\"Ab\";\"Abcd\"))")] [InlineData("en-US", "Select(Button1)", "Select(Button1)")] [InlineData("fr", "Max(1;;2;;3;30;;Max(230;;23;;33);;3)", "Max(1;;2;;3;30;;Max(230;;23;;33);;3)")] - // Once new powerfx version is consumed, this would fail due to recent PowerFx Lexer changes - // Fix would be to this to InlineData("en-US", "'Test;2212", "'Test", "2212") - [InlineData("en-US", "'Test;2212", "'Test;2212")] + [InlineData("en-US", "'Test;2212", "'Test", "2212")] [InlineData("en-US", "'Test;;2333';Max(1;'Button1;Button2';23;/*\n\n;;Comment\n\n*/33,100;Max(100;200,20;30)))", "'Test;;2333'", "Max(1;'Button1;Button2';23;/*\n\n;;Comment\n\n*/33,100;Max(100;200,20;30)))")] [InlineData("en-US", "'Test;;2333';Max(1;'Button1;Button2';23;/*\n\n;;Comment\n\n33,100;Max(100;200,20;30)))", "'Test;;2333'", "Max(1;'Button1;Button2';23;/*\n\n;;Comment\n\n33,100;Max(100;200,20;30)))")] public void TestFormulasSeparatedByChainingOpAreExtractedCorrectly(string locale, string expression, params string[] expectedFormulas) { // Arrange - FeatureFlags.StringInterpolation = true; + // Setting this feature flag is no longer needed + //FeatureFlags.StringInterpolation = true; var oldUICulture = CultureInfo.CurrentUICulture; var culture = new CultureInfo(locale); CultureInfo.CurrentUICulture = culture; var (result, engine) = GetCheckResultAndEngine(expression, locale); // Act - var actualFormulas = PowerFxHelper.ExtractFormulasSeparatedByChainingOperator(engine, result); + var actualFormulas = PowerFxHelper.ExtractFormulasSeparatedByChainingOperator(engine, result, culture); // Assert try @@ -75,7 +73,9 @@ public void TestFormulasSeparatedByChainingOpAreExtractedCorrectly(string locale private static Engine GetEngine(string locale) { - var recalcEngine = new RecalcEngine(new PowerFxConfig(new CultureInfo(locale))); + //TODO: Temporarily removed the locale input parameter from PowerFxConfig(...), make sure to add it back + //var recalcEngine = new RecalcEngine(new PowerFxConfig(new CultureInfo(locale))); + var recalcEngine = new RecalcEngine(new PowerFxConfig()); return recalcEngine; } diff --git a/src/Microsoft.PowerApps.TestEngine.Tests/SingleTestRunnerTests.cs b/src/Microsoft.PowerApps.TestEngine.Tests/SingleTestRunnerTests.cs index 55fc70538..5a9ced969 100644 --- a/src/Microsoft.PowerApps.TestEngine.Tests/SingleTestRunnerTests.cs +++ b/src/Microsoft.PowerApps.TestEngine.Tests/SingleTestRunnerTests.cs @@ -88,10 +88,10 @@ private void SetupMocks(string testRunId, string testSuiteId, string testId, str var locale = string.IsNullOrEmpty(testSuitelocale) ? CultureInfo.CurrentCulture : new CultureInfo(testSuitelocale); - MockPowerFxEngine.Setup(x => x.Setup(locale)); + MockPowerFxEngine.Setup(x => x.Setup()); MockPowerFxEngine.Setup(x => x.RunRequirementsCheckAsync()).Returns(Task.CompletedTask); MockPowerFxEngine.Setup(x => x.UpdatePowerFxModelAsync()).Returns(Task.CompletedTask); - MockPowerFxEngine.Setup(x => x.Execute(It.IsAny())).Returns(FormulaValue.NewBlank()); + MockPowerFxEngine.Setup(x => x.Execute(It.IsAny(), It.IsAny())).Returns(FormulaValue.NewBlank()); MockTestEngineEventHandler.Setup(x => x.SetAndInitializeCounters(It.IsAny())); MockTestEngineEventHandler.Setup(x => x.EncounteredException(It.IsAny())); @@ -102,11 +102,11 @@ private void SetupMocks(string testRunId, string testSuiteId, string testId, str if (powerFxTestSuccess) { - MockPowerFxEngine.Setup(x => x.ExecuteWithRetryAsync(It.IsAny())).Returns(Task.CompletedTask); + MockPowerFxEngine.Setup(x => x.ExecuteWithRetryAsync(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask); } else { - MockPowerFxEngine.Setup(x => x.ExecuteWithRetryAsync(It.IsAny())).Throws(new Exception("something bad happened")); + MockPowerFxEngine.Setup(x => x.ExecuteWithRetryAsync(It.IsAny(), It.IsAny())).Throws(new Exception("something bad happened")); } MockPowerFxEngine.Setup(x => x.GetPowerAppFunctions()).Returns(MockPowerAppFunctions.Object); @@ -139,7 +139,7 @@ private void VerifyTestStateSetup(string testSuiteId, string testRunId, TestSuit private void VerifySuccessfulTestExecution(string testResultDirectory, TestSuiteDefinition testSuiteDefinition, BrowserConfiguration browserConfig, string testSuiteId, string testRunId, string testId, bool testSuccess, string[]? additionalFiles, string? errorMessage, string? stackTrace, string appUrl, CultureInfo locale) { - MockPowerFxEngine.Verify(x => x.Setup(locale), Times.Once()); + MockPowerFxEngine.Verify(x => x.Setup(), Times.Once()); MockPowerFxEngine.Verify(x => x.UpdatePowerFxModelAsync(), Times.Once()); MockTestInfraFunctions.Verify(x => x.SetupAsync(), Times.Once()); MockUserManager.Verify(x => x.LoginAsUserAsync(appUrl), Times.Once()); @@ -329,7 +329,7 @@ public async Task PowerFxSetupThrowsTest() { await SingleTestRunnerHandlesExceptionsThrownCorrectlyHelper((Exception exceptionToThrow) => { - MockPowerFxEngine.Setup(x => x.Setup(It.IsAny())).Throws(exceptionToThrow); + MockPowerFxEngine.Setup(x => x.Setup()).Throws(exceptionToThrow); }); } @@ -405,7 +405,7 @@ public async Task PowerFxExecuteThrowsTest() var exceptionToThrow = new InvalidOperationException("Test exception"); - MockPowerFxEngine.Setup(x => x.Execute(It.IsAny())).Throws(exceptionToThrow); + MockPowerFxEngine.Setup(x => x.Execute(It.IsAny(), It.IsAny())).Throws(exceptionToThrow); var locale = string.IsNullOrEmpty(testData.testSuiteLocale) ? CultureInfo.CurrentCulture : new CultureInfo(testData.testSuiteLocale); diff --git a/src/Microsoft.PowerApps.TestEngine/Microsoft.PowerApps.TestEngine.csproj b/src/Microsoft.PowerApps.TestEngine/Microsoft.PowerApps.TestEngine.csproj index 3dbeafb41..63b79fd62 100644 --- a/src/Microsoft.PowerApps.TestEngine/Microsoft.PowerApps.TestEngine.csproj +++ b/src/Microsoft.PowerApps.TestEngine/Microsoft.PowerApps.TestEngine.csproj @@ -30,7 +30,7 @@ - + diff --git a/src/Microsoft.PowerApps.TestEngine/PowerApps/PowerAppFunctions.cs b/src/Microsoft.PowerApps.TestEngine/PowerApps/PowerAppFunctions.cs index 219f2c282..6a69faf7c 100644 --- a/src/Microsoft.PowerApps.TestEngine/PowerApps/PowerAppFunctions.cs +++ b/src/Microsoft.PowerApps.TestEngine/PowerApps/PowerAppFunctions.cs @@ -273,7 +273,7 @@ public async Task SetPropertyDateAsync(ItemPath itemPath, DateValue value) var itemPathString = JsonConvert.SerializeObject(itemPath); var propertyNameString = JsonConvert.SerializeObject(itemPath.PropertyName); - var recordValue = value.Value; + var recordValue = value.GetConvertedValue(null); // Date.parse() parses the date to unix timestamp var expression = $"PowerAppsTestEngine.setPropertyValue({itemPathString},{{{propertyNameString}:Date.parse(\"{recordValue}\")}})"; diff --git a/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/WaitFunction.cs b/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/WaitFunction.cs index f6f5ee2d2..bfa3d9a3e 100644 --- a/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/WaitFunction.cs +++ b/src/Microsoft.PowerApps.TestEngine/PowerFx/Functions/WaitFunction.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.Logging; using Microsoft.PowerApps.TestEngine.Helpers; -using Microsoft.PowerApps.TestEngine.PowerApps; using Microsoft.PowerApps.TestEngine.PowerApps.PowerFxModel; using Microsoft.PowerFx; using Microsoft.PowerFx.Types; @@ -206,17 +205,17 @@ private void Wait(RecordValue obj, StringValue propName, DateTimeValue value) // Handling in the case that the property is a DateTime if (propType.GetType() == typeof(DateTimeValue)) { - PollingCondition((x) => x != value.Value, () => + PollingCondition((x) => x != value.GetConvertedValue(null), () => { - return ((DateTimeValue)controlModel.GetField(propName.Value)).Value; + return ((DateTimeValue)controlModel.GetField(propName.Value)).GetConvertedValue(null); }, _timeout); } // Otherwise, the property should be be a Date else { - PollingCondition((x) => x != value.Value, () => + PollingCondition((x) => x != value.GetConvertedValue(null), () => { - return ((DateValue)controlModel.GetField(propName.Value)).Value; + return ((DateValue)controlModel.GetField(propName.Value)).GetConvertedValue(null); }, _timeout); } @@ -249,9 +248,9 @@ private void Wait(RecordValue obj, StringValue propName, DateValue value) // Handling in the case that the property is a DateTime if (propType.GetType() == typeof(DateTimeValue)) { - PollingCondition((x) => x != value.Value, () => + PollingCondition((x) => x != value.GetConvertedValue(null), () => { - return ((DateTimeValue)controlModel.GetField(propName.Value)).Value; + return ((DateTimeValue)controlModel.GetField(propName.Value)).GetConvertedValue(null); }, _timeout); _logger.LogInformation("Successfully finished executing Wait function, condition was met."); @@ -260,9 +259,9 @@ private void Wait(RecordValue obj, StringValue propName, DateValue value) // Handling in the case that the property is a Date else if (propType.GetType() == typeof(DateValue)) { - PollingCondition((x) => x != value.Value, () => + PollingCondition((x) => x != value.GetConvertedValue(null), () => { - return ((DateValue)controlModel.GetField(propName.Value)).Value; + return ((DateValue)controlModel.GetField(propName.Value)).GetConvertedValue(null); }, _timeout); _logger.LogInformation("Successfully finished executing Wait function, condition was met."); diff --git a/src/Microsoft.PowerApps.TestEngine/PowerFx/IPowerFxEngine.cs b/src/Microsoft.PowerApps.TestEngine/PowerFx/IPowerFxEngine.cs index 3ef83aa4a..14fd81773 100644 --- a/src/Microsoft.PowerApps.TestEngine/PowerFx/IPowerFxEngine.cs +++ b/src/Microsoft.PowerApps.TestEngine/PowerFx/IPowerFxEngine.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using System.Globalization; +using System.Text.RegularExpressions; using Microsoft.PowerApps.TestEngine.PowerApps; using Microsoft.PowerFx.Types; @@ -14,23 +15,24 @@ public interface IPowerFxEngine { /// /// Set up the Power FX engine - /// - /// The locale to be used when setting up the Power FX engine. This is typically provided by the test plan file - public void Setup(CultureInfo locale); + /// + public void Setup(); /// /// Executes testSteps with retry /// /// Test steps + /// The locale to be used when excecuting tests. This is typically provided by the test plan file /// A task - public Task ExecuteWithRetryAsync(string testSteps); + public Task ExecuteWithRetryAsync(string testSteps, CultureInfo culture); /// /// Executes a list of Power FX functions /// /// Test steps + /// The locale to be when excecuting tests. This is typically provided by the test plan file /// Result of the Power FX. - public FormulaValue Execute(string testSteps); + public FormulaValue Execute(string testSteps, CultureInfo culture); /// /// Update the Power FX object model diff --git a/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs b/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs index f743f7dc6..ee55b6751 100644 --- a/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs +++ b/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxEngine.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -using System.Dynamic; using System.Globalization; using Microsoft.Extensions.Logging; using Microsoft.PowerApps.TestEngine.Config; @@ -11,7 +10,6 @@ using Microsoft.PowerApps.TestEngine.System; using Microsoft.PowerApps.TestEngine.TestInfra; using Microsoft.PowerFx; -using Microsoft.PowerFx.Syntax; using Microsoft.PowerFx.Types; namespace Microsoft.PowerApps.TestEngine.PowerFx @@ -44,9 +42,9 @@ public PowerFxEngine(ITestInfraFunctions testInfraFunctions, _fileSystem = fileSystem; } - public void Setup(CultureInfo locale) + public void Setup() { - var powerFxConfig = new PowerFxConfig(locale); + var powerFxConfig = new PowerFxConfig(); powerFxConfig.AddFunction(new SelectOneParamFunction(_powerAppFunctions, async () => await UpdatePowerFxModelAsync(), Logger)); powerFxConfig.AddFunction(new SelectTwoParamsFunction(_powerAppFunctions, async () => await UpdatePowerFxModelAsync(), Logger)); @@ -60,7 +58,7 @@ public void Setup(CultureInfo locale) Engine = new RecalcEngine(powerFxConfig); } - public async Task ExecuteWithRetryAsync(string testSteps) + public async Task ExecuteWithRetryAsync(string testSteps, CultureInfo culture) { int currentRetry = 0; FormulaValue result = FormulaValue.NewBlank(); @@ -69,7 +67,7 @@ public async Task ExecuteWithRetryAsync(string testSteps) { try { - result = Execute(testSteps); + result = Execute(testSteps, culture); break; } catch (Exception e) when (e.Message.Contains("locale")) @@ -89,7 +87,7 @@ public async Task ExecuteWithRetryAsync(string testSteps) } } - public FormulaValue Execute(string testSteps) + public FormulaValue Execute(string testSteps, CultureInfo culture) { if (Engine == null) { @@ -105,7 +103,7 @@ public FormulaValue Execute(string testSteps) var goStepByStep = false; // Check if the syntax is correct - var checkResult = Engine.Check(testSteps, null, new ParserOptions() { AllowsSideEffects = true }); + var checkResult = Engine.Check(testSteps, null, GetPowerFxParserOptions(culture)); if (!checkResult.IsSuccess) { // If it isn't, we have to go step by step as the object model isn't fully loaded @@ -115,20 +113,20 @@ public FormulaValue Execute(string testSteps) if (goStepByStep) { - var splitSteps = PowerFxHelper.ExtractFormulasSeparatedByChainingOperator(Engine, checkResult); + var splitSteps = PowerFxHelper.ExtractFormulasSeparatedByChainingOperator(Engine, checkResult, culture); FormulaValue result = FormulaValue.NewBlank(); foreach (var step in splitSteps) { Logger.LogTrace($"Attempting:{step.Replace("\n", "").Replace("\r", "")}"); - result = Engine.Eval(step, null, new ParserOptions() { AllowsSideEffects = true }); + result = Engine.Eval(step, null, new ParserOptions() { AllowsSideEffects = true, Culture = culture, NumberIsFloat = true }); } return result; } else { Logger.LogTrace($"Attempting:\n\n{{\n{testSteps}}}"); - return Engine.Eval(testSteps, null, new ParserOptions() { AllowsSideEffects = true }); + return Engine.Eval(testSteps, null, new ParserOptions() { AllowsSideEffects = true, Culture = culture, NumberIsFloat = true }); } } @@ -149,6 +147,13 @@ public async Task UpdatePowerFxModelAsync() } } + private static ParserOptions GetPowerFxParserOptions(CultureInfo culture) + { + // Currently support for decimal is in progress for PowerApps + // Power Fx by default treats number as decimal. Hence setting NumberIsFloat config to true in our case + return new ParserOptions() { AllowsSideEffects = true, Culture = culture, NumberIsFloat = true }; + } + public IPowerAppFunctions GetPowerAppFunctions() { return _powerAppFunctions; diff --git a/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxHelper.cs b/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxHelper.cs index 121f67d78..5b3d5c575 100644 --- a/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxHelper.cs +++ b/src/Microsoft.PowerApps.TestEngine/PowerFx/PowerFxHelper.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. +using System.Globalization; using Microsoft.PowerFx; using Microsoft.PowerFx.Syntax; @@ -17,15 +18,16 @@ public static class PowerFxHelper /// /// Instance of an engine configured with the desired locale /// Check result instance created after processing the expression + /// The locale to be used when excecuting tests /// An enumerable of formulas extracted from the expression that are separated by chaining operator - public static IEnumerable ExtractFormulasSeparatedByChainingOperator(Engine engine, CheckResult result) + public static IEnumerable ExtractFormulasSeparatedByChainingOperator(Engine engine, CheckResult result, CultureInfo culture) { if (string.IsNullOrEmpty(result?.Parse?.Text)) { return new string[0]; } - var spansForFormulasSeparatedAcrossMultipleDepths = ExtractSpansOfFormulasSeparatedByChainingOperator(engine, result?.Parse.Text); + var spansForFormulasSeparatedAcrossMultipleDepths = ExtractSpansOfFormulasSeparatedByChainingOperator(engine, result?.Parse.Text, culture); if (result?.Parse?.Root == null) { return spansForFormulasSeparatedAcrossMultipleDepths.Select(span => result.Parse.Text.Substring(span.Start, span.End - span.Start)); @@ -40,10 +42,11 @@ public static IEnumerable ExtractFormulasSeparatedByChainingOperator(Eng /// /// Instance of an engine configured with the desired locale /// Expression from which formulas separated by chaining operator would be extracted + /// The locale to be used when excecuting tests /// Spans that represent formulas separated by chaining operator at multiple levels and depths - private static IEnumerable ExtractSpansOfFormulasSeparatedByChainingOperator(Engine engine, string expression) + private static IEnumerable ExtractSpansOfFormulasSeparatedByChainingOperator(Engine engine, string expression, CultureInfo culture) { - var chainOperatorTokens = engine.Tokenize(expression).Where(tok => tok.Kind == TokKind.Semicolon).OrderBy(tok => tok.Span.Min); + var chainOperatorTokens = engine.Tokenize(expression, culture).Where(tok => tok.Kind == TokKind.Semicolon).OrderBy(tok => tok.Span.Min); var formulas = new List(); var lowerBound = 0; diff --git a/src/Microsoft.PowerApps.TestEngine/SingleTestRunner.cs b/src/Microsoft.PowerApps.TestEngine/SingleTestRunner.cs index 36fee3e08..87e06067a 100644 --- a/src/Microsoft.PowerApps.TestEngine/SingleTestRunner.cs +++ b/src/Microsoft.PowerApps.TestEngine/SingleTestRunner.cs @@ -127,7 +127,7 @@ public async Task RunTestAsync(string testRunId, string testRunDirectory, TestSu await _testInfraFunctions.SetupNetworkRequestMockAsync(); // Set up Power Fx - _powerFxEngine.Setup(locale); + _powerFxEngine.Setup(); await _powerFxEngine.RunRequirementsCheckAsync(); await _powerFxEngine.UpdatePowerFxModelAsync(); @@ -160,15 +160,15 @@ public async Task RunTestAsync(string testRunId, string testRunDirectory, TestSu if (!string.IsNullOrEmpty(testSuiteDefinition.OnTestCaseStart)) { Logger.LogInformation($"Running OnTestCaseStart for test case: {testCase.TestCaseName}"); - await _powerFxEngine.ExecuteWithRetryAsync(testSuiteDefinition.OnTestCaseStart); + await _powerFxEngine.ExecuteWithRetryAsync(testSuiteDefinition.OnTestCaseStart, locale); } - await _powerFxEngine.ExecuteWithRetryAsync(testCase.TestSteps); + await _powerFxEngine.ExecuteWithRetryAsync(testCase.TestSteps, locale); if (!string.IsNullOrEmpty(testSuiteDefinition.OnTestCaseComplete)) { Logger.LogInformation($"Running OnTestCaseComplete for test case: {testCase.TestCaseName}"); - await _powerFxEngine.ExecuteWithRetryAsync(testSuiteDefinition.OnTestCaseComplete); + await _powerFxEngine.ExecuteWithRetryAsync(testSuiteDefinition.OnTestCaseComplete, locale); } _eventHandler.TestCaseEnd(true); @@ -220,7 +220,7 @@ public async Task RunTestAsync(string testRunId, string testRunDirectory, TestSu { Logger.LogInformation($"Running OnTestSuiteComplete for test suite: {testSuiteName}"); _testState.SetTestResultsDirectory(testResultDirectory); - _powerFxEngine.Execute(testSuiteDefinition.OnTestSuiteComplete); + _powerFxEngine.Execute(testSuiteDefinition.OnTestSuiteComplete, locale); } } catch (Exception ex)