From f67b9f31ded0b4dec604ae0ce89317309b16da06 Mon Sep 17 00:00:00 2001 From: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Date: Thu, 18 Mar 2021 11:29:38 -0700 Subject: [PATCH] Enable Nullable for Xamarin.CommunityToolkit.Markup.UnitTests (#1086) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Markup Unit Tests * Add Nullable to Xamarin.CommunityToolkit.Markdown.UnitTests * Update BindingHelpers.cs * Fix Failing Unit Tests * Don't capture XUnit's Synchronization Context * Re-order Unit Tests * Revert "Don't capture XUnit's Synchronization Context" This reverts commit b8b3f16ec466b848e83387c26b3e08a86028613b. Co-authored-by: Javier Suárez --- azure-pipelines.yml | 33 +++- samples/XCT.Sample.sln | 27 ++++ .../BaseTestFixture.cs | 12 +- .../BindableLayoutExtensionsTests.cs | 14 +- .../BindableObjectExtensionsTests.cs | 134 ++++++++-------- .../BindableObjectMultiBindExtensionsTests.cs | 151 ++++++++++-------- .../BindingHelpers.cs | 96 +++++------ .../DefaultBindablePropertiesTests.cs | 4 +- .../ElementExtensionsTests.cs | 39 ++--- .../ElementGesturesExtensionsTests.cs | 36 ++--- .../FuncConverter.cs | 51 +++--- .../LabelExtensionsLeftToRightTests.cs | 4 +- .../LabelExtensionsRightToLeftTests.cs | 4 +- .../LabelExtensionsTests.cs | 25 +-- .../MarkupBaseTestFixture.cs | 32 ++-- .../MockPlatformServices.cs | 2 +- .../PaddingElementExtensionsTests.cs | 8 +- .../RelativeLayoutExtensionsTests.cs | 5 +- .../StyleTests.cs | 125 ++++++++------- .../UnitExpressionSearch.cs | 8 +- .../ViewExtensionsLeftToRightTests.cs | 8 +- .../ViewExtensionsRightToLeftTests.cs | 8 +- .../ViewExtensionsTests.cs | 48 +++--- .../ViewInFlexLayoutExtensionsTests.cs | 10 +- .../ViewInGridExtensionsTests.cs | 20 +-- .../VisualElementExtensionsTests.cs | 5 +- ...n.CommunityToolkit.Markup.UnitTests.csproj | 9 +- .../BindableObjectExtensions.cs | 2 +- .../RelativeLayout.cs | 2 +- .../Xamarin.CommunityToolkit.Markup.csproj | 4 + 30 files changed, 506 insertions(+), 420 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b27fb0ddc..2e4f772ed 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,7 +13,8 @@ variables: PathToMarkupCsproj: 'src/Markup/Xamarin.CommunityToolkit.Markup/Xamarin.CommunityToolkit.Markup.csproj' PathToCommunityToolkitCsproj: 'src/CommunityToolkit/Xamarin.CommunityToolkit/Xamarin.CommunityToolkit.csproj' PathToSamplesSln: 'samples/XCT.Sample.sln' - PathToUnitTestCsproj: 'src/CommunityToolkit/Xamarin.CommunityToolkit.UnitTests/Xamarin.CommunityToolkit.UnitTests.csproj' + PathToCommunityToolkitUnitTestCsproj: 'src/CommunityToolkit/Xamarin.CommunityToolkit.UnitTests/Xamarin.CommunityToolkit.UnitTests.csproj' + PathToMarkupUnitTestCsproj: 'src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/Xamarin.CommunityToolkit.Markup.UnitTests.csproj' PathToMsBuildOnMacOS: 'mono /Applications/Visual\ studio.app/Contents/Resources/lib/monodevelop/bin/MSBuild/Current/bin/MSBuild.dll' PathToSln: 'samples/XCT.Sample.sln' @@ -107,7 +108,7 @@ jobs: Contents: 'SignList.xml' TargetFolder: '$(Build.ArtifactStagingDirectory)/nuget' - task: MSBuild@1 - displayName: Pack NuGets + displayName: Pack Community Toolkit NuGets inputs: solution: $(PathToCommunityToolkitCsproj) configuration: Release @@ -132,9 +133,13 @@ jobs: # custom: 'nuget' # arguments: 'push --source https://nuget.pkg.github.com/xamarin/index.json --api-key $(GitHub.NuGet.Token) "$(Build.ArtifactStagingDirectory)\nuget\*.nupkg"' - task: CmdLine@2 - displayName: 'Run Unit Tests' + displayName: 'Run Markup Unit Tests' inputs: - script: dotnet test $(PathToUnitTestCsproj) -c Release --collect "Code coverage" -p:BuildInParallel=false + script: dotnet test $(PathToMarkupUnitTestCsproj) -c Release --collect "Code coverage" -p:BuildInParallel=false + - task: CmdLine@2 + displayName: 'Run Community Toolkit Unit Tests' + inputs: + script: dotnet test $(PathToCommunityToolkitUnitTestCsproj) -c Release --collect "Code coverage" -p:BuildInParallel=false # publish the packages - task: PublishBuildArtifacts@1 displayName: 'Publish Unsigned NuGets' @@ -193,15 +198,27 @@ jobs: version: $(NETCORE_TEST_VERSION_2_1) includePreviewVersions: false - task: CmdLine@2 - displayName: 'Build Xamarin.CommunityToolkit.csproj' + displayName: 'Build Markup' + inputs: + script: '$(PathToMsBuildOnMacOS) $(PathToMarkupCsproj) /p:Configuration=Release /restore /t:Build /p:ContinuousIntegrationBuild=true /p:Deterministic=false' + - task: CmdLine@2 + displayName: 'Build Community Toolkit' inputs: script: '$(PathToMsBuildOnMacOS) $(PathToCommunityToolkitCsproj) /p:Configuration=Release /restore /t:Build /p:ContinuousIntegrationBuild=true /p:Deterministic=false' - task: CmdLine@2 - displayName: 'Run Unit Tests' + displayName: 'Run Markup Unit Tests' + inputs: + script: 'dotnet test $(PathToMarkupUnitTestCsproj) /p:Configuration=Release -p:BuildInParallel=false' + - task: CmdLine@2 + displayName: 'Run Community Toolkit Unit Tests' + inputs: + script: 'dotnet test $(PathToCommunityToolkitUnitTestCsproj) /p:Configuration=Release -p:BuildInParallel=false' + - task: CmdLine@2 + displayName: 'Pack Markup NuGets' inputs: - script: 'dotnet test $(PathToUnitTestCsproj) /p:Configuration=Release' + script: '$(PathToMsBuildOnMacOS) $(PathToMarkupUnitTestCsproj) /p:Configuration=Release /t:Pack /p:PackageVersion=$(NugetPackageVersion) /p:PackageOutputPath="$(Build.ArtifactStagingDirectory)/nuget"' - task: CmdLine@2 - displayName: 'Pack NuGets' + displayName: 'Pack CommunityToolkit NuGets' inputs: script: '$(PathToMsBuildOnMacOS) $(PathToCommunityToolkitCsproj) /p:Configuration=Release /t:Pack /p:PackageVersion=$(NugetPackageVersion) /p:PackageOutputPath="$(Build.ArtifactStagingDirectory)/nuget"' diff --git a/samples/XCT.Sample.sln b/samples/XCT.Sample.sln index eab396c70..4fcef2ef2 100644 --- a/samples/XCT.Sample.sln +++ b/samples/XCT.Sample.sln @@ -31,6 +31,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.CommunityToolkit.Sa EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.CommunityToolkit.Markup", "..\src\Markup\Xamarin.CommunityToolkit.Markup\Xamarin.CommunityToolkit.Markup.csproj", "{A5AAB927-15D7-498C-8295-4209F21836CE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.CommunityToolkit.Markup.UnitTests", "..\src\Markup\Xamarin.CommunityToolkit.Markup.UnitTests\Xamarin.CommunityToolkit.Markup.UnitTests.csproj", "{AAE423C4-E9B4-434E-885C-2164C12BF79C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -301,12 +303,37 @@ Global {A5AAB927-15D7-498C-8295-4209F21836CE}.Release|x64.Build.0 = Release|Any CPU {A5AAB927-15D7-498C-8295-4209F21836CE}.Release|x86.ActiveCfg = Release|Any CPU {A5AAB927-15D7-498C-8295-4209F21836CE}.Release|x86.Build.0 = Release|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Debug|ARM.ActiveCfg = Debug|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Debug|ARM.Build.0 = Debug|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Debug|iPhone.Build.0 = Debug|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Debug|x64.ActiveCfg = Debug|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Debug|x64.Build.0 = Debug|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Debug|x86.ActiveCfg = Debug|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Debug|x86.Build.0 = Debug|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Release|Any CPU.Build.0 = Release|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Release|ARM.ActiveCfg = Release|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Release|ARM.Build.0 = Release|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Release|iPhone.ActiveCfg = Release|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Release|iPhone.Build.0 = Release|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Release|x64.ActiveCfg = Release|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Release|x64.Build.0 = Release|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Release|x86.ActiveCfg = Release|Any CPU + {AAE423C4-E9B4-434E-885C-2164C12BF79C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {54B7812B-45A5-4FE2-9CEA-C5F17D65BDE9} = {47DFE508-04F1-433D-8C55-0C1ACD033573} + {AAE423C4-E9B4-434E-885C-2164C12BF79C} = {47DFE508-04F1-433D-8C55-0C1ACD033573} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {449686DB-B85A-4DFA-AA26-9BC92468CC2A} diff --git a/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BaseTestFixture.cs b/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BaseTestFixture.cs index eb6feb334..8834e4e66 100644 --- a/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BaseTestFixture.cs +++ b/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BaseTestFixture.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System; +using System.Globalization; using NUnit.Framework; using Xamarin.Forms; @@ -6,8 +7,8 @@ namespace Xamarin.CommunityToolkit.Markup.UnitTests { public class BaseTestFixture { - CultureInfo defaultCulture; - CultureInfo defaultUICulture; + CultureInfo? defaultCulture; + CultureInfo? defaultUICulture; [SetUp] public virtual void Setup() @@ -21,8 +22,9 @@ public virtual void Setup() public virtual void TearDown() { Device.PlatformServices = null; - System.Threading.Thread.CurrentThread.CurrentCulture = defaultCulture; - System.Threading.Thread.CurrentThread.CurrentUICulture = defaultUICulture; + + System.Threading.Thread.CurrentThread.CurrentCulture = defaultCulture ?? throw new NullReferenceException(); + System.Threading.Thread.CurrentThread.CurrentUICulture = defaultUICulture ?? throw new NullReferenceException(); } } } \ No newline at end of file diff --git a/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindableLayoutExtensionsTests.cs b/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindableLayoutExtensionsTests.cs index 923cf13be..2e0472e88 100644 --- a/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindableLayoutExtensionsTests.cs +++ b/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindableLayoutExtensionsTests.cs @@ -11,21 +11,21 @@ public class BindableLayoutExtensionsTests : MarkupBaseTestFixture public void EmptyView() { var view = new BoxView(); - TestPropertiesSet(l => l.EmptyView(view), (BindableLayout.EmptyViewProperty, view)); + TestPropertiesSet(l => l?.EmptyView(view), (BindableLayout.EmptyViewProperty, view)); } [Test] public void EmptyViewTemplate() { var template = new DataTemplate(() => new BoxView()); - TestPropertiesSet(l => l.EmptyViewTemplate(template), (BindableLayout.EmptyViewTemplateProperty, template)); + TestPropertiesSet(l => l?.EmptyViewTemplate(template), (BindableLayout.EmptyViewTemplateProperty, template)); } [Test] public void EmptyViewTemplateFunction() { Func loadTemplate = () => new BoxView(); - Bindable.EmptyViewTemplate(loadTemplate); + Bindable?.EmptyViewTemplate(loadTemplate); Assert.That(BindableLayout.GetEmptyViewTemplate(Bindable), Is.Not.Null); } @@ -33,21 +33,21 @@ public void EmptyViewTemplateFunction() public void ItemsSource() { var source = new string[] { }; - TestPropertiesSet(l => l.ItemsSource(source), (BindableLayout.ItemsSourceProperty, source)); + TestPropertiesSet(l => l?.ItemsSource(source), (BindableLayout.ItemsSourceProperty, source)); } [Test] public void ItemTemplate() { var template = new DataTemplate(() => new BoxView()); - TestPropertiesSet(l => l.ItemTemplate(template), (BindableLayout.ItemTemplateProperty, template)); + TestPropertiesSet(l => l?.ItemTemplate(template), (BindableLayout.ItemTemplateProperty, template)); } [Test] public void ItemTemplateFunction() { Func loadTemplate = () => new BoxView(); - Bindable.ItemTemplate(loadTemplate); + Bindable?.ItemTemplate(loadTemplate); Assert.That(BindableLayout.GetItemTemplate(Bindable), Is.Not.Null); } @@ -55,7 +55,7 @@ public void ItemTemplateFunction() public void ItemTemplateSelector() { var selector = new Selector(); - TestPropertiesSet(l => l.ItemTemplateSelector(selector), (BindableLayout.ItemTemplateSelectorProperty, selector)); + TestPropertiesSet(l => l?.ItemTemplateSelector(selector), (BindableLayout.ItemTemplateSelectorProperty, selector)); } class Selector : DataTemplateSelector diff --git a/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindableObjectExtensionsTests.cs b/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindableObjectExtensionsTests.cs index 43df590e0..2f449f7ec 100644 --- a/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindableObjectExtensionsTests.cs +++ b/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindableObjectExtensionsTests.cs @@ -10,7 +10,7 @@ namespace Xamarin.CommunityToolkit.Markup.UnitTests [TestFixture] public class BindableObjectExtensionsTests : MarkupBaseTestFixture { - ViewModel viewModel; + ViewModel? viewModel; [SetUp] public override void Setup() @@ -41,7 +41,7 @@ public void BindSpecifiedPropertyWithPositionalParameters() { var button = new Button(); object converterParameter = 1; - string stringFormat = nameof(BindSpecifiedPropertyWithPositionalParameters) + " {0}"; + var stringFormat = nameof(BindSpecifiedPropertyWithPositionalParameters) + " {0}"; IValueConverter converter = new ToStringConverter(); object source = new ViewModel(); object targetNullValue = nameof(BindSpecifiedPropertyWithPositionalParameters) + " null"; @@ -80,7 +80,7 @@ public void BindSpecifiedPropertyWithInlineOneWayConvertAndDefaults() label.Bind( Label.TextColorProperty, nameof(viewModel.IsRed), - convert: (bool isRed) => isRed ? Color.Red : Color.Transparent + convert: (bool? isRed) => isRed.HasValue && isRed.Value ? Color.Red : Color.Transparent ); BindingHelpers.AssertBindingExists( @@ -88,7 +88,7 @@ public void BindSpecifiedPropertyWithInlineOneWayConvertAndDefaults() Label.TextColorProperty, nameof(viewModel.IsRed), assertConverterInstanceIsAnyNotNull: true, - assertConvert: c => c.AssertConvert(true, Color.Red).AssertConvert(false, Color.Transparent) + assertConvert: c => c.AssertConvert(true, Color.Red).AssertConvert(false, Color.Transparent) ); } @@ -99,7 +99,7 @@ public void BindSpecifiedPropertyWithInlineOneWayParameterizedConvertAndDefaults label.Bind( Label.TextColorProperty, nameof(viewModel.IsRed), - convert: (bool isRed, double alpha) => (isRed ? Color.Red : Color.Green).MultiplyAlpha(alpha), + convert: (bool? isRed, double? alpha) => (isRed.HasValue && isRed.Value ? Color.Red : Color.Green).MultiplyAlpha(alpha ?? throw new NullReferenceException()), converterParameter: 0.5 ); @@ -109,8 +109,8 @@ public void BindSpecifiedPropertyWithInlineOneWayParameterizedConvertAndDefaults nameof(viewModel.IsRed), assertConverterInstanceIsAnyNotNull: true, converterParameter: 0.5, - assertConvert: c => c.AssertConvert(true, 0.5, Color.Red.MultiplyAlpha(0.5)) - .AssertConvert(false, 0.2, Color.Green.MultiplyAlpha(0.2)) + assertConvert: c => c.AssertConvert(true, 0.5, Color.Red.MultiplyAlpha(0.5)) + .AssertConvert(false, 0.2, Color.Green.MultiplyAlpha(0.2)) ); } @@ -122,7 +122,7 @@ public void BindSpecifiedPropertyWithInlineTwoWayConvertAndDefaults() Label.TextColorProperty, nameof(viewModel.IsRed), BindingMode.TwoWay, - (bool isRed) => isRed ? Color.Red : Color.Transparent, + (bool? isRed) => isRed.HasValue && isRed.Value ? Color.Red : Color.Transparent, color => color == Color.Red ); @@ -145,8 +145,8 @@ public void BindSpecifiedPropertyWithInlineTwoWayParameterizedConvertAndDefaults Label.TextColorProperty, nameof(viewModel.IsRed), BindingMode.TwoWay, - (bool isRed, double alpha) => (isRed ? Color.Red : Color.Green).MultiplyAlpha(alpha), - (color, alpha) => color == Color.Red.MultiplyAlpha(alpha), + (bool? isRed, double? alpha) => (isRed.HasValue && isRed.Value ? Color.Red : Color.Green).MultiplyAlpha(alpha ?? throw new NullReferenceException()), + (color, alpha) => color == Color.Red.MultiplyAlpha(alpha ?? throw new NullReferenceException()), 0.5 ); @@ -166,16 +166,16 @@ public void BindSpecifiedPropertyWithInlineTwoWayParameterizedConvertAndDefaults public void BindSpecifiedPropertyWithInlineOneWayConvertAndPositionalParameters() { var button = new Button(); - string stringFormat = nameof(BindSpecifiedPropertyWithInlineOneWayConvertAndPositionalParameters) + " {0}"; + var stringFormat = nameof(BindSpecifiedPropertyWithInlineOneWayConvertAndPositionalParameters) + " {0}"; object source = new ViewModel(); - string targetNullValue = nameof(BindSpecifiedPropertyWithInlineOneWayConvertAndPositionalParameters) + " null"; - string fallbackValue = nameof(BindSpecifiedPropertyWithInlineOneWayConvertAndPositionalParameters) + " fallback"; + var targetNullValue = nameof(BindSpecifiedPropertyWithInlineOneWayConvertAndPositionalParameters) + " null"; + var fallbackValue = nameof(BindSpecifiedPropertyWithInlineOneWayConvertAndPositionalParameters) + " fallback"; button.Bind( Button.TextProperty, nameof(viewModel.Text), BindingMode.OneWay, - (string text) => $"'{text?.Trim('\'')}'", + (string? text) => $"'{text?.Trim('\'')}'", null, stringFormat, source, @@ -201,17 +201,17 @@ public void BindSpecifiedPropertyWithInlineOneWayConvertAndPositionalParameters( public void BindSpecifiedPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters() { var button = new Button(); - int converterParameter = 1; - string stringFormat = nameof(BindSpecifiedPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " {0}"; + var converterParameter = 1; + var stringFormat = nameof(BindSpecifiedPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " {0}"; object source = new ViewModel(); - string targetNullValue = nameof(BindSpecifiedPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " null"; - string fallbackValue = nameof(BindSpecifiedPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " fallback"; + var targetNullValue = nameof(BindSpecifiedPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " null"; + var fallbackValue = nameof(BindSpecifiedPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " fallback"; button.Bind( Button.TextProperty, nameof(viewModel.Text), BindingMode.OneWay, - (string text, int repeat) => string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat)), + (string? text, int? repeat) => string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat ?? throw new NullReferenceException())), null, converterParameter, stringFormat, @@ -239,17 +239,17 @@ public void BindSpecifiedPropertyWithInlineOneWayParameterizedConvertAndPosition public void BindSpecifiedPropertyWithInlineTwoWayConvertAndPositionalParameters() { var button = new Button(); - string stringFormat = nameof(BindSpecifiedPropertyWithInlineTwoWayConvertAndPositionalParameters) + " {0}"; + var stringFormat = nameof(BindSpecifiedPropertyWithInlineTwoWayConvertAndPositionalParameters) + " {0}"; object source = new ViewModel(); - string targetNullValue = nameof(BindSpecifiedPropertyWithInlineTwoWayConvertAndPositionalParameters) + " null"; - string fallbackValue = nameof(BindSpecifiedPropertyWithInlineTwoWayConvertAndPositionalParameters) + " fallback"; + var targetNullValue = nameof(BindSpecifiedPropertyWithInlineTwoWayConvertAndPositionalParameters) + " null"; + var fallbackValue = nameof(BindSpecifiedPropertyWithInlineTwoWayConvertAndPositionalParameters) + " fallback"; button.Bind( Button.TextProperty, nameof(viewModel.Text), BindingMode.TwoWay, - (string text) => $"'{text?.Trim('\'')}'", - text => text?.Trim('\''), + (string? text) => $"'{text?.Trim('\'')}'", + text => text?.Trim('\'') ?? throw new NullReferenceException(), stringFormat, source, targetNullValue, @@ -274,18 +274,18 @@ public void BindSpecifiedPropertyWithInlineTwoWayConvertAndPositionalParameters( public void BindSpecifiedPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters() { var button = new Button(); - int converterParameter = 1; - string stringFormat = nameof(BindSpecifiedPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " {0}"; + var converterParameter = 1; + var stringFormat = nameof(BindSpecifiedPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " {0}"; object source = new ViewModel(); - string targetNullValue = nameof(BindSpecifiedPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " null"; - string fallbackValue = nameof(BindSpecifiedPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " fallback"; + var targetNullValue = nameof(BindSpecifiedPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " null"; + var fallbackValue = nameof(BindSpecifiedPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " fallback"; button.Bind( Button.TextProperty, nameof(viewModel.Text), BindingMode.TwoWay, - (string text, int repeat) => string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat)), - (text, repeat) => text?.Substring(0, text.Length / repeat).Trim('\''), + (string? text, int? repeat) => string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat ?? throw new NullReferenceException())), + (text, repeat) => text?.Substring(0, text.Length / repeat ?? throw new NullReferenceException())?.Trim('\'') ?? throw new NullReferenceException(), converterParameter, stringFormat, source, @@ -321,7 +321,7 @@ public void BindDefaultPropertyWithPositionalParameters() { var label = new Label(); object converterParameter = 1; - string stringFormat = nameof(BindDefaultPropertyWithPositionalParameters) + " {0}"; + var stringFormat = nameof(BindDefaultPropertyWithPositionalParameters) + " {0}"; IValueConverter converter = new ToStringConverter(); object source = new ViewModel(); object targetNullValue = nameof(BindDefaultPropertyWithPositionalParameters) + " null"; @@ -358,7 +358,7 @@ public void BindDefaultPropertyWithInlineOneWayConvertAndDefaults() var label = new Label(); label.Bind( nameof(viewModel.Text), - convert: (string text) => $"'{text}'" + convert: (string? text) => $"'{text}'" ); BindingHelpers.AssertBindingExists( @@ -376,7 +376,7 @@ public void BindDefaultPropertyWithInlineOneWayParameterizedConvertAndDefaults() var label = new Label(); label.Bind( nameof(viewModel.Text), - convert: (string text, int repeat) => string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat)), + convert: (string? text, int? repeat) => string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat ?? throw new NullReferenceException())), converterParameter: 1 ); @@ -397,8 +397,8 @@ public void BindDefaultPropertyWithInlineTwoWayConvertAndDefaults() label.Bind( nameof(viewModel.Text), BindingMode.TwoWay, - (string text) => $"'{text?.Trim('\'')}'", - text => text?.Trim('\'') + (string? text) => $"'{text?.Trim('\'')}'", + text => text?.Trim('\'') ?? throw new NullReferenceException() ); BindingHelpers.AssertBindingExists( @@ -418,8 +418,8 @@ public void BindDefaultPropertyWithInlineTwoWayParameterizedConvertAndDefaults() label.Bind( nameof(viewModel.Text), BindingMode.TwoWay, - (string text, int repeat) => string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat)), - (text, repeat) => text?.Substring(0, text.Length / repeat).Trim('\''), + (string? text, int? repeat) => string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat ?? throw new NullReferenceException())), + (text, repeat) => text?.Substring(0, text.Length / repeat ?? throw new NullReferenceException()).Trim('\'') ?? throw new NullReferenceException(), 2 ); @@ -438,15 +438,15 @@ public void BindDefaultPropertyWithInlineTwoWayParameterizedConvertAndDefaults() public void BindDefaultPropertyWithInlineOneWayConvertAndPositionalParameters() { var label = new Label(); - string stringFormat = nameof(BindDefaultPropertyWithInlineOneWayConvertAndPositionalParameters) + " {0}"; + var stringFormat = nameof(BindDefaultPropertyWithInlineOneWayConvertAndPositionalParameters) + " {0}"; object source = new ViewModel(); - string targetNullValue = nameof(BindDefaultPropertyWithInlineOneWayConvertAndPositionalParameters) + " null"; - string fallbackValue = nameof(BindDefaultPropertyWithInlineOneWayConvertAndPositionalParameters) + " fallback"; + var targetNullValue = nameof(BindDefaultPropertyWithInlineOneWayConvertAndPositionalParameters) + " null"; + var fallbackValue = nameof(BindDefaultPropertyWithInlineOneWayConvertAndPositionalParameters) + " fallback"; label.Bind( nameof(viewModel.Text), BindingMode.OneWay, - (string text) => $"'{text?.Trim('\'')}'", + (string? text) => $"'{text?.Trim('\'')}'", null, stringFormat, source, @@ -472,16 +472,16 @@ public void BindDefaultPropertyWithInlineOneWayConvertAndPositionalParameters() public void BindDefaultPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters() { var label = new Label(); - int converterParameter = 1; - string stringFormat = nameof(BindDefaultPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " {0}"; + var converterParameter = 1; + var stringFormat = nameof(BindDefaultPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " {0}"; object source = new ViewModel(); - string targetNullValue = nameof(BindDefaultPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " null"; - string fallbackValue = nameof(BindDefaultPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " fallback"; + var targetNullValue = nameof(BindDefaultPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " null"; + var fallbackValue = nameof(BindDefaultPropertyWithInlineOneWayParameterizedConvertAndPositionalParameters) + " fallback"; label.Bind( nameof(viewModel.Text), BindingMode.OneWay, - (string text, int repeat) => string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat)), + (string? text, int? repeat) => string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat ?? throw new NullReferenceException())), null, converterParameter, stringFormat, @@ -509,16 +509,16 @@ public void BindDefaultPropertyWithInlineOneWayParameterizedConvertAndPositional public void BindDefaultPropertyWithInlineTwoWayConvertAndPositionalParameters() { var label = new Label(); - string stringFormat = nameof(BindDefaultPropertyWithInlineTwoWayConvertAndPositionalParameters) + " {0}"; + var stringFormat = nameof(BindDefaultPropertyWithInlineTwoWayConvertAndPositionalParameters) + " {0}"; object source = new ViewModel(); - string targetNullValue = nameof(BindDefaultPropertyWithInlineTwoWayConvertAndPositionalParameters) + " null"; - string fallbackValue = nameof(BindDefaultPropertyWithInlineTwoWayConvertAndPositionalParameters) + " fallback"; + var targetNullValue = nameof(BindDefaultPropertyWithInlineTwoWayConvertAndPositionalParameters) + " null"; + var fallbackValue = nameof(BindDefaultPropertyWithInlineTwoWayConvertAndPositionalParameters) + " fallback"; label.Bind( nameof(viewModel.Text), BindingMode.TwoWay, - (string text) => $"'{text?.Trim('\'')}'", - text => text?.Trim('\''), + (string? text) => $"'{text?.Trim('\'')}'", + text => text?.Trim('\'') ?? throw new NullReferenceException(), stringFormat, source, targetNullValue, @@ -543,17 +543,17 @@ public void BindDefaultPropertyWithInlineTwoWayConvertAndPositionalParameters() public void BindDefaultPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters() { var label = new Label(); - int converterParameter = 1; - string stringFormat = nameof(BindDefaultPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " {0}"; + var converterParameter = 1; + var stringFormat = nameof(BindDefaultPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " {0}"; object source = new ViewModel(); - string targetNullValue = nameof(BindDefaultPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " null"; - string fallbackValue = nameof(BindDefaultPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " fallback"; + var targetNullValue = nameof(BindDefaultPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " null"; + var fallbackValue = nameof(BindDefaultPropertyWithInlineTwoWayParameterizedConvertAndPositionalParameters) + " fallback"; label.Bind( nameof(viewModel.Text), BindingMode.TwoWay, - (string text, int repeat) => string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat)), - (text, repeat) => text?.Substring(0, text.Length / repeat).Trim('\''), + (string? text, int? repeat) => string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat ?? throw new NullReferenceException())), + (text, repeat) => text?.Substring(0, text.Length / repeat ?? throw new NullReferenceException()).Trim('\'') ?? throw new NullReferenceException(), converterParameter, stringFormat, source, @@ -580,7 +580,7 @@ public void BindDefaultPropertyWithInlineTwoWayParameterizedConvertAndPositional public void BindCommandWithDefaults() { var textCell = new TextCell(); - string path = nameof(viewModel.Command); + var path = nameof(viewModel.Command); textCell.BindCommand(path); @@ -592,7 +592,7 @@ public void BindCommandWithDefaults() public void BindCommandWithoutParameter() { var textCell = new TextCell(); - string path = nameof(viewModel.Command); + var path = nameof(viewModel.Command); textCell.BindCommand(path, parameterPath: null); @@ -605,8 +605,8 @@ public void BindCommandWithPositionalParameters() { var textCell = new TextCell(); object source = new ViewModel(); - string path = nameof(viewModel.Command); - string parameterPath = nameof(viewModel.Id); + var path = nameof(viewModel.Command); + var parameterPath = nameof(viewModel.Id); object parameterSource = new ViewModel(); textCell.BindCommand(path, source, parameterPath, parameterSource); @@ -637,21 +637,21 @@ public void SupportDerivedElements() .Bind(nameof(viewModel.Text)) .Bind( nameof(viewModel.Text), - convert: (string text) => $"'{text}'") + convert: (string? text) => $"'{text}'") .Bind( nameof(viewModel.Text), - convert: (string text, int repeat) => string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat))) + convert: (string? text, int? repeat) => string.Concat(Enumerable.Repeat($"'{text?.Trim('\'')}'", repeat ?? throw new NullReferenceException()))) .Bind( DerivedFromLabel.TextColorProperty, nameof(viewModel.TextColor)) .Bind( DerivedFromLabel.BackgroundColorProperty, nameof(viewModel.IsRed), - convert: (bool isRed) => isRed ? Color.Black : Color.Transparent) + convert: (bool? isRed) => isRed.HasValue && isRed.Value ? Color.Black : Color.Transparent) .Bind( Label.TextColorProperty, nameof(viewModel.IsRed), - convert: (bool isRed, double alpha) => (isRed ? Color.Red : Color.Green).MultiplyAlpha(alpha)) + convert: (bool? isRed, double? alpha) => (isRed.HasValue && isRed.Value ? Color.Red : Color.Green).MultiplyAlpha(alpha ?? throw new NullReferenceException())) .Invoke(l => l.Text = nameof(SupportDerivedElements)) .Assign(out DerivedFromLabel assignDerivedFromLabel)); @@ -664,9 +664,9 @@ class ViewModel { public Guid Id { get; set; } - public ICommand Command { get; set; } + public ICommand? Command { get; set; } - public string Text { get; set; } + public string? Text { get; set; } public Color TextColor { get; set; } diff --git a/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs b/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs index f23ff75a1..5822b74e3 100644 --- a/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs +++ b/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindableObjectMultiBindExtensionsTests.cs @@ -9,9 +9,9 @@ namespace Xamarin.CommunityToolkit.Markup.UnitTests [TestFixture] public class BindableObjectMultiBindExtensionsTests : MarkupBaseTestFixture { - ViewModel viewModel; - List testBindings; - List testConvertValues; + ViewModel? viewModel; + List? testBindings; + List? testConvertValues; [SetUp] public override void Setup() @@ -20,13 +20,13 @@ public override void Setup() viewModel = new ViewModel(); testBindings = new List - { + { new Binding(nameof(viewModel.Text)), new Binding(nameof(viewModel.Id)), new Binding(nameof(viewModel.IsDone)), new Binding(nameof(viewModel.Fraction)), new Binding(nameof(viewModel.Count)) - }; + }; testConvertValues = new List { @@ -53,16 +53,23 @@ public override void TearDown() [TestCase(true, true)] public void BindSpecifiedPropertyWith2BindingsAndInlineConvert(bool testConvert, bool testConvertBack) { + _ = testBindings ?? throw new NullReferenceException(); + var label = new Label(); // Repeat inline converter code to test that the Bind overloads allow inferring the generic parameter types if (testConvert && testConvertBack) { - label.Bind( + label.Bind( Label.TextProperty, testBindings[0], testBindings[1], - ((string text, Guid id) v) => Format(0, v.text, v.id), - (string formatted) => { var u = Unformat(0, formatted); return (u.Text, u.Id); } + ((string? text, Guid id) v) => Format(0, v.text, v.id), + (string? formatted) => + { + _ = formatted ?? throw new NullReferenceException(); + var u = Unformat(0, formatted); + return (u.Text, u.Id); + } ); } else if (testConvert && !testConvertBack) @@ -70,7 +77,7 @@ public void BindSpecifiedPropertyWith2BindingsAndInlineConvert(bool testConvert, label.Bind( Label.TextProperty, testBindings[0], testBindings[1], - ((string text, Guid id) v) => Format(0, v.text, v.id) + ((string? text, Guid id) v) => Format(0, v.text, v.id) ); } else if (!testConvert && testConvertBack) @@ -78,7 +85,7 @@ public void BindSpecifiedPropertyWith2BindingsAndInlineConvert(bool testConvert, label.Bind( Label.TextProperty, testBindings[0], testBindings[1], - convertBack: (string formatted) => { var u = Unformat(0, formatted); return (u.Text, u.Id); } + convertBack: (string? formatted) => { var u = Unformat(0, formatted ?? throw new NullReferenceException()); return (u.Text, u.Id); } ); } @@ -91,34 +98,36 @@ public void BindSpecifiedPropertyWith2BindingsAndInlineConvert(bool testConvert, [TestCase(true, true)] public void BindSpecifiedPropertyWith2BindingsAndInlineConvertAndParameter(bool testConvert, bool testConvertBack) { + _ = testBindings ?? throw new NullReferenceException(); + var label = new Label(); // Repeat inline converter code to test that the Bind overloads allow inferring the generic parameter types if (testConvert && testConvertBack) { - label.Bind( + label.Bind( Label.TextProperty, testBindings[0], testBindings[1], - ((string text, Guid id) v, int parameter) => Format(parameter, v.text, v.id), - (string formatted, int parameter) => { var u = Unformat(parameter, formatted); return (u.Text, u.Id); }, + ((string? text, Guid id) v, int? parameter) => Format(parameter ?? throw new NullReferenceException(), v.text, v.id), + (string? formatted, int? parameter) => { var u = Unformat(parameter ?? throw new NullReferenceException(), formatted ?? throw new NullReferenceException()); return (u.Text, u.Id); }, converterParameter: 2 ); } else if (testConvert && !testConvertBack) { - label.Bind( + label.Bind( Label.TextProperty, testBindings[0], testBindings[1], - ((string text, Guid id) v, int parameter) => Format(parameter, v.text, v.id), + ((string? text, Guid id) v, int? parameter) => Format(parameter ?? throw new NullReferenceException(), v.text, v.id), converterParameter: 2 ); } else if (!testConvert && testConvertBack) { - label.Bind( + label.Bind( Label.TextProperty, testBindings[0], testBindings[1], - convertBack: (string formatted, int parameter) => { var u = Unformat(parameter, formatted); return (u.Text, u.Id); }, + convertBack: (string? formatted, int? parameter) => { var u = Unformat(parameter ?? throw new NullReferenceException(), formatted ?? throw new NullReferenceException()); return (u.Text, u.Id); }, converterParameter: 2 ); } @@ -132,32 +141,34 @@ public void BindSpecifiedPropertyWith2BindingsAndInlineConvertAndParameter(bool [TestCase(true, true)] public void BindSpecifiedPropertyWith3BindingsAndInlineConvert(bool testConvert, bool testConvertBack) { + _ = testBindings ?? throw new NullReferenceException(); + var label = new Label(); // Repeat inline converter code to test that the Bind overloads allow inferring the generic parameter types if (testConvert && testConvertBack) { - label.Bind( + label.Bind( Label.TextProperty, testBindings[0], testBindings[1], testBindings[2], - ((string text, Guid id, bool isDone) v) => Format(0, v.text, v.id, v.isDone), - (string formatted) => { var u = Unformat(0, formatted); return (u.Text, u.Id, u.IsDone); } + ((string? text, Guid id, bool isDone) v) => Format(0, v.text, v.id, v.isDone), + (string? formatted) => { var u = Unformat(0, formatted ?? throw new NullReferenceException()); return (u.Text, u.Id, u.IsDone); } ); } else if (testConvert && !testConvertBack) { - label.Bind( + label.Bind( Label.TextProperty, testBindings[0], testBindings[1], testBindings[2], - ((string text, Guid id, bool isDone) v) => Format(0, v.text, v.id, v.isDone) + ((string? text, Guid id, bool isDone) v) => Format(0, v.text, v.id, v.isDone) ); } else if (!testConvert && testConvertBack) { - label.Bind( + label.Bind( Label.TextProperty, testBindings[0], testBindings[1], testBindings[2], - convertBack: (string formatted) => { var u = Unformat(0, formatted); return (u.Text, u.Id, u.IsDone); } + convertBack: (string? formatted) => { var u = Unformat(0, formatted ?? throw new NullReferenceException()); return (u.Text, u.Id, u.IsDone); } ); } @@ -170,25 +181,27 @@ public void BindSpecifiedPropertyWith3BindingsAndInlineConvert(bool testConvert, [TestCase(true, true)] public void BindSpecifiedPropertyWith3BindingsAndInlineConvertAndParameter(bool testConvert, bool testConvertBack) { + _ = testBindings ?? throw new NullReferenceException(); + var label = new Label(); // Repeat inline converter code to test that the Bind overloads allow inferring the generic parameter types if (testConvert && testConvertBack) { label.Bind( - Label.TextProperty, - testBindings[0], testBindings[1], testBindings[2], - ((string text, Guid id, bool isDone) v, int parameter) => Format(parameter, v.text, v.id, v.isDone), - (string formatted, int parameter) => { var u = Unformat(parameter, formatted); return (u.Text, u.Id, u.IsDone); }, - converterParameter: 2 - ); + Label.TextProperty, + testBindings[0], testBindings[1], testBindings[2], + ((string? text, Guid id, bool isDone) v, int? parameter) => Format(parameter ?? throw new NullReferenceException(), v.text, v.id, v.isDone), + (string? formatted, int? parameter) => { var u = Unformat(parameter ?? throw new NullReferenceException(), formatted ?? throw new NullReferenceException()); return (u.Text ?? throw new NullReferenceException(), u.Id, u.IsDone); }, + converterParameter: 2 + ); } else if (testConvert && !testConvertBack) { label.Bind( Label.TextProperty, testBindings[0], testBindings[1], testBindings[2], - ((string text, Guid id, bool isDone) v, int parameter) => Format(parameter, v.text, v.id, v.isDone), + ((string? text, Guid id, bool isDone) v, int? parameter) => Format(parameter ?? throw new NullReferenceException(), v.text, v.id, v.isDone), converterParameter: 2 ); } @@ -197,7 +210,7 @@ public void BindSpecifiedPropertyWith3BindingsAndInlineConvertAndParameter(bool label.Bind( Label.TextProperty, testBindings[0], testBindings[1], testBindings[2], - convertBack: (string formatted, int parameter) => { var u = Unformat(parameter, formatted); return (u.Text, u.Id, u.IsDone); }, + convertBack: (string? formatted, int? parameter) => { var u = Unformat(parameter ?? throw new NullReferenceException(), formatted ?? throw new NullReferenceException()); return (u.Text, u.Id, u.IsDone); }, converterParameter: 2 ); } @@ -211,6 +224,8 @@ public void BindSpecifiedPropertyWith3BindingsAndInlineConvertAndParameter(bool [TestCase(true, true)] public void BindSpecifiedPropertyWith4BindingsAndInlineConvert(bool testConvert, bool testConvertBack) { + _ = testBindings ?? throw new NullReferenceException(); + var label = new Label(); // Repeat inline converter code to test that the Bind overloads allow inferring the generic parameter types @@ -219,8 +234,8 @@ public void BindSpecifiedPropertyWith4BindingsAndInlineConvert(bool testConvert, label.Bind( Label.TextProperty, testBindings[0], testBindings[1], testBindings[2], testBindings[3], - ((string text, Guid id, bool isDone, double fraction) v) => Format(0, v.text, v.id, v.isDone, v.fraction), - (string formatted) => { var u = Unformat(0, formatted); return (u.Text, u.Id, u.IsDone, u.Fraction); } + ((string? text, Guid id, bool isDone, double fraction) v) => Format(0, v.text, v.id, v.isDone, v.fraction), + (string? formatted) => { var u = Unformat(0, formatted ?? throw new NullReferenceException()); return (u.Text ?? string.Empty, u.Id, u.IsDone, u.Fraction); } ); } else if (testConvert && !testConvertBack) @@ -228,7 +243,7 @@ public void BindSpecifiedPropertyWith4BindingsAndInlineConvert(bool testConvert, label.Bind( Label.TextProperty, testBindings[0], testBindings[1], testBindings[2], testBindings[3], - ((string text, Guid id, bool isDone, double fraction) v) => Format(0, v.text, v.id, v.isDone, v.fraction) + ((string? text, Guid id, bool isDone, double fraction) v) => Format(0, v.text, v.id, v.isDone, v.fraction) ); } else if (!testConvert && testConvertBack) @@ -236,7 +251,7 @@ public void BindSpecifiedPropertyWith4BindingsAndInlineConvert(bool testConvert, label.Bind( Label.TextProperty, testBindings[0], testBindings[1], testBindings[2], testBindings[3], - convertBack: (string formatted) => { var u = Unformat(0, formatted); return (u.Text, u.Id, u.IsDone, u.Fraction); } + convertBack: (string? formatted) => { var u = Unformat(0, formatted ?? throw new NullReferenceException()); return (u.Text, u.Id, u.IsDone, u.Fraction); } ); } @@ -249,6 +264,8 @@ public void BindSpecifiedPropertyWith4BindingsAndInlineConvert(bool testConvert, [TestCase(true, true)] public void BindSpecifiedPropertyWith4BindingsAndInlineConvertAndParameter(bool testConvert, bool testConvertBack) { + _ = testBindings ?? throw new NullReferenceException(); + var label = new Label(); // Repeat inline converter code to test that the Bind overloads allow inferring the generic parameter types @@ -257,8 +274,8 @@ public void BindSpecifiedPropertyWith4BindingsAndInlineConvertAndParameter(bool label.Bind( Label.TextProperty, testBindings[0], testBindings[1], testBindings[2], testBindings[3], - ((string text, Guid id, bool isDone, double fraction) v, int parameter) => Format(parameter, v.text, v.id, v.isDone, v.fraction), - (string formatted, int parameter) => { var u = Unformat(parameter, formatted); return (u.Text, u.Id, u.IsDone, u.Fraction); }, + ((string? text, Guid id, bool isDone, double fraction) v, int? parameter) => Format(parameter ?? throw new NullReferenceException(), v.text, v.id, v.isDone, v.fraction), + (string? formatted, int? parameter) => { var u = Unformat(parameter ?? throw new NullReferenceException(), formatted ?? throw new NullReferenceException()); return (u.Text ?? string.Empty, u.Id, u.IsDone, u.Fraction); }, converterParameter: 2 ); } @@ -267,7 +284,7 @@ public void BindSpecifiedPropertyWith4BindingsAndInlineConvertAndParameter(bool label.Bind( Label.TextProperty, testBindings[0], testBindings[1], testBindings[2], testBindings[3], - ((string text, Guid id, bool isDone, double fraction) v, int parameter) => Format(parameter, v.text, v.id, v.isDone, v.fraction), + ((string? text, Guid id, bool isDone, double fraction) v, int? parameter) => Format(parameter ?? throw new NullReferenceException(), v.text, v.id, v.isDone, v.fraction), converterParameter: 2 ); } @@ -276,7 +293,7 @@ public void BindSpecifiedPropertyWith4BindingsAndInlineConvertAndParameter(bool label.Bind( Label.TextProperty, testBindings[0], testBindings[1], testBindings[2], testBindings[3], - convertBack: (string formatted, int parameter) => { var u = Unformat(parameter, formatted); return (u.Text, u.Id, u.IsDone, u.Fraction); }, + convertBack: (string? formatted, int? parameter) => { var u = Unformat(parameter ?? throw new NullReferenceException(), formatted ?? throw new NullReferenceException()); return (u.Text, u.Id, u.IsDone, u.Fraction); }, converterParameter: 2 ); } @@ -290,22 +307,24 @@ public void BindSpecifiedPropertyWith4BindingsAndInlineConvertAndParameter(bool [TestCase(true, true)] public void BindSpecifiedPropertyWithMultipleBindings(bool testConvert, bool testConvertBack) { - Func convert = null; + _ = testBindings ?? throw new NullReferenceException(); + + Func? convert = null; if (testConvert) - convert = (object[] v) => Format(0, v[0], v[1], v[2], v[3], v[4]); + convert = (object[] v) => Format(0, v[0], v[1], v[2], v[3], v[4]); - Func convertBack = null; + Func? convertBack = null; if (testConvertBack) { - convertBack = (string formatted) => - { - var u = Unformat(0, formatted); - return new object[] { u.Text, u.Id, u.IsDone, u.Fraction, u.Count }; + convertBack = (string? formatted) => + { + var result = Unformat(0, formatted ?? throw new NullReferenceException()); + return new object[] { result.Text ?? string.Empty, result.Id, result.IsDone, result.Fraction, result.Count }; }; } var converter = new FuncMultiConverter(convert, convertBack); - var label = new Label { } .Bind (Label.TextProperty, GetTestBindings(5), converter); + var label = new Label { }.Bind(Label.TextProperty, GetTestBindings(5), converter); AssertLabelTextMultiBound(label, 5, testConvert, testConvertBack, converter: converter); } @@ -315,25 +334,25 @@ public void BindSpecifiedPropertyWithMultipleBindings(bool testConvert, bool tes [TestCase(true, true)] public void BindSpecifiedPropertyWithMultipleBindingsAndParameter(bool testConvert, bool testConvertBack) { - Func convert = null; + Func? convert = null; if (testConvert) { convert = (object[] v, int parameter) => Format(parameter, v[0], v[1], v[2], v[3], v[4]); } - Func convertBack = null; + Func? convertBack = null; if (testConvertBack) { - convertBack = (string text, int parameter) => + convertBack = (string? text, int parameter) => { - var u = Unformat(parameter, text); - return new object[] { u.Text, u.Id, u.IsDone, u.Fraction, u.Count }; + var u = Unformat(parameter, text ?? throw new NullReferenceException()); + return new object[] { u.Text ?? string.Empty, u.Id, u.IsDone, u.Fraction, u.Count }; }; } - var converter = new FuncMultiConverter(convert, convertBack); - var label = new Label { } .Bind (Label.TextProperty, GetTestBindings(5), converter, 2); + var converter = new FuncMultiConverter(convert, convertBack); + var label = new Label { }.Bind(Label.TextProperty, GetTestBindings(5), converter, 2); AssertLabelTextMultiBound(label, 5, testConvert, testConvertBack, 2, converter); } @@ -341,22 +360,22 @@ public void BindSpecifiedPropertyWithMultipleBindingsAndParameter(bool testConve object[] GetTestConvertValues(int count) => testConvertValues.Take(count).ToArray(); - string PrefixDots(object value, int count) => $"{new string('.', count)}{value}"; + string PrefixDots(object? value, int count) => $"{new string('.', count)}{value}"; string RemoveDots(string text, int count) => text.Substring(count); - string Format(int parameter, params object[] values) + string Format(int parameter, params object?[] values) { - string formatted = $"'{PrefixDots(values[0], parameter)}'"; - for (int i = 1; i < values.Length; i++) + var formatted = $"'{PrefixDots(values[0], parameter)}'"; + for (var i = 1; i < values.Length; i++) formatted += $", '{values[i]}'"; return formatted; } - (string Text, Guid Id, bool IsDone, double Fraction, int Count) Unformat(int parameter, string formatted) + (string? Text, Guid Id, bool IsDone, double Fraction, int Count) Unformat(int parameter, string formatted) { var split = formatted.Split('\''); - int n = split.Length; + var n = split.Length; return ( n > 1 ? RemoveDots(split[1], parameter) : null, @@ -366,10 +385,10 @@ string Format(int parameter, params object[] values) n > 9 ? int.Parse(split[9]) : 0); } - void AssertLabelTextMultiBound(Label label, int nBindings, bool testConvert, bool testConvertBack, int parameter, IMultiValueConverter converter = null) + void AssertLabelTextMultiBound(Label label, int nBindings, bool testConvert, bool testConvertBack, int parameter, IMultiValueConverter? converter = null) { var values = GetTestConvertValues(nBindings); - string expected = Format(parameter, values); + var expected = Format(parameter, values); BindingHelpers.AssertBindingExists( label, @@ -381,10 +400,10 @@ void AssertLabelTextMultiBound(Label label, int nBindings, bool testConvert, boo assertConvert: c => c.AssertConvert(values, parameter, expected, twoWay: testConvert && testConvertBack, backOnly: !testConvert && testConvertBack)); } - void AssertLabelTextMultiBound(Label label, int nBindings, bool testConvert, bool testConvertBack, IMultiValueConverter converter = null) + void AssertLabelTextMultiBound(Label label, int nBindings, bool testConvert, bool testConvertBack, IMultiValueConverter? converter = null) { var values = GetTestConvertValues(nBindings); - string expected = Format(0, values); + var expected = Format(0, values); BindingHelpers.AssertBindingExists( label, @@ -399,7 +418,7 @@ class ViewModel { public Guid Id { get; set; } - public string Text { get; set; } + public string Text { get; set; } = string.Empty; public bool IsDone { get; set; } diff --git a/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindingHelpers.cs b/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindingHelpers.cs index b92161d09..1bd826306 100644 --- a/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindingHelpers.cs +++ b/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/BindingHelpers.cs @@ -8,10 +8,10 @@ namespace Xamarin.CommunityToolkit.Markup.UnitTests { - internal static class BindingHelpers + static class BindingHelpers { - static MethodInfo getContextMethodInfo; - static FieldInfo bindingFieldInfo; + static MethodInfo? getContextMethodInfo; + static FieldInfo? bindingFieldInfo; internal static void AssertBindingExists( BindableObject bindable, @@ -19,13 +19,13 @@ internal static void AssertBindingExists( string path = ".", BindingMode mode = BindingMode.Default, bool assertConverterInstanceIsAnyNotNull = false, - IValueConverter converter = null, - object converterParameter = null, - string stringFormat = null, - object source = null, - object targetNullValue = default, - object fallbackValue = default, - Action assertConvert = null) + IValueConverter? converter = null, + object? converterParameter = null, + string? stringFormat = null, + object? source = null, + object? targetNullValue = default, + object? fallbackValue = default, + Action? assertConvert = null) => AssertBindingExists( bindable, targetProperty, path, mode, assertConverterInstanceIsAnyNotNull, converter, converterParameter, stringFormat, source, targetNullValue, fallbackValue, assertConvert); @@ -36,12 +36,12 @@ internal static void AssertBindingExists( string path = ".", BindingMode mode = BindingMode.Default, bool assertConverterInstanceIsAnyNotNull = false, - IValueConverter converter = null, - string stringFormat = null, - object source = null, - TDest targetNullValue = default, - TDest fallbackValue = default, - Action assertConvert = null) + IValueConverter? converter = null, + string? stringFormat = null, + object? source = null, + TDest? targetNullValue = default, + TDest? fallbackValue = default, + Action? assertConvert = null) => AssertBindingExists( bindable, targetProperty, path, mode, assertConverterInstanceIsAnyNotNull, converter, null, stringFormat, source, targetNullValue, fallbackValue, assertConvert); @@ -52,15 +52,15 @@ internal static void AssertBindingExists( string path = ".", BindingMode mode = BindingMode.Default, bool assertConverterInstanceIsAnyNotNull = false, - IValueConverter converter = null, - TParam converterParameter = default, - string stringFormat = null, - object source = null, - TDest targetNullValue = default, - TDest fallbackValue = default, - Action assertConvert = null) + IValueConverter? converter = null, + TParam? converterParameter = default, + string? stringFormat = null, + object? source = null, + TDest? targetNullValue = default, + TDest? fallbackValue = default, + Action? assertConvert = null) { - var binding = BindingHelpers.GetBinding(bindable, targetProperty); + var binding = GetBinding(bindable, targetProperty) ?? throw new NullReferenceException(); Assert.That(binding, Is.Not.Null); Assert.That(binding.Path, Is.EqualTo(path)); Assert.That(binding.Mode, Is.EqualTo(mode)); @@ -81,13 +81,13 @@ internal static void AssertBindingExists( BindableObject bindable, BindableProperty targetProperty, IList bindings, - IMultiValueConverter converter = null, + IMultiValueConverter? converter = null, BindingMode mode = BindingMode.Default, bool assertConverterInstanceIsAnyNotNull = false, - string stringFormat = null, - TDest targetNullValue = default, - TDest fallbackValue = default, - Action assertConvert = null) + string? stringFormat = null, + TDest? targetNullValue = default, + TDest? fallbackValue = default, + Action? assertConvert = null) => AssertBindingExists( bindable, targetProperty, bindings, converter, null, mode, assertConverterInstanceIsAnyNotNull, stringFormat, targetNullValue, fallbackValue, assertConvert); @@ -96,16 +96,16 @@ internal static void AssertBindingExists( BindableObject bindable, BindableProperty targetProperty, IList bindings, - IMultiValueConverter converter = null, - TParam converterParameter = default, + IMultiValueConverter? converter = null, + TParam? converterParameter = default, BindingMode mode = BindingMode.Default, bool assertConverterInstanceIsAnyNotNull = false, - string stringFormat = null, - TDest targetNullValue = default, - TDest fallbackValue = default, - Action assertConvert = null) + string? stringFormat = null, + TDest? targetNullValue = default, + TDest? fallbackValue = default, + Action? assertConvert = null) { - var binding = BindingHelpers.GetMultiBinding(bindable, targetProperty); + var binding = GetMultiBinding(bindable, targetProperty) ?? throw new NullReferenceException(); Assert.That(binding, Is.Not.Null); Assert.That(binding.Bindings.SequenceEqual(bindings), Is.True); Assert.That(binding.Mode, Is.EqualTo(mode)); @@ -121,16 +121,16 @@ internal static void AssertBindingExists( assertConvert?.Invoke(binding.Converter); } - internal static Binding GetBinding(BindableObject bindable, BindableProperty property) => GetBindingBase(bindable, property) as Binding; + internal static Binding? GetBinding(BindableObject bindable, BindableProperty property) => GetBindingBase(bindable, property) as Binding; - internal static MultiBinding GetMultiBinding(BindableObject bindable, BindableProperty property) => GetBindingBase(bindable, property) as MultiBinding; + internal static MultiBinding? GetMultiBinding(BindableObject bindable, BindableProperty property) => GetBindingBase(bindable, property) as MultiBinding; /// /// Note that we are only testing whether the Markup helpers create the correct bindings, /// we are not testing the binding mechanism itself; this is why it is justified to access /// private binding API's here for testing. /// - internal static BindingBase GetBindingBase(BindableObject bindable, BindableProperty property) + internal static BindingBase? GetBindingBase(BindableObject bindable, BindableProperty property) { if (getContextMethodInfo == null) getContextMethodInfo = typeof(BindableObject).GetMethod("GetContext", BindingFlags.NonPublic | BindingFlags.Instance); @@ -145,24 +145,24 @@ internal static BindingBase GetBindingBase(BindableObject bindable, BindableProp return bindingFieldInfo?.GetValue(context) as BindingBase; } - internal static IValueConverter AssertConvert(this IValueConverter converter, TValue value, object parameter, TConvertedValue expectedConvertedValue, bool twoWay = false, bool backOnly = false, CultureInfo culture = null) + internal static IValueConverter AssertConvert(this IValueConverter converter, TValue value, object? parameter, TConvertedValue expectedConvertedValue, bool twoWay = false, bool backOnly = false, CultureInfo? culture = null) { - Assert.That(converter?.Convert(value, typeof(object), parameter, culture), Is.EqualTo(backOnly ? default(TConvertedValue) : expectedConvertedValue)); - Assert.That(converter?.ConvertBack(expectedConvertedValue, typeof(object), parameter, culture), Is.EqualTo(twoWay || backOnly ? value : default(TValue))); + Assert.That(converter.Convert(value, typeof(object), parameter, culture), Is.EqualTo(backOnly ? default(TConvertedValue) : expectedConvertedValue)); + Assert.That(converter.ConvertBack(expectedConvertedValue, typeof(object), parameter, culture), Is.EqualTo(twoWay || backOnly ? value : default(TValue))); return converter; } - internal static IValueConverter AssertConvert(this IValueConverter converter, TValue value, TConvertedValue expectedConvertedValue, bool twoWay = false, bool backOnly = false, CultureInfo culture = null) + internal static IValueConverter AssertConvert(this IValueConverter converter, TValue value, TConvertedValue expectedConvertedValue, bool twoWay = false, bool backOnly = false, CultureInfo? culture = null) => AssertConvert(converter, value, null, expectedConvertedValue, twoWay: twoWay, backOnly: backOnly, culture: culture); - internal static IMultiValueConverter AssertConvert(this IMultiValueConverter converter, object[] values, object parameter, TConvertedValue expectedConvertedValue, bool twoWay = false, bool backOnly = false, CultureInfo culture = null) + internal static IMultiValueConverter AssertConvert(this IMultiValueConverter converter, object[] values, object? parameter, TConvertedValue expectedConvertedValue, bool twoWay = false, bool backOnly = false, CultureInfo? culture = null) { - Assert.That(converter?.Convert(values, typeof(TConvertedValue), parameter, culture), Is.EqualTo(backOnly ? BindableProperty.UnsetValue : expectedConvertedValue)); - var convertedBackValues = converter?.ConvertBack(expectedConvertedValue, null, parameter, culture); + Assert.That(converter.Convert(values, typeof(TConvertedValue), parameter, culture), Is.EqualTo(backOnly ? BindableProperty.UnsetValue : expectedConvertedValue)); + var convertedBackValues = converter.ConvertBack(expectedConvertedValue, null, parameter, culture); if (twoWay || backOnly) { Assert.That(convertedBackValues.Length, Is.EqualTo(values.Length)); - for (int i = 0; i < values.Length; i++) + for (var i = 0; i < values.Length; i++) Assert.That(convertedBackValues[i], Is.EqualTo(values[i])); } else @@ -170,7 +170,7 @@ internal static IMultiValueConverter AssertConvert(this IMultiV return converter; } - internal static IMultiValueConverter AssertConvert(this IMultiValueConverter converter, object[] values, TConvertedValue expectedConvertedValue, bool twoWay = false, bool backOnly = false, CultureInfo culture = null) + internal static IMultiValueConverter AssertConvert(this IMultiValueConverter converter, object[] values, TConvertedValue expectedConvertedValue, bool twoWay = false, bool backOnly = false, CultureInfo? culture = null) => AssertConvert(converter, values, null, expectedConvertedValue, twoWay, backOnly, culture); } } diff --git a/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/DefaultBindablePropertiesTests.cs b/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/DefaultBindablePropertiesTests.cs index aefdf1c53..860445cef 100644 --- a/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/DefaultBindablePropertiesTests.cs +++ b/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/DefaultBindablePropertiesTests.cs @@ -127,7 +127,7 @@ public void AllBindableElementsInCoreHaveDefaultBindablePropertyOrAreExcluded() foreach (var type in bindableObjectTypes) { - if (excludedTypeReasons.TryGetValue(type, out string exclusionReason)) + if (excludedTypeReasons.TryGetValue(type, out var exclusionReason)) { Console.WriteLine($"Info: no default BindableProperty defined for BindableObject type {type.FullName} because {exclusionReason}"); continue; @@ -137,7 +137,7 @@ public void AllBindableElementsInCoreHaveDefaultBindablePropertyOrAreExcluded() { failMessage.AppendLine(type.FullName); var propertyNames = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly) - .Where(f => f.FieldType == typeof(BindableProperty)).Select(f => f.DeclaringType.Name + "." + f.Name).ToList(); + .Where(f => f.FieldType == typeof(BindableProperty)).Select(f => f?.DeclaringType?.Name + "." + f?.Name).ToList(); if (propertyNames.Count > 0) { failMessage.AppendLine("\tCandidate properties:"); diff --git a/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/ElementExtensionsTests.cs b/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/ElementExtensionsTests.cs index 40ceabbc3..39916b894 100644 --- a/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/ElementExtensionsTests.cs +++ b/src/Markup/Xamarin.CommunityToolkit.Markup.UnitTests/ElementExtensionsTests.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using System; +using NUnit.Framework; using Xamarin.Forms; using FontElement = Xamarin.Forms.Label; // TODO: Get rid of this after we have default interface implementation in Forms for IFontElement @@ -7,7 +8,7 @@ namespace Xamarin.CommunityToolkit.Markup.UnitTests [TestFixture] public class ElementExtensionsTests : MarkupBaseTestFixture