From 1da32c92e7ed3f111a32d011ae2d87e809ee99a0 Mon Sep 17 00:00:00 2001 From: Gerald Versluis Date: Wed, 24 Jun 2026 18:43:33 +0200 Subject: [PATCH 01/17] Remove MAUI internals usage Replace MAUI internal helper dependencies with public API mappings and public Binding-based typed binding behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../BindingHelpers.cs | 23 +- .../ElementExtensionsTests.cs | 20 +- .../GesturesExtensionsTests.cs | 8 +- .../ImageExtensionTests.cs | 8 +- .../Mocks/MockApplication.cs | 4 - .../Mocks/MockResourcesProvider.cs | 14 - .../PaceholderExtensionsTests.cs | 6 +- .../PaddingElementExtensionsTests.cs | 10 +- .../TextTests.cs | 40 +- .../TypedBindingExtensionsTests.cs | 182 ++++++- .../UnitExpressionSearch.cs | 35 -- .../BindablePropertyHelpers.cs | 119 +++++ .../DynamicResourceHandlerExtensions.cs | 9 +- .../ElementExtensions.cs | 55 ++- .../ImageExtensions.cs | 6 +- .../PlaceholderExtensions.cs | 4 +- .../TypedBinding.cs | 444 ------------------ .../TypedBindingExtensions.cs | 75 ++- .../TypedBindingsExtensions.Handlers.cs | 323 +++++++++++-- .../TypedGesturesExtensions.cs | 38 +- 20 files changed, 764 insertions(+), 659 deletions(-) delete mode 100644 src/CommunityToolkit.Maui.Markup.UnitTests/Mocks/MockResourcesProvider.cs delete mode 100644 src/CommunityToolkit.Maui.Markup.UnitTests/UnitExpressionSearch.cs create mode 100644 src/CommunityToolkit.Maui.Markup/BindablePropertyHelpers.cs delete mode 100644 src/CommunityToolkit.Maui.Markup/TypedBinding.cs diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/BindingHelpers.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/BindingHelpers.cs index 3913bfed..388c5f0d 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/BindingHelpers.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/BindingHelpers.cs @@ -1,6 +1,5 @@ -using System.Globalization; +using System.Globalization; using System.Reflection; -using Microsoft.Maui.Controls.Internals; using NUnit.Framework; namespace CommunityToolkit.Maui.Markup.UnitTests; @@ -79,14 +78,17 @@ internal static void AssertTypedBindingExists { Assert.That(binding, Is.Not.Null); Assert.That(binding.Mode, Is.EqualTo(expectedBindingMode)); - Assert.That(binding.Converter?.ToString(), Is.EqualTo(expectedConverter?.ToString())); + var actualConverter = binding.Converter?.GetType().Name.StartsWith("GetterValueConverter", StringComparison.Ordinal) is true && expectedConverter is null + ? null + : binding.Converter; + Assert.That(actualConverter?.ToString(), Is.EqualTo(expectedConverter?.ToString())); Assert.That(binding.ConverterParameter, Is.EqualTo(expectedConverterParameter)); @@ -200,8 +202,6 @@ internal static void AssertBindingExists( internal static Binding? GetBinding(BindableObject bindable, BindableProperty property) => GetBindingBase(bindable, property); - internal static TypedBindingBase? GetTypedBinding(BindableObject bindable, BindableProperty property) => GetBindingBase(bindable, property); - internal static MultiBinding? GetMultiBinding(BindableObject bindable, BindableProperty property) => GetBindingBase(bindable, property); /// @@ -213,8 +213,15 @@ internal static void AssertBindingExists( { getContextMethodInfo ??= typeof(BindableObject).GetMethod("GetContext", BindingFlags.NonPublic | BindingFlags.Instance); - var context = (BindableObject.BindablePropertyContext?)getContextMethodInfo?.Invoke(bindable, [property]); - return (TBinding?)context?.Bindings.GetValue(); + var context = getContextMethodInfo?.Invoke(bindable, [property]); + var contextType = context?.GetType(); + var bindings = contextType?.GetProperty("Bindings", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(context) + ?? contextType?.GetField("Bindings", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(context); + var binding = bindings?.GetType() + .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .SingleOrDefault(static method => method.Name is "GetValue" && method.GetParameters().Length is 0) + ?.Invoke(bindings, null); + return binding as TBinding; } internal static IValueConverter AssertConvert(this IValueConverter converter, TValue value, object? parameter, TConvertedValue expectedConvertedValue, bool twoWay = false, bool backOnly = false, CultureInfo? culture = null) diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/ElementExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/ElementExtensionsTests.cs index bf2e1833..775ea12e 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/ElementExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/ElementExtensionsTests.cs @@ -60,39 +60,39 @@ public void EffectsMultiple() [Test] public void FontSize() - => TestPropertiesSet(l => l.FontSize(8), (FontElement.FontSizeProperty, 6.0, 8.0)); + => TestPropertiesSet(l => l.FontSize(8), (Label.FontSizeProperty, 6.0, 8.0)); [Test] public void Bold() - => TestPropertiesSet(l => l.Bold(), (FontElement.FontAttributesProperty, FontAttributes.None, FontAttributes.Bold)); + => TestPropertiesSet(l => l.Bold(), (Label.FontAttributesProperty, FontAttributes.None, FontAttributes.Bold)); [Test] public void Italic() - => TestPropertiesSet(l => l.Italic(), (FontElement.FontAttributesProperty, FontAttributes.None, FontAttributes.Italic)); + => TestPropertiesSet(l => l.Italic(), (Label.FontAttributesProperty, FontAttributes.None, FontAttributes.Italic)); [Test] public void FontWithPositionalParameters() => TestPropertiesSet( l => l.Font("AFontName", 8, true, true), - (FontElement.FontSizeProperty, 6.0, 8.0), - (FontElement.FontAttributesProperty, FontAttributes.None, FontAttributes.Bold | FontAttributes.Italic), - (FontElement.FontFamilyProperty, string.Empty, "AFontName")); + (Label.FontSizeProperty, 6.0, 8.0), + (Label.FontAttributesProperty, FontAttributes.None, FontAttributes.Bold | FontAttributes.Italic), + (Label.FontFamilyProperty, string.Empty, "AFontName")); [Test] public void FontWithSizeNamedParameter() - => TestPropertiesSet(l => l.Font(size: 8), (FontElement.FontSizeProperty, 6.0, 8.0)); + => TestPropertiesSet(l => l.Font(size: 8), (Label.FontSizeProperty, 6.0, 8.0)); [Test] public void FontWithBoldNamedParameter() - => TestPropertiesSet(l => l.Font(bold: true), (FontElement.FontAttributesProperty, FontAttributes.None, FontAttributes.Bold)); + => TestPropertiesSet(l => l.Font(bold: true), (Label.FontAttributesProperty, FontAttributes.None, FontAttributes.Bold)); [Test] public void FontWithItalicNamedParameter() - => TestPropertiesSet(l => l.Font(italic: true), (FontElement.FontAttributesProperty, FontAttributes.None, FontAttributes.Italic)); + => TestPropertiesSet(l => l.Font(italic: true), (Label.FontAttributesProperty, FontAttributes.None, FontAttributes.Italic)); [Test] public void FontWithFamilyNamedParameter() - => TestPropertiesSet(l => l.Font(family: "AFontName"), (FontElement.FontFamilyProperty, string.Empty, "AFontName")); + => TestPropertiesSet(l => l.Font(family: "AFontName"), (Label.FontFamilyProperty, string.Empty, "AFontName")); static Label AssertDynamicResources() { diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/GesturesExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/GesturesExtensionsTests.cs index 805bf367..1d6f5d9c 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/GesturesExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/GesturesExtensionsTests.cs @@ -206,7 +206,7 @@ public void BindTapGestureDefaults() Assert.That(gestureElement.GestureRecognizers[0], Is.InstanceOf()); }); - BindingHelpers.AssertTypedBindingExists((TapGestureRecognizer)gestureElement.GestureRecognizers[0], TapGestureRecognizer.CommandProperty, BindingMode.Default, gestureElement.BindingContext); + BindingHelpers.AssertTypedBindingExists((TapGestureRecognizer)gestureElement.GestureRecognizers[0], TapGestureRecognizer.CommandProperty, BindingMode.OneWay, gestureElement.BindingContext); } [Test] @@ -308,7 +308,7 @@ public void BindTapGesturePositionalParametersWithNestedBindings() }); BindingHelpers.AssertTypedBindingExists(tapGestureRecognizer, TapGestureRecognizer.CommandProperty, BindingMode.OneTime, gestureElement.BindingContext); - BindingHelpers.AssertTypedBindingExists(tapGestureRecognizer, TapGestureRecognizer.CommandParameterProperty, BindingMode.Default, gestureElement.BindingContext); + BindingHelpers.AssertTypedBindingExists(tapGestureRecognizer, TapGestureRecognizer.CommandParameterProperty, BindingMode.OneWay, gestureElement.BindingContext); } [Test] @@ -323,7 +323,7 @@ public void BindSwipeGestureDefaults() Assert.That(gestureElement.GestureRecognizers, Has.Count.EqualTo(1)); Assert.That(gestureElement.GestureRecognizers[0], Is.InstanceOf()); - BindingHelpers.AssertTypedBindingExists((SwipeGestureRecognizer)gestureElement.GestureRecognizers[0], SwipeGestureRecognizer.CommandProperty, BindingMode.Default, gestureElement.BindingContext); + BindingHelpers.AssertTypedBindingExists((SwipeGestureRecognizer)gestureElement.GestureRecognizers[0], SwipeGestureRecognizer.CommandProperty, BindingMode.OneWay, gestureElement.BindingContext); } [Test] @@ -429,7 +429,7 @@ public void BindSwipeGesturePositionalParametersWithNestedBindings() }); BindingHelpers.AssertTypedBindingExists((SwipeGestureRecognizer)gestureElement.GestureRecognizers[0], SwipeGestureRecognizer.CommandProperty, BindingMode.OneTime, gestureElement.BindingContext); - BindingHelpers.AssertTypedBindingExists((SwipeGestureRecognizer)gestureElement.GestureRecognizers[0], SwipeGestureRecognizer.CommandParameterProperty, BindingMode.Default, gestureElement.BindingContext); + BindingHelpers.AssertTypedBindingExists((SwipeGestureRecognizer)gestureElement.GestureRecognizers[0], SwipeGestureRecognizer.CommandParameterProperty, BindingMode.OneWay, gestureElement.BindingContext); } [Test] diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/ImageExtensionTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/ImageExtensionTests.cs index 5d5f1291..b7c676bc 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/ImageExtensionTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/ImageExtensionTests.cs @@ -23,14 +23,14 @@ public void SetSourceTest() [Test] public void SetAspectTest() { - TestPropertiesSet(i => i.Aspect(Aspect.AspectFill), (ImageElement.AspectProperty, Aspect.AspectFill)); - TestPropertiesSet(i => i.Aspect(Aspect.Center), (ImageElement.AspectProperty, Aspect.Center)); - TestPropertiesSet(i => i.Aspect(Aspect.Fill), (ImageElement.AspectProperty, Aspect.Fill)); + TestPropertiesSet(i => i.Aspect(Aspect.AspectFill), (Image.AspectProperty, Aspect.AspectFill)); + TestPropertiesSet(i => i.Aspect(Aspect.Center), (Image.AspectProperty, Aspect.Center)); + TestPropertiesSet(i => i.Aspect(Aspect.Fill), (Image.AspectProperty, Aspect.Fill)); } [Test] public void SetIsOpaqueTest() - => TestPropertiesSet(i => i.IsOpaque(true), (ImageElement.IsOpaqueProperty, true)); + => TestPropertiesSet(i => i.IsOpaque(true), (Image.IsOpaqueProperty, true)); [Test] public void SupportDerivedFromImage() diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/Mocks/MockApplication.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/Mocks/MockApplication.cs index daed3192..c1846663 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/Mocks/MockApplication.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/Mocks/MockApplication.cs @@ -1,4 +1,3 @@ -using Microsoft.Maui.Controls.Internals; using Microsoft.Maui.Handlers; namespace CommunityToolkit.Maui.Markup.UnitTests.Mocks; @@ -9,9 +8,6 @@ public MockApplication(IServiceProvider serviceProvider) Resources = new MockResourceDictionary(); Services = serviceProvider; -#pragma warning disable CS0612 // Type or member is obsolete - DependencyService.Register(); -#pragma warning restore CS0612 // Type or member is obsolete } public IApplication Application => this; diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/Mocks/MockResourcesProvider.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/Mocks/MockResourcesProvider.cs deleted file mode 100644 index 4bfa6d5e..00000000 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/Mocks/MockResourcesProvider.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.Maui.Controls.Internals; -namespace CommunityToolkit.Maui.Markup.UnitTests.Mocks; - - -// Inspired by https://github.com/dotnet/maui/blob/79695fbb7ba6517a334c795ecf0a1d6358ef309a/src/Controls/Foldable/test/MockPlatformServices.cs#L145-L176 - -#pragma warning disable CS0612 // Type or member is obsolete -class MockResourcesProvider : ISystemResourcesProvider -#pragma warning restore CS0612 // Type or member is obsolete -{ - readonly ResourceDictionary dictionary = new(); - - public IResourceDictionary GetSystemResources() => dictionary; -} \ No newline at end of file diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/PaceholderExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/PaceholderExtensionsTests.cs index 6d2b9465..b220f8b3 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/PaceholderExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/PaceholderExtensionsTests.cs @@ -6,15 +6,15 @@ class PaceholderExtensionsTests : BaseMarkupTestFixture { [Test] public void SetPlaceholderTest() - => TestPropertiesSet(e => e.Placeholder("Hello World"), (PlaceholderElement.PlaceholderProperty, "Hello World")); + => TestPropertiesSet(e => e.Placeholder("Hello World"), (Entry.PlaceholderProperty, "Hello World")); [Test] public void SetPlaceholderAndColorTest() - => TestPropertiesSet(e => e.Placeholder("Hello World", Colors.Red), (PlaceholderElement.PlaceholderProperty, "Hello World"), (PlaceholderElement.PlaceholderColorProperty, Colors.Red)); + => TestPropertiesSet(e => e.Placeholder("Hello World", Colors.Red), (Entry.PlaceholderProperty, "Hello World"), (Entry.PlaceholderColorProperty, Colors.Red)); [Test] public void SetPlaceholderColorTest() - => TestPropertiesSet(e => e.PlaceholderColor(Colors.Red), (PlaceholderElement.PlaceholderColorProperty, Colors.Red)); + => TestPropertiesSet(e => e.PlaceholderColor(Colors.Red), (Entry.PlaceholderColorProperty, Colors.Red)); [Test] public void SupportDerivedFromEditor() diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/PaddingElementExtensionsTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/PaddingElementExtensionsTests.cs index 52afb4a3..3e55c0b1 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/PaddingElementExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/PaddingElementExtensionsTests.cs @@ -7,23 +7,23 @@ namespace CommunityToolkit.Maui.Markup.UnitTests; [TestFixture(typeof(ImageButton))] [TestFixture(typeof(Label))] [TestFixture(typeof(Page))] -class PaddingElementExtensionsTests : BaseMarkupTestFixture where TPaddingElement : Element, IPaddingElement, new() +class PaddingElementExtensionsTests : BaseMarkupTestFixture where TPaddingElement : BindableObject, new() { [Test] public void PaddingThickness() - => TestPropertiesSet(l => l.Padding(new Thickness(1)), (PaddingElement.PaddingProperty, new Thickness(0), new Thickness(1))); + => TestPropertiesSet(l => l.Padding(new Thickness(1)), (BindablePropertyHelpers.GetPaddingProperty(Bindable), new Thickness(0), new Thickness(1))); [Test] public void PaddingUniform() - => TestPropertiesSet(l => l.Padding(1), (PaddingElement.PaddingProperty, new Thickness(0), new Thickness(1))); + => TestPropertiesSet(l => l.Padding(1), (BindablePropertyHelpers.GetPaddingProperty(Bindable), new Thickness(0), new Thickness(1))); [Test] public void PaddingHorizontalVertical() - => TestPropertiesSet(l => l.Padding(1, 2), (PaddingElement.PaddingProperty, new Thickness(0), new Thickness(1, 2))); + => TestPropertiesSet(l => l.Padding(1, 2), (BindablePropertyHelpers.GetPaddingProperty(Bindable), new Thickness(0), new Thickness(1, 2))); [Test] public void Paddings() - => TestPropertiesSet(l => l.Paddings(left: 1, top: 2, right: 3, bottom: 4), (PaddingElement.PaddingProperty, new Thickness(0), new Thickness(1, 2, 3, 4))); + => TestPropertiesSet(l => l.Paddings(left: 1, top: 2, right: 3, bottom: 4), (BindablePropertyHelpers.GetPaddingProperty(Bindable), new Thickness(0), new Thickness(1, 2, 3, 4))); [Test] public void SupportDerivedFrom() diff --git a/src/CommunityToolkit.Maui.Markup.UnitTests/TextTests.cs b/src/CommunityToolkit.Maui.Markup.UnitTests/TextTests.cs index e72585eb..7bf6b674 100644 --- a/src/CommunityToolkit.Maui.Markup.UnitTests/TextTests.cs +++ b/src/CommunityToolkit.Maui.Markup.UnitTests/TextTests.cs @@ -6,11 +6,11 @@ class LabelTextTests : BaseMarkupTestFixture