From b1e68c3e0f4de7a35df5948f68e274682c491d64 Mon Sep 17 00:00:00 2001 From: Theofanis kouniakis <62156040+KouniakiosKatakthths@users.noreply.github.com> Date: Wed, 26 Aug 2026 14:26:31 +0000 Subject: [PATCH] Implement ObjectMultiConverters. (#21428) * Implement object multi converters and tests. * Rename null check methods * Remove method AreNotEqual * Move to ObjectConverters * Change logic for AreAnyNull --------- Co-authored-by: Emmanuel Hansen --- .../Data/Converters/ObjectConverters.cs | 50 +++++++++++++ .../Data/ObjectConvertersTests.cs | 71 +++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 tests/Avalonia.Base.UnitTests/Data/ObjectConvertersTests.cs diff --git a/src/Avalonia.Base/Data/Converters/ObjectConverters.cs b/src/Avalonia.Base/Data/Converters/ObjectConverters.cs index 8496ee1ae6..bfec53c3f6 100644 --- a/src/Avalonia.Base/Data/Converters/ObjectConverters.cs +++ b/src/Avalonia.Base/Data/Converters/ObjectConverters.cs @@ -1,3 +1,6 @@ +using System.Linq; +using System.Collections.Generic; + namespace Avalonia.Data.Converters { /// @@ -28,5 +31,52 @@ namespace Avalonia.Data.Converters /// public static readonly IValueConverter NotEqual = new FuncValueConverter((a, b) => !a?.Equals(b) ?? b is not null); + + /// + /// A multi-value converter that returns true if all inputs are null. + /// + public static readonly IMultiValueConverter AreAllNull = + new FuncMultiValueConverter(x => x.All(item => item is null)); + + /// + /// A multi-value converter that returns true if at least one input is null. + /// + public static readonly IMultiValueConverter AreAnyNull = + new FuncMultiValueConverter(x => x.Any(item => item is null)); + + /// + /// A multi-value converter that returns true if all inputs are equal to each other. + /// Null values are not considered equal to anything since they don't have Equals method. + /// + public static readonly IMultiValueConverter AreAllEqual = + new FuncMultiValueConverter(EqualityFunction); + + /// + /// Helper function for AreAllEqual converter. Returns true if all inputs are equal to each other. + /// + /// The values to compare. + /// True if all values are equal, false otherwise. + private static bool EqualityFunction(IEnumerable values) + { + using var enumerator = values.GetEnumerator(); + + //Empty collection is considered equal. + if (!enumerator.MoveNext()) + return true; + + //Null values are not considered equal to anything since they don't have Equals method. + var first = enumerator.Current; + if (first is null) + return false; + + while (enumerator.MoveNext()) + { + var item = enumerator.Current; + if (item is null || !item.Equals(first)) + return false; + } + + return true; + } } } diff --git a/tests/Avalonia.Base.UnitTests/Data/ObjectConvertersTests.cs b/tests/Avalonia.Base.UnitTests/Data/ObjectConvertersTests.cs new file mode 100644 index 0000000000..96848ce2b9 --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/Data/ObjectConvertersTests.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Avalonia.Data.Converters; +using Xunit; + +namespace Avalonia.Base.UnitTests.Data +{ + public class ObjectConvertersTests + { + [Theory] + [InlineData(null, null, null, true)] + [InlineData(null, null, "value", false)] + [InlineData(null, "value", null, false)] + [InlineData("value", null, null, false)] + [InlineData("value", "value", "value", false)] + public void ObjectConvertersTests_AreAllNull_Works(object? value1, object? value2, object? value3, bool valid) + { + var converter = ObjectConverters.AreAllNull; + var result = converter.Convert([value1, value2, value3], typeof(bool), null, CultureInfo.CurrentCulture); + Assert.Equal(valid, Assert.IsType(result)); + } + + [Theory] + [InlineData(null, null, null, true)] + [InlineData(null, null, "value", true)] + [InlineData(null, "value", null, true)] + [InlineData("value", null, null, true)] + [InlineData("value", "value", "value", false)] + public void ObjectConvertersTests_AreAnyNull_Works(object? value1, object? value2, object? value3, bool valid) + { + var converter = ObjectConverters.AreAnyNull; + var result = converter.Convert([value1, value2, value3], typeof(bool), null, CultureInfo.CurrentCulture); + Assert.Equal(valid, Assert.IsType(result)); + } + + [Theory] + [InlineData("value", "value", "value", true)] + [InlineData(null, "value", null, false)] + [InlineData("value", null, "value", false)] + [InlineData("value", "value", "value1", false)] + [InlineData("value1", "value", "value1", false)] + [InlineData("value", "value", 1, false)] + public void ObjectConvertersTests_AreEqual_Works(object? value1, object? value2, object? value3, bool valid) + { + var converter = ObjectConverters.AreAllEqual; + var result = converter.Convert([value1, value2, value3], typeof(bool), null, CultureInfo.CurrentCulture); + Assert.Equal(valid, Assert.IsType(result)); + } + + [Theory] + [InlineData(true, true, true)] + [InlineData(false, true, false)] + [InlineData(false, false, true)] + public void ObjectConvertersTests_AreEqual_Edge_Works(bool empty, bool unique, bool valid) + { + ICollection values; + if (empty) + values = Array.Empty(); + else if (unique) + values = ["1", "2"]; + else + values = ["1", "1"]; + + var converter = ObjectConverters.AreAllEqual; + var result = converter.Convert(values.ToList(), typeof(bool), null, CultureInfo.CurrentCulture); + Assert.Equal(valid, Assert.IsType(result)); + } + } +}