diff --git a/Microsoft.Dotnet.Wpf.sln b/Microsoft.Dotnet.Wpf.sln index 712d90ca334..d79c7150d1c 100644 --- a/Microsoft.Dotnet.Wpf.sln +++ b/Microsoft.Dotnet.Wpf.sln @@ -252,6 +252,7 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PresentationFramework.Fluent", "src\Microsoft.DotNet.Wpf\src\Themes\PresentationFramework.Fluent\PresentationFramework.Fluent.csproj", "{3F2C0E0E-BB13-46D9-8D9A-08256A49ECA9}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PresentationFramework.Fluent-ref", "src\Microsoft.DotNet.Wpf\src\Themes\PresentationFramework.Fluent\ref\PresentationFramework.Fluent-ref.csproj", "{3C43C553-2C1F-4EB9-8BF8-371D4A42E0FD}" +Project("{6FFF8BB3-74D1-4860-BEF1-5B307BCDBB95}") = "WindowsBase.Tests", "src\Microsoft.DotNet.Wpf\tests\UnitTests\WindowsBase.Tests\WindowsBase.Tests.csproj", "{A5DC575D-EC10-445B-94C1-98015F926FFA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1978,6 +1979,28 @@ Global {3C43C553-2C1F-4EB9-8BF8-371D4A42E0FD}.Release|x64.Build.0 = Release|x64 {3C43C553-2C1F-4EB9-8BF8-371D4A42E0FD}.Release|x86.ActiveCfg = Release|Any CPU {3C43C553-2C1F-4EB9-8BF8-371D4A42E0FD}.Release|x86.Build.0 = Release|Any CPU + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Debug|Any CPU.Build.0 = Debug|Win32 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Debug|Any CPU.Deploy.0 = Debug|Win32 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Debug|ARM64.Build.0 = Debug|ARM64 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Debug|x64.ActiveCfg = Debug|x64 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Debug|x64.Build.0 = Debug|x64 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Debug|x64.Deploy.0 = Debug|x64 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Debug|x86.ActiveCfg = Debug|Win32 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Debug|x86.Build.0 = Debug|Win32 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Debug|x86.Deploy.0 = Debug|Win32 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Release|Any CPU.ActiveCfg = Release|Win32 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Release|Any CPU.Build.0 = Release|Win32 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Release|Any CPU.Deploy.0 = Release|Win32 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Release|ARM64.ActiveCfg = Release|ARM64 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Release|ARM64.Build.0 = Release|ARM64 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Release|x64.ActiveCfg = Release|x64 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Release|x64.Build.0 = Release|x64 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Release|x64.Deploy.0 = Release|x64 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Release|x86.ActiveCfg = Release|Win32 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Release|x86.Build.0 = Release|Win32 + {A5DC575D-EC10-445B-94C1-98015F926FFA}.Release|x86.Deploy.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2100,6 +2123,7 @@ Global {B2F2A89C-55C9-41B1-A645-0948609BD8BE} = {A48B585E-6AB0-4F8D-8484-77F37CB44437} {3F2C0E0E-BB13-46D9-8D9A-08256A49ECA9} = {5ACFB055-649D-4A01-98C2-B0BFE7E543D6} {3C43C553-2C1F-4EB9-8BF8-371D4A42E0FD} = {60F4058B-D35B-42D2-B276-D44B3AC579BD} + {A5DC575D-EC10-445B-94C1-98015F926FFA} = {A48B585E-6AB0-4F8D-8484-77F37CB44437} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B4340004-DAC0-497D-B69D-CFA7CD93F567} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/Helpers.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/Helpers.cs new file mode 100644 index 00000000000..3f9646d19ed --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/Helpers.cs @@ -0,0 +1,125 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Specialized; +using System.ComponentModel; +using System.IO; +using System.Runtime.ExceptionServices; +using System.Text; +using System.Threading; +using System.Windows; +using System.Windows.Media; +using Xunit.Sdk; + +public static class Helpers +{ +#pragma warning disable xUnit1013 + public static string GetResourcePath(string name) => Path.GetFullPath(Path.Combine("Resources", name)); + + public static void ExecuteOnDifferentThread(Action action, ApartmentState? state = null) + { + ExceptionDispatchInfo? edi = null; + var t = new Thread(() => + { + try + { + action(); + } + catch (Exception e) + { + edi = ExceptionDispatchInfo.Capture(e); + } + }); + if (state is not null) + { + t.SetApartmentState(state.Value); + } + t.Start(); + t.Join(); + + if (edi is not null) + { + edi.Throw(); + } + } + + public static T ExecuteOnDifferentThread(Func action, ApartmentState? state = null) + { + T? result = default; + ExceptionDispatchInfo? edi = null; + var t = new Thread(() => + { + try + { + result = action(); + } + catch (Exception e) + { + edi = ExceptionDispatchInfo.Capture(e); + } + }); + if (state is not null) + { + t.SetApartmentState(state.Value); + } + t.Start(); + t.Join(); + + if (edi is not null) + { + edi.Throw(); + throw new Exception("Not reachable."); + } + else + { + return result!; + } + } + + public static void AssertEqualRounded(Matrix expected, Matrix actual, int precision = 5) + { + if (expected.Equals(actual)) + { + return; + } + + try + { + Assert.Equal(expected.M11, actual.M11, precision); + Assert.Equal(expected.M12, actual.M12, precision); + Assert.Equal(expected.M21, actual.M21, precision); + Assert.Equal(expected.M22, actual.M22, precision); + Assert.Equal(expected.OffsetX, actual.OffsetX, precision); + Assert.Equal(expected.OffsetY, actual.OffsetY, precision); + } + catch (Exception) + { + // Throw main AssertException with formatting. + //Assert.Equal(expected, actual); + } + } + + public static void AssertEqualRounded(Rect expected, Rect actual, int precision) + { + if (expected.Equals(actual)) + { + return; + } + + try + { + Assert.Equal(expected.X, actual.X, precision); + Assert.Equal(expected.Y, actual.Y, precision); + Assert.Equal(expected.Width, actual.Width, precision); + Assert.Equal(expected.Height, actual.Height, precision); + } + catch (Exception) + { + // Throw main AssertException with formatting. + Assert.Equal(expected, actual); + } + } +#pragma warning restore xUnit1013 +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Collections/Specialized/CollectionChangedEventManagerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Collections/Specialized/CollectionChangedEventManagerTests.cs new file mode 100644 index 00000000000..14782fcea44 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Collections/Specialized/CollectionChangedEventManagerTests.cs @@ -0,0 +1,574 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Windows; + +namespace System.Collections.Specialized.Tests; + +public class CollectionChangedEventManagerTests +{ + public static IEnumerable AddHandler_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset) }; + } + + [Theory] + [MemberData(nameof(AddHandler_TestData))] + public void AddHandler_InvokeWithHandler_CallsCollectionChanged(NotifyCollectionChangedEventArgs e) + { + var source = new CustomNotifyCollectionChanged(); + int callCount1 = 0; + var listener1 = new CustomWeakEventListener(); + listener1.HandlerAction += (actualSender, actualE) => + { + Assert.Same(source, actualSender); + Assert.Same(e, actualE); + callCount1++; + }; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), listener1, nameof(CustomWeakEventListener.Handler)); + int callCount2 = 0; + var listener2 = new CustomWeakEventListener(); + listener2.HandlerAction += (actualSender, actualE) => + { + Assert.Same(source, actualSender); + Assert.Same(e, actualE); + callCount2++; + }; + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), listener2, nameof(CustomWeakEventListener.Handler)); + + // Add. + CollectionChangedEventManager.AddHandler(source, handler1); + + // Call. + source.OnCollectionChanged(source, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Call invalid source. + source.OnCollectionChanged(listener1, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source.OnCollectionChanged(new object(), e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source.OnCollectionChanged(null!, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Add again. + CollectionChangedEventManager.AddHandler(source, handler1); + source.OnCollectionChanged(source, e); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + // Add another. + CollectionChangedEventManager.AddHandler(source, handler2); + source.OnCollectionChanged(source, e); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + + // Remove second. + CollectionChangedEventManager.RemoveHandler(source, handler2); + source.OnCollectionChanged(source, e); + Assert.Equal(7, callCount1); + Assert.Equal(1, callCount2); + + // Remove first. + CollectionChangedEventManager.RemoveHandler(source, handler1); + source.OnCollectionChanged(source, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + + // Remove again. + CollectionChangedEventManager.RemoveHandler(source, handler1); + source.OnCollectionChanged(source, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + } + + [Fact] + public void AddHandler_InvokeMultipleTimes_Success() + { + var source1 = new CustomNotifyCollectionChanged(); + var source2 = new CustomNotifyCollectionChanged(); + var target1 = new CustomWeakEventListener(); + int callCount1 = 0; + target1.HandlerAction += (actualSender, actualE) => callCount1++; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + int callCount2 = 0; + target2.HandlerAction += (actualSender, actualE) => callCount2++; + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + var target3 = new CustomWeakEventListener(); + int callCount3 = 0; + target3.HandlerAction += (actualSender, actualE) => callCount3++; + EventHandler handler3 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target3, nameof(CustomWeakEventListener.Handler)); + + // Add. + CollectionChangedEventManager.AddHandler(source1, handler1); + source1.OnCollectionChanged(source1, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + source2.OnCollectionChanged(source2, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + + // Add again. + CollectionChangedEventManager.AddHandler(source1, handler1); + source1.OnCollectionChanged(source1, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + source2.OnCollectionChanged(source2, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + + // Add another handler. + CollectionChangedEventManager.AddHandler(source1, handler2); + source1.OnCollectionChanged(source1, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + source2.OnCollectionChanged(source2, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + + // Add another source. + CollectionChangedEventManager.AddHandler(source2, handler3); + source1.OnCollectionChanged(source1, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(7, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(0, callCount3); + source2.OnCollectionChanged(source2, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(7, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(1, callCount3); + } + + [Fact] + public void AddHandler_InvokeNoSource_Success() + { + var target1 = new CustomWeakEventListener(); + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + + // Add. + Assert.Throws(() => CollectionChangedEventManager.AddHandler(null, handler1)); + + // Add again. + CollectionChangedEventManager.AddHandler(null, handler1); + + // Add another. + CollectionChangedEventManager.AddHandler(null, handler2); + } + + [Fact] + public void AddHandler_NullHandler_ThrowsArgumentNullException() + { + var source = new CustomNotifyCollectionChanged(); + Assert.Throws("handler", () => CollectionChangedEventManager.AddHandler(null, null)); + Assert.Throws("handler", () => CollectionChangedEventManager.AddHandler(source, null)); + } + + public static IEnumerable AddListener_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset) }; + } + + [Theory] + [MemberData(nameof(AddListener_TestData))] + public void AddListener_InvokeWithHandler_CallsCollectionChanged(NotifyCollectionChangedEventArgs e) + { + var source = new CustomNotifyCollectionChanged(); + int callCount1 = 0; + var listener1 = new CustomWeakEventListener(); + listener1.ReceiveWeakEventAction += (actualManagerType, actualSender, actualE) => + { + Assert.Equal(typeof(CollectionChangedEventManager), actualManagerType); + Assert.Same(source, actualSender); + Assert.Same(e, actualE); + callCount1++; + return true; + }; + int callCount2 = 0; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction += (actualManagerType, actualSender, actualE) => + { + Assert.Equal(typeof(CollectionChangedEventManager), actualManagerType); + Assert.Same(source, actualSender); + Assert.Same(e, actualE); + callCount2++; + return true; + }; + + // Add. + CollectionChangedEventManager.AddListener(source, listener1); + + // Call. + source.OnCollectionChanged(source, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Call invalid source. + source.OnCollectionChanged(listener1, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source.OnCollectionChanged(new object(), e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source.OnCollectionChanged(null!, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Add again. + CollectionChangedEventManager.AddListener(source, listener1); + source.OnCollectionChanged(source, e); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + // Add another. + CollectionChangedEventManager.AddListener(source, listener2); + source.OnCollectionChanged(source, e); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + + // Remove second. + CollectionChangedEventManager.RemoveListener(source, listener2); + source.OnCollectionChanged(source, e); + Assert.Equal(7, callCount1); + Assert.Equal(1, callCount2); + + // Remove first. + CollectionChangedEventManager.RemoveListener(source, listener1); + source.OnCollectionChanged(source, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + + // Remove again. + CollectionChangedEventManager.RemoveListener(source, listener1); + source.OnCollectionChanged(source, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + } + + [Fact] + public void AddListener_InvokeMultipleTimes_Success() + { + var source1 = new CustomNotifyCollectionChanged(); + var source2 = new CustomNotifyCollectionChanged(); + var listener1 = new CustomWeakEventListener(); + int callCount1 = 0; + listener1.ReceiveWeakEventAction += (t, s, e) => + { + callCount1++; + return true; + }; + var listener2 = new CustomWeakEventListener(); + int callCount2 = 0; + listener2.ReceiveWeakEventAction += (t, s, e) => + { + callCount2++; + return true; + }; + var listener3 = new CustomWeakEventListener(); + int callCount3 = 0; + listener3.ReceiveWeakEventAction += (t, s, e) => + { + callCount3++; + return true; + }; + + // Add. + CollectionChangedEventManager.AddListener(source1, listener1); + source1.OnCollectionChanged(source1, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + source2.OnCollectionChanged(source2, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + + // Add again. + CollectionChangedEventManager.AddListener(source1, listener1); + source1.OnCollectionChanged(source1, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + source2.OnCollectionChanged(source2, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + + // Add another listener. + CollectionChangedEventManager.AddListener(source1, listener2); + source1.OnCollectionChanged(source1, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + source2.OnCollectionChanged(source2, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + + // Add another source. + CollectionChangedEventManager.AddListener(source2, listener3); + source1.OnCollectionChanged(source1, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(7, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(0, callCount3); + source2.OnCollectionChanged(source2, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(7, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(1, callCount3); + } + + [Fact] + public void AddListener_NullSource_ThrowsArgumentNullException() + { + var listener = new CustomWeakEventListener(); + Assert.Throws("source", () => CollectionChangedEventManager.AddListener(null, listener)); + } + + [Fact] + public void AddListener_NullListener_ThrowsArgumentNullException() + { + var source = new CustomNotifyCollectionChanged(); + Assert.Throws("listener", () => CollectionChangedEventManager.AddListener(source, null)); + } + + [Fact] + public void RemoveHandler_Invoke_Success() + { + var source = new CustomNotifyCollectionChanged(); + var target = new CustomWeakEventListener(); + int callCount = 0; + target.HandlerAction += (actualSender, actualE) => callCount++; + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + CollectionChangedEventManager.AddHandler(source, handler); + + // Remove. + CollectionChangedEventManager.RemoveHandler(source, handler); + source.OnCollectionChanged(source, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(0, callCount); + + // Remove again. + CollectionChangedEventManager.RemoveHandler(source, handler); + source.OnCollectionChanged(source, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(0, callCount); + } + + [Fact] + public void RemoveHandler_InvokeNoSource_Success() + { + var target = new CustomWeakEventListener(); + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + CollectionChangedEventManager.AddHandler(null, handler); + + // Remove. + CollectionChangedEventManager.RemoveHandler(null, handler); + + // Remove again. + CollectionChangedEventManager.RemoveHandler(null, handler); + } + + [Fact] + public void RemoveHandler_InvokeNoSuchSource_Nop() + { + var source1 = new CustomNotifyCollectionChanged(); + var source2 = new CustomNotifyCollectionChanged(); + var target = new CustomWeakEventListener(); + int callCount = 0; + target.HandlerAction += (actualSender, actualE) => callCount++; + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + CollectionChangedEventManager.AddHandler(source1, handler); + + // Remove. + CollectionChangedEventManager.RemoveHandler(source2, handler); + source1.OnCollectionChanged(source1, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(1, callCount); + source2.OnCollectionChanged(source2, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(1, callCount); + + // Remove again. + CollectionChangedEventManager.RemoveHandler(source2, handler); + source1.OnCollectionChanged(source1, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(2, callCount); + source2.OnCollectionChanged(source2, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(2, callCount); + } + + [Fact] + public void RemoveHandler_InvokeNoSuchHandler_Nop() + { + var source = new CustomNotifyCollectionChanged(); + var target1 = new CustomWeakEventListener(); + int callCount1 = 0; + target1.HandlerAction += (actualSender, actualE) => callCount1++; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + int callCount2 = 0; + target2.HandlerAction += (actualSender, actualE) => callCount2++; + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + CollectionChangedEventManager.AddHandler(source, handler1); + + // Remove. + CollectionChangedEventManager.RemoveHandler(source, handler2); + source.OnCollectionChanged(source, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Remove again. + CollectionChangedEventManager.RemoveHandler(source, handler2); + source.OnCollectionChanged(source, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(2, callCount1); + Assert.Equal(0, callCount2); + } + + [Fact] + public void RemoveHandler_NullHandler_ThrowsArgumentNullException() + { + var source = new CustomNotifyCollectionChanged(); + Assert.Throws("handler", () => CollectionChangedEventManager.RemoveHandler(null, null)); + Assert.Throws("handler", () => CollectionChangedEventManager.RemoveHandler(source, null)); + } + + [Fact] + public void RemoveListener_Invoke_Success() + { + var source = new CustomNotifyCollectionChanged(); + var listener = new CustomWeakEventListener(); + int callCount = 0; + listener.ReceiveWeakEventAction += (t, s, e) => + { + callCount++; + return true; + }; + CollectionChangedEventManager.AddListener(source, listener); + + // Remove. + CollectionChangedEventManager.RemoveListener(source, listener); + source.OnCollectionChanged(source, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(0, callCount); + + // Remove again. + CollectionChangedEventManager.RemoveListener(source, listener); + source.OnCollectionChanged(source, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(0, callCount); + } + + [Fact] + public void RemoveListener_InvokeNoSuchSource_Nop() + { + var source1 = new CustomNotifyCollectionChanged(); + var source2 = new CustomNotifyCollectionChanged(); + var listener = new CustomWeakEventListener(); + int callCount = 0; + listener.ReceiveWeakEventAction += (t, s, e) => + { + callCount++; + return true; + }; + CollectionChangedEventManager.AddListener(source1, listener); + + // Remove. + CollectionChangedEventManager.RemoveListener(source2, listener); + source1.OnCollectionChanged(source1, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(1, callCount); + source2.OnCollectionChanged(source2, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(1, callCount); + + // Remove again. + CollectionChangedEventManager.RemoveListener(source2, listener); + source1.OnCollectionChanged(source1, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(2, callCount); + source2.OnCollectionChanged(source2, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(2, callCount); + } + + [Fact] + public void RemoveListener_InvokeNoSuchListener_Nop() + { + var source = new CustomNotifyCollectionChanged(); + var listener1 = new CustomWeakEventListener(); + int callCount1 = 0; + listener1.ReceiveWeakEventAction += (t, s, e) => + { + callCount1++; + return true; + }; + var listener2 = new CustomWeakEventListener(); + int callCount2 = 0; + listener2.ReceiveWeakEventAction += (t, s, e) => + { + callCount2++; + return true; + }; + CollectionChangedEventManager.AddListener(source, listener1); + + // Remove. + CollectionChangedEventManager.RemoveListener(source, listener2); + source.OnCollectionChanged(source, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Remove again. + CollectionChangedEventManager.RemoveListener(source, listener2); + source.OnCollectionChanged(source, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + Assert.Equal(2, callCount1); + Assert.Equal(0, callCount2); + } + + [Fact] + public void RemoveListener_NullListener_ThrowsArgumentNullException() + { + var source = new CustomNotifyCollectionChanged(); + Assert.Throws("listener", () => CollectionChangedEventManager.RemoveListener(source, null)); + } + + private class CustomNotifyCollectionChanged : INotifyCollectionChanged + { + #pragma warning disable CS0067 + public event NotifyCollectionChangedEventHandler? CollectionChanged; + #pragma warning restore CS0067 + + public void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + CollectionChanged?.Invoke(sender, e); + } + } + + private class CustomWeakEventListener : IWeakEventListener + { + public Func? ReceiveWeakEventAction { get; set; } + + public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (ReceiveWeakEventAction is null) + { + return true; + } + + return ReceiveWeakEventAction(managerType, sender, e); + } + + public Action? HandlerAction { get; set; } + + public void Handler(object sender, NotifyCollectionChangedEventArgs e) + { + HandlerAction?.Invoke(sender, e); + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/CurrentChangedEventManagerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/CurrentChangedEventManagerTests.cs new file mode 100644 index 00000000000..751f7b6d389 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/CurrentChangedEventManagerTests.cs @@ -0,0 +1,633 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Globalization; +using System.Windows; + +namespace System.ComponentModel.Tests; + +public class CurrentChangedEventManagerTests +{ + public static IEnumerable AddHandler_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new EventArgs() }; + yield return new object?[] { EventArgs.Empty }; + } + + [Theory] + [MemberData(nameof(AddHandler_TestData))] + public void AddHandler_InvokeWithHandler_CallsCurrentChanged(EventArgs e) + { + var source = new CustomCollectionView(); + int callCount1 = 0; + var listener1 = new CustomWeakEventListener(); + listener1.HandlerAction += (actualSender, actualE) => + { + Assert.Same(source, actualSender); + Assert.Same(e, actualE); + callCount1++; + }; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), listener1, nameof(CustomWeakEventListener.Handler)); + int callCount2 = 0; + var listener2 = new CustomWeakEventListener(); + listener2.HandlerAction += (actualSender, actualE) => + { + Assert.Same(source, actualSender); + Assert.Same(e, actualE); + callCount2++; + }; + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), listener2, nameof(CustomWeakEventListener.Handler)); + + // Add. + CurrentChangedEventManager.AddHandler(source, handler1); + + // Call. + source.OnCurrentChanged(source, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Call invalid source. + source.OnCurrentChanged(listener1, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source.OnCurrentChanged(new object(), e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source.OnCurrentChanged(null!, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Add again. + CurrentChangedEventManager.AddHandler(source, handler1); + source.OnCurrentChanged(source, e); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + // Add another. + CurrentChangedEventManager.AddHandler(source, handler2); + source.OnCurrentChanged(source, e); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + + // Remove second. + CurrentChangedEventManager.RemoveHandler(source, handler2); + source.OnCurrentChanged(source, e); + Assert.Equal(7, callCount1); + Assert.Equal(1, callCount2); + + // Remove first. + CurrentChangedEventManager.RemoveHandler(source, handler1); + source.OnCurrentChanged(source, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + + // Remove again. + CurrentChangedEventManager.RemoveHandler(source, handler1); + source.OnCurrentChanged(source, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + } + + [Fact] + public void AddHandler_Invoke_Success() + { + var source1 = new CustomCollectionView(); + var source2 = new CustomCollectionView(); + var target1 = new CustomWeakEventListener(); + int callCount1 = 0; + target1.HandlerAction += (sender, e) => callCount1++; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + int callCount2 = 0; + target2.HandlerAction += (sender, e) => callCount2++; + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + int callCount3 = 0; + var target3 = new CustomWeakEventListener(); + target3.HandlerAction += (sender, e) => callCount3++; + EventHandler handler3 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target3, nameof(CustomWeakEventListener.Handler)); + + // Add. + CurrentChangedEventManager.AddHandler(source1, handler1); + source1.OnCurrentChanged(source1, EventArgs.Empty); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanged(source2, EventArgs.Empty); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + + // Add again. + CurrentChangedEventManager.AddHandler(source1, handler1); + source1.OnCurrentChanged(source1, EventArgs.Empty); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanged(source2, EventArgs.Empty); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + + // Add another handler. + CurrentChangedEventManager.AddHandler(source1, handler2); + source1.OnCurrentChanged(source1, EventArgs.Empty); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanged(source2, EventArgs.Empty); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + + // Add another source. + CurrentChangedEventManager.AddHandler(source2, handler3); + source1.OnCurrentChanged(source1, EventArgs.Empty); + Assert.Equal(7, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanged(source2, EventArgs.Empty); + Assert.Equal(7, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(1, callCount3); + } + + [Fact] + public void AddHandler_InvokeNoSource_Success() + { + var target1 = new CustomWeakEventListener(); + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + + // Add. + Assert.Throws(() => CurrentChangedEventManager.AddHandler(null, handler1)); + + // Add again. + CurrentChangedEventManager.AddHandler(null, handler1); + + // Add another. + CurrentChangedEventManager.AddHandler(null, handler2); + } + + [Fact] + public void AddHandler_NullHandler_ThrowsArgumentNullException() + { + var source = new CustomCollectionView(); + Assert.Throws("handler", () => CurrentChangedEventManager.AddHandler(null, null)); + Assert.Throws("handler", () => CurrentChangedEventManager.AddHandler(source, null)); + } + + public static IEnumerable AddListener_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset) }; + } + + [Theory] + [MemberData(nameof(AddListener_TestData))] + public void AddListener_InvokeWithHandler_CallsCurrentChanged(NotifyCollectionChangedEventArgs e) + { + var source = new CustomCollectionView(); + int callCount1 = 0; + var listener1 = new CustomWeakEventListener(); + listener1.ReceiveWeakEventAction += (actualManagerType, actualSender, actualE) => + { + Assert.Equal(typeof(CurrentChangedEventManager), actualManagerType); + Assert.Same(source, actualSender); + Assert.Same(e, actualE); + callCount1++; + return true; + }; + int callCount2 = 0; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction += (actualManagerType, actualSender, actualE) => + { + Assert.Equal(typeof(CurrentChangedEventManager), actualManagerType); + Assert.Same(source, actualSender); + Assert.Same(e, actualE); + callCount2++; + return true; + }; + + // Add. + CurrentChangedEventManager.AddListener(source, listener1); + + // Call. + source.OnCurrentChanged(source, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Call invalid source. + source.OnCurrentChanged(listener1, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source.OnCurrentChanged(new object(), e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source.OnCurrentChanged(null!, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Add again. + CurrentChangedEventManager.AddListener(source, listener1); + source.OnCurrentChanged(source, e); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + // Add another. + CurrentChangedEventManager.AddListener(source, listener2); + source.OnCurrentChanged(source, e); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + + // Remove second. + CurrentChangedEventManager.RemoveListener(source, listener2); + source.OnCurrentChanged(source, e); + Assert.Equal(7, callCount1); + Assert.Equal(1, callCount2); + + // Remove first. + CurrentChangedEventManager.RemoveListener(source, listener1); + source.OnCurrentChanged(source, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + + // Remove again. + CurrentChangedEventManager.RemoveListener(source, listener1); + source.OnCurrentChanged(source, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + } + + [Fact] + public void AddListener_InvokeMultipleTimes_Success() + { + var source1 = new CustomCollectionView(); + var source2 = new CustomCollectionView(); + var listener1 = new CustomWeakEventListener(); + int callCount1 = 0; + listener1.ReceiveWeakEventAction += (managerType, sender, e) => + { + callCount1++; + return true; + }; + var listener2 = new CustomWeakEventListener(); + int callCount2 = 0; + listener2.ReceiveWeakEventAction += (managerType, sender, e) => + { + callCount2++; + return true; + }; + var listener3 = new CustomWeakEventListener(); + int callCount3 = 0; + listener3.ReceiveWeakEventAction += (managerType, sender, e) => + { + callCount3++; + return true; + }; + + // Add. + CurrentChangedEventManager.AddListener(source1, listener1); + source1.OnCurrentChanged(source1, EventArgs.Empty); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanged(source2, EventArgs.Empty); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + + // Add again. + CurrentChangedEventManager.AddListener(source1, listener1); + source1.OnCurrentChanged(source1, EventArgs.Empty); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanged(source2, EventArgs.Empty); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + + // Add another listener. + CurrentChangedEventManager.AddListener(source1, listener2); + source1.OnCurrentChanged(source1, EventArgs.Empty); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanged(source2, EventArgs.Empty); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + + // Add another source. + CurrentChangedEventManager.AddListener(source2, listener3); + source1.OnCurrentChanged(source1, EventArgs.Empty); + Assert.Equal(7, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanged(source2, EventArgs.Empty); + Assert.Equal(7, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(1, callCount3); + } + + [Fact] + public void AddListener_NullSource_ThrowsArgumentNullException() + { + var listener = new CustomWeakEventListener(); + Assert.Throws("source", () => CurrentChangedEventManager.AddListener(null, listener)); + } + + [Fact] + public void AddListener_NullListener_ThrowsArgumentNullException() + { + var source = new CustomCollectionView(); + Assert.Throws("listener", () => CurrentChangedEventManager.AddListener(source, null)); + } + + [Fact] + public void RemoveHandler_Invoke_Success() + { + var source = new CustomCollectionView(); + var target = new CustomWeakEventListener(); + int callCount = 0; + target.HandlerAction += (sender, e) => callCount++; + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + CurrentChangedEventManager.AddHandler(source, handler); + + // Remove. + CurrentChangedEventManager.RemoveHandler(source, handler); + source.OnCurrentChanged(source, EventArgs.Empty); + Assert.Equal(0, callCount); + + // Remove again. + CurrentChangedEventManager.RemoveHandler(source, handler); + source.OnCurrentChanged(source, EventArgs.Empty); + Assert.Equal(0, callCount); + } + + [Fact] + public void RemoveHandler_InvokeNoSource_Success() + { + var target = new CustomWeakEventListener(); + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + CurrentChangedEventManager.AddHandler(null, handler); + + // Remove. + CurrentChangedEventManager.RemoveHandler(null, handler); + + // Remove again. + CurrentChangedEventManager.RemoveHandler(null, handler); + } + + [Fact] + public void RemoveHandler_InvokeNoSuchSource_Nop() + { + var source1 = new CustomCollectionView(); + var source2 = new CustomCollectionView(); + var target = new CustomWeakEventListener(); + int callCount = 0; + target.HandlerAction += (sender, e) => callCount++; + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + CurrentChangedEventManager.AddHandler(source1, handler); + + // Remove. + CurrentChangedEventManager.RemoveHandler(source2, handler); + source1.OnCurrentChanged(source1, EventArgs.Empty); + Assert.Equal(1, callCount); + source2.OnCurrentChanged(source2, EventArgs.Empty); + Assert.Equal(1, callCount); + + // Remove again. + CurrentChangedEventManager.RemoveHandler(source2, handler); + source1.OnCurrentChanged(source1, EventArgs.Empty); + Assert.Equal(2, callCount); + source2.OnCurrentChanged(source2, EventArgs.Empty); + Assert.Equal(2, callCount); + } + + [Fact] + public void RemoveHandler_InvokeNoSuchHandler_Nop() + { + var source = new CustomCollectionView(); + var target1 = new CustomWeakEventListener(); + int callCount1 = 0; + target1.HandlerAction += (sender, e) => callCount1++; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + int callCount2 = 0; + target2.HandlerAction += (sender, e) => callCount2++; + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + CurrentChangedEventManager.AddHandler(source, handler1); + + // Remove. + CurrentChangedEventManager.RemoveHandler(source, handler2); + source.OnCurrentChanged(source, EventArgs.Empty); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Remove again. + CurrentChangedEventManager.RemoveHandler(source, handler2); + source.OnCurrentChanged(source, EventArgs.Empty); + Assert.Equal(2, callCount1); + Assert.Equal(0, callCount2); + } + + [Fact] + public void RemoveHandler_NullHandler_ThrowsArgumentNullException() + { + var source = new CustomCollectionView(); + Assert.Throws("handler", () => CurrentChangedEventManager.RemoveHandler(null, null)); + Assert.Throws("handler", () => CurrentChangedEventManager.RemoveHandler(source, null)); + } + + [Fact] + public void RemoveListener_Invoke_Success() + { + var source = new CustomCollectionView(); + var listener = new CustomWeakEventListener(); + int callCount = 0; + listener.ReceiveWeakEventAction += (managerType, sender, e) => + { + callCount++; + return true; + }; + CurrentChangedEventManager.AddListener(source, listener); + + // Remove. + CurrentChangedEventManager.RemoveListener(source, listener); + source.OnCurrentChanged(source, EventArgs.Empty); + Assert.Equal(0, callCount); + + // Remove again. + CurrentChangedEventManager.RemoveListener(source, listener); + source.OnCurrentChanged(source, EventArgs.Empty); + Assert.Equal(0, callCount); + } + + [Fact] + public void RemoveListener_NoSuchSource_Success() + { + var source1 = new CustomCollectionView(); + var source2 = new CustomCollectionView(); + var listener = new CustomWeakEventListener(); + int callCount = 0; + listener.ReceiveWeakEventAction += (managerType, sender, e) => + { + callCount++; + return true; + }; + CurrentChangedEventManager.AddListener(source1, listener); + + // Remove. + CurrentChangedEventManager.RemoveListener(source2, listener); + source1.OnCurrentChanged(source1, EventArgs.Empty); + Assert.Equal(1, callCount); + source2.OnCurrentChanged(source2, EventArgs.Empty); + Assert.Equal(1, callCount); + + // Remove again. + CurrentChangedEventManager.RemoveListener(source2, listener); + source1.OnCurrentChanged(source1, EventArgs.Empty); + Assert.Equal(2, callCount); + source2.OnCurrentChanged(source2, EventArgs.Empty); + Assert.Equal(2, callCount); + } + + [Fact] + public void RemoveListener_InvokeNoSuchListener_Nop() + { + var source = new CustomCollectionView(); + var listener1 = new CustomWeakEventListener(); + int callCount1 = 0; + listener1.ReceiveWeakEventAction += (managerType, sender, e) => + { + callCount1++; + return true; + }; + var listener2 = new CustomWeakEventListener(); + int callCount2 = 0; + listener2.ReceiveWeakEventAction += (managerType, sender, e) => + { + callCount2++; + return true; + }; + CurrentChangedEventManager.AddListener(source, listener1); + + // Remove. + CurrentChangedEventManager.RemoveListener(source, listener2); + source.OnCurrentChanged(source, EventArgs.Empty); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Remove again. + CurrentChangedEventManager.RemoveListener(source, listener2); + source.OnCurrentChanged(source, EventArgs.Empty); + Assert.Equal(2, callCount1); + } + + [Fact] + public void RemoveListener_NullListener_ThrowsArgumentNullException() + { + var source = new CustomCollectionView(); + Assert.Throws("listener", () => CurrentChangedEventManager.RemoveListener(source, null)); + } + + private class CustomCollectionView : ICollectionView + { + public bool CanFilter => throw new NotImplementedException(); + + public bool CanGroup => throw new NotImplementedException(); + + public bool CanSort => throw new NotImplementedException(); + + public CultureInfo Culture + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public object CurrentItem => throw new NotImplementedException(); + + public int CurrentPosition => throw new NotImplementedException(); + + public Predicate Filter + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public ObservableCollection GroupDescriptions => throw new NotImplementedException(); + + public ReadOnlyObservableCollection Groups => throw new NotImplementedException(); + + public bool IsCurrentAfterLast => throw new NotImplementedException(); + + public bool IsCurrentBeforeFirst => throw new NotImplementedException(); + + public bool IsEmpty => throw new NotImplementedException(); + + public SortDescriptionCollection SortDescriptions => throw new NotImplementedException(); + + public IEnumerable SourceCollection => throw new NotImplementedException(); + + #pragma warning disable CS0067 + public event EventHandler? CurrentChanged; + public event CurrentChangingEventHandler? CurrentChanging; + public event NotifyCollectionChangedEventHandler? CollectionChanged; + #pragma warning restore CS0067 + + public bool Contains(object item) => throw new NotImplementedException(); + + public IDisposable DeferRefresh() => throw new NotImplementedException(); + + public IEnumerator GetEnumerator() => throw new NotImplementedException(); + + public bool MoveCurrentTo(object item) => throw new NotImplementedException(); + + public bool MoveCurrentToFirst() => throw new NotImplementedException(); + + public bool MoveCurrentToLast() => throw new NotImplementedException(); + + public bool MoveCurrentToNext() => throw new NotImplementedException(); + + public bool MoveCurrentToPosition(int position) => throw new NotImplementedException(); + + public bool MoveCurrentToPrevious() => throw new NotImplementedException(); + + public void Refresh() => throw new NotImplementedException(); + + public void OnCurrentChanged(object sender, EventArgs e) => CurrentChanged?.Invoke(sender, e); + } + + private class CustomWeakEventListener : IWeakEventListener + { + public Func? ReceiveWeakEventAction { get; set; } + + public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (ReceiveWeakEventAction is null) + { + return true; + } + + return ReceiveWeakEventAction(managerType, sender, e); + } + + public Action? HandlerAction { get; set; } + + public void Handler(object sender, EventArgs e) + { + HandlerAction?.Invoke(sender, e); + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/CurrentChangingEventArgsTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/CurrentChangingEventArgsTests.cs new file mode 100644 index 00000000000..9ed2ed10c9e --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/CurrentChangingEventArgsTests.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel.Tests; + +public class CurrentChangingEventArgsTests +{ + [Fact] + public void Ctor_Default() + { + var args = new CurrentChangingEventArgs(); + Assert.True(args.IsCancelable); + Assert.False(args.Cancel); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Ctor_Bool(bool isCancelable) + { + var args = new CurrentChangingEventArgs(isCancelable); + Assert.Equal(isCancelable, args.IsCancelable); + Assert.False(args.Cancel); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Cancel_Set_GetReturnsExpected(bool value) + { + var args = new CurrentChangingEventArgs(); + + // Set. + args.Cancel = value; + Assert.Equal(value, args.Cancel); + + // Set same. + args.Cancel = value; + Assert.Equal(value, args.Cancel); + + // Set different. + args.Cancel = !value; + Assert.Equal(!value, args.Cancel); + } + + [Fact] + public void Cancel_SetFalseNotCancellable_Success() + { + var args = new CurrentChangingEventArgs(false); + + // Set. + args.Cancel = false; + Assert.False(args.Cancel); + } + + [Fact] + public void Cancel_SetTrueNotCancellable_ThrowsInvalidOperationException() + { + var args = new CurrentChangingEventArgs(false); + Assert.Throws(() => args.Cancel = true); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/CurrentChangingEventManagerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/CurrentChangingEventManagerTests.cs new file mode 100644 index 00000000000..f6daebc9528 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/CurrentChangingEventManagerTests.cs @@ -0,0 +1,631 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Globalization; +using System.Windows; + +namespace System.ComponentModel.Tests; + +public class CurrentChangingEventManagerTests +{ + public static IEnumerable AddHandler_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new CurrentChangingEventArgs(false) }; + } + + [Theory] + [MemberData(nameof(AddHandler_TestData))] + public void AddHandler_InvokeWithHandler_CallsCurrentChanging(CurrentChangingEventArgs e) + { + var source = new CustomCollectionView(); + int callCount1 = 0; + var listener1 = new CustomWeakEventListener(); + listener1.HandlerAction += (actualSender, actualE) => + { + Assert.Same(source, actualSender); + Assert.Same(e, actualE); + callCount1++; + }; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), listener1, nameof(CustomWeakEventListener.Handler)); + int callCount2 = 0; + var listener2 = new CustomWeakEventListener(); + listener2.HandlerAction += (actualSender, actualE) => + { + Assert.Same(source, actualSender); + Assert.Same(e, actualE); + callCount2++; + }; + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), listener2, nameof(CustomWeakEventListener.Handler)); + + // Add. + CurrentChangingEventManager.AddHandler(source, handler1); + + // Call. + source.OnCurrentChanging(source, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Call invalid source. + source.OnCurrentChanging(listener1, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source.OnCurrentChanging(new object(), e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source.OnCurrentChanging(null!, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Add again. + CurrentChangingEventManager.AddHandler(source, handler1); + source.OnCurrentChanging(source, e); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + // Add another. + CurrentChangingEventManager.AddHandler(source, handler2); + source.OnCurrentChanging(source, e); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + + // Remove second. + CurrentChangingEventManager.RemoveHandler(source, handler2); + source.OnCurrentChanging(source, e); + Assert.Equal(7, callCount1); + Assert.Equal(1, callCount2); + + // Remove first. + CurrentChangingEventManager.RemoveHandler(source, handler1); + source.OnCurrentChanging(source, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + + // Remove again. + CurrentChangingEventManager.RemoveHandler(source, handler1); + source.OnCurrentChanging(source, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + } + + [Fact] + public void AddHandler_InvokeMultipleTimes_Success() + { + var source1 = new CustomCollectionView(); + var source2 = new CustomCollectionView(); + var target1 = new CustomWeakEventListener(); + int callCount1 = 0; + target1.HandlerAction += (s, e) => callCount1++; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + int callCount2 = 0; + target2.HandlerAction += (s, e) => callCount2++; + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + var target3 = new CustomWeakEventListener(); + int callCount3 = 0; + target3.HandlerAction += (s, e) => callCount3++; + EventHandler handler3 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target3, nameof(CustomWeakEventListener.Handler)); + + // Add. + CurrentChangingEventManager.AddHandler(source1, handler1); + source1.OnCurrentChanging(source1, new CurrentChangingEventArgs(false)); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanging(source2, new CurrentChangingEventArgs(false)); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + + // Add again. + CurrentChangingEventManager.AddHandler(source1, handler1); + source1.OnCurrentChanging(source1, new CurrentChangingEventArgs(false)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanging(source2, new CurrentChangingEventArgs(false)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + + // Add another handler. + CurrentChangingEventManager.AddHandler(source1, handler2); + source1.OnCurrentChanging(source1, new CurrentChangingEventArgs(false)); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanging(source2, new CurrentChangingEventArgs(false)); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + + // Add another source. + CurrentChangingEventManager.AddHandler(source2, handler3); + source1.OnCurrentChanging(source1, new CurrentChangingEventArgs(false)); + Assert.Equal(7, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanging(source2, new CurrentChangingEventArgs(false)); + Assert.Equal(7, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(1, callCount3); + } + + [Fact] + public void AddHandler_InvokeNoSource_Success() + { + var target1 = new CustomWeakEventListener(); + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + + // Add. + Assert.Throws(() => CurrentChangingEventManager.AddHandler(null, handler1)); + + // Add again. + CurrentChangingEventManager.AddHandler(null, handler1); + + // Add another. + CurrentChangingEventManager.AddHandler(null, handler2); + } + + [Fact] + public void AddHandler_NullHandler_ThrowsArgumentNullException() + { + var source = new CustomCollectionView(); + Assert.Throws("handler", () => CurrentChangingEventManager.AddHandler(null, null)); + Assert.Throws("handler", () => CurrentChangingEventManager.AddHandler(source, null)); + } + + public static IEnumerable AddListener_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new CurrentChangingEventArgs(false) }; + } + + [Theory] + [MemberData(nameof(AddListener_TestData))] + public void AddListener_InvokeWithHandler_CallsCurrentChanging(CurrentChangingEventArgs e) + { + var source = new CustomCollectionView(); + int callCount1 = 0; + var listener1 = new CustomWeakEventListener(); + listener1.ReceiveWeakEventAction += (actualManagerType, actualSender, actualE) => + { + Assert.Equal(typeof(CurrentChangingEventManager), actualManagerType); + Assert.Same(source, actualSender); + Assert.Same(e, actualE); + callCount1++; + return true; + }; + int callCount2 = 0; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction += (actualManagerType, actualSender, actualE) => + { + Assert.Equal(typeof(CurrentChangingEventManager), actualManagerType); + Assert.Same(source, actualSender); + Assert.Same(e, actualE); + callCount2++; + return true; + }; + + // Add. + CurrentChangingEventManager.AddListener(source, listener1); + + // Call. + source.OnCurrentChanging(source, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Call invalid source. + source.OnCurrentChanging(listener1, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source.OnCurrentChanging(new object(), e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source.OnCurrentChanging(null!, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Add again. + CurrentChangingEventManager.AddListener(source, listener1); + source.OnCurrentChanging(source, e); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + // Add another. + CurrentChangingEventManager.AddListener(source, listener2); + source.OnCurrentChanging(source, e); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + + // Remove second. + CurrentChangingEventManager.RemoveListener(source, listener2); + source.OnCurrentChanging(source, e); + Assert.Equal(7, callCount1); + Assert.Equal(1, callCount2); + + // Remove first. + CurrentChangingEventManager.RemoveListener(source, listener1); + source.OnCurrentChanging(source, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + + // Remove again. + CurrentChangingEventManager.RemoveListener(source, listener1); + source.OnCurrentChanging(source, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + } + + [Fact] + public void AddListener_InvokeMultipleTimes_Success() + { + var source1 = new CustomCollectionView(); + var source2 = new CustomCollectionView(); + var listener1 = new CustomWeakEventListener(); + int callCount1 = 0; + listener1.ReceiveWeakEventAction += (t, s, e) => + { + callCount1++; + return true; + }; + var listener2 = new CustomWeakEventListener(); + int callCount2 = 0; + listener2.ReceiveWeakEventAction += (t, s, e) => + { + callCount2++; + return true; + }; + var listener3 = new CustomWeakEventListener(); + int callCount3 = 0; + listener3.ReceiveWeakEventAction += (t, s, e) => + { + callCount3++; + return true; + }; + + // Add. + CurrentChangingEventManager.AddListener(source1, listener1); + source1.OnCurrentChanging(source1, new CurrentChangingEventArgs(false)); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanging(source2, new CurrentChangingEventArgs(false)); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + + // Add again. + CurrentChangingEventManager.AddListener(source1, listener1); + source1.OnCurrentChanging(source1, new CurrentChangingEventArgs(false)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanging(source2, new CurrentChangingEventArgs(false)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + + // Add another listener. + CurrentChangingEventManager.AddListener(source1, listener2); + source1.OnCurrentChanging(source1, new CurrentChangingEventArgs(false)); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanging(source2, new CurrentChangingEventArgs(false)); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + + // Add another source. + CurrentChangingEventManager.AddListener(source2, listener3); + source1.OnCurrentChanging(source1, new CurrentChangingEventArgs(false)); + Assert.Equal(7, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(0, callCount3); + source2.OnCurrentChanging(source2, new CurrentChangingEventArgs(false)); + Assert.Equal(7, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(1, callCount3); + } + + [Fact] + public void AddListener_NullSource_ThrowsArgumentNullException() + { + var listener = new CustomWeakEventListener(); + Assert.Throws("source", () => CurrentChangingEventManager.AddListener(null, listener)); + } + + [Fact] + public void AddListener_NullListener_ThrowsArgumentNullException() + { + var source = new CustomCollectionView(); + Assert.Throws("listener", () => CurrentChangingEventManager.AddListener(source, null)); + } + + [Fact] + public void RemoveHandler_Invoke_Success() + { + var source = new CustomCollectionView(); + var target = new CustomWeakEventListener(); + int callCount = 0; + target.HandlerAction += (s, e) => callCount++; + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + CurrentChangingEventManager.AddHandler(source, handler); + + // Remove. + CurrentChangingEventManager.RemoveHandler(source, handler); + source.OnCurrentChanging(source, new CurrentChangingEventArgs(false)); + Assert.Equal(0, callCount); + + // Remove again. + CurrentChangingEventManager.RemoveHandler(source, handler); + source.OnCurrentChanging(source, new CurrentChangingEventArgs(false)); + Assert.Equal(0, callCount); + } + + [Fact] + public void RemoveHandler_InvokeNoSource_Success() + { + var target = new CustomWeakEventListener(); + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + Assert.Throws(() => CurrentChangingEventManager.AddHandler(null, handler)); + + // Remove. + Assert.Throws(() => CurrentChangingEventManager.RemoveHandler(null, handler)); + + // Remove again. + CurrentChangingEventManager.RemoveHandler(null, handler); + } + + [Fact] + public void RemoveHandler_InvokeNoSuchSource_Nop() + { + var source1 = new CustomCollectionView(); + var source2 = new CustomCollectionView(); + var target = new CustomWeakEventListener(); + int callCount = 0; + target.HandlerAction += (s, e) => callCount++; + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + CurrentChangingEventManager.AddHandler(source1, handler); + + // Remove. + CurrentChangingEventManager.RemoveHandler(source2, handler); + source1.OnCurrentChanging(source1, new CurrentChangingEventArgs(false)); + Assert.Equal(1, callCount); + source2.OnCurrentChanging(source2, new CurrentChangingEventArgs(false)); + Assert.Equal(1, callCount); + + // Remove again. + CurrentChangingEventManager.RemoveHandler(source2, handler); + source1.OnCurrentChanging(source1, new CurrentChangingEventArgs(false)); + Assert.Equal(2, callCount); + source2.OnCurrentChanging(source2, new CurrentChangingEventArgs(false)); + Assert.Equal(2, callCount); + } + + [Fact] + public void RemoveHandler_InvokeNoSuchHandler_Nop() + { + var source = new CustomCollectionView(); + var target1 = new CustomWeakEventListener(); + int callCount1 = 0; + target1.HandlerAction += (s, e) => callCount1++; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + int callCount2 = 0; + target2.HandlerAction += (s, e) => callCount2++; + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + CurrentChangingEventManager.AddHandler(source, handler1); + + // Remove. + CurrentChangingEventManager.RemoveHandler(source, handler2); + source.OnCurrentChanging(source, new CurrentChangingEventArgs(false)); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Remove again. + CurrentChangingEventManager.RemoveHandler(source, handler2); + source.OnCurrentChanging(source, new CurrentChangingEventArgs(false)); + Assert.Equal(2, callCount1); + Assert.Equal(0, callCount2); + } + + [Fact] + public void RemoveHandler_NullHandler_ThrowsArgumentNullException() + { + var source = new CustomCollectionView(); + Assert.Throws("handler", () => CurrentChangingEventManager.RemoveHandler(null, null)); + Assert.Throws("handler", () => CurrentChangingEventManager.RemoveHandler(source, null)); + } + + [Fact] + public void RemoveListener_Invoke_Success() + { + var source = new CustomCollectionView(); + var listener = new CustomWeakEventListener(); + int callCount = 0; + listener.ReceiveWeakEventAction += (t, s, e) => + { + callCount++; + return true; + }; + CurrentChangingEventManager.AddListener(source, listener); + + // Remove. + CurrentChangingEventManager.RemoveListener(source, listener); + source.OnCurrentChanging(source, new CurrentChangingEventArgs(false)); + Assert.Equal(0, callCount); + + // Remove again. + CurrentChangingEventManager.RemoveListener(source, listener); + source.OnCurrentChanging(source, new CurrentChangingEventArgs(false)); + Assert.Equal(0, callCount); + } + + [Fact] + public void RemoveListener_InvokeNoSuchSource_Nop() + { + var source1 = new CustomCollectionView(); + var source2 = new CustomCollectionView(); + var listener = new CustomWeakEventListener(); + int callCount = 0; + listener.ReceiveWeakEventAction += (t, s, e) => + { + callCount++; + return true; + }; + CurrentChangingEventManager.AddListener(source1, listener); + + // Remove. + CurrentChangingEventManager.RemoveListener(source2, listener); + source1.OnCurrentChanging(source1, new CurrentChangingEventArgs(false)); + Assert.Equal(1, callCount); + source2.OnCurrentChanging(source2, new CurrentChangingEventArgs(false)); + Assert.Equal(1, callCount); + + // Remove again. + CurrentChangingEventManager.RemoveListener(source2, listener); + source1.OnCurrentChanging(source1, new CurrentChangingEventArgs(false)); + Assert.Equal(2, callCount); + source2.OnCurrentChanging(source2, new CurrentChangingEventArgs(false)); + Assert.Equal(2, callCount); + } + + [Fact] + public void RemoveListener_InvokeNoSuchListener_Nop() + { + var source = new CustomCollectionView(); + var listener1 = new CustomWeakEventListener(); + int callCount1 = 0; + listener1.ReceiveWeakEventAction += (t, s, e) => + { + callCount1++; + return true; + }; + var listener2 = new CustomWeakEventListener(); + int callCount2 = 0; + listener2.ReceiveWeakEventAction += (t, s, e) => + { + callCount2++; + return true; + }; + CurrentChangingEventManager.AddListener(source, listener1); + + // Remove. + CurrentChangingEventManager.RemoveListener(source, listener2); + source.OnCurrentChanging(source, new CurrentChangingEventArgs(false)); + Assert.Equal(1, callCount1); + + // Remove again. + CurrentChangingEventManager.RemoveListener(source, listener2); + source.OnCurrentChanging(source, new CurrentChangingEventArgs(false)); + Assert.Equal(2, callCount1); + } + + [Fact] + public void RemoveListener_NullListener_ThrowsArgumentNullException() + { + var source = new CustomCollectionView(); + Assert.Throws("listener", () => CurrentChangingEventManager.RemoveListener(source, null)); + } + + private class CustomCollectionView : ICollectionView + { + public bool CanFilter => throw new NotImplementedException(); + + public bool CanGroup => throw new NotImplementedException(); + + public bool CanSort => throw new NotImplementedException(); + + public CultureInfo Culture + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public object CurrentItem => throw new NotImplementedException(); + + public int CurrentPosition => throw new NotImplementedException(); + + public Predicate Filter + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + public ObservableCollection GroupDescriptions => throw new NotImplementedException(); + + public ReadOnlyObservableCollection Groups => throw new NotImplementedException(); + + public bool IsCurrentAfterLast => throw new NotImplementedException(); + + public bool IsCurrentBeforeFirst => throw new NotImplementedException(); + + public bool IsEmpty => throw new NotImplementedException(); + + public SortDescriptionCollection SortDescriptions => throw new NotImplementedException(); + + public IEnumerable SourceCollection => throw new NotImplementedException(); + + #pragma warning disable CS0067 + public event EventHandler? CurrentChanged; + public event CurrentChangingEventHandler? CurrentChanging; + public event NotifyCollectionChangedEventHandler? CollectionChanged; + #pragma warning restore CS0067 + + public bool Contains(object item) => throw new NotImplementedException(); + + public IDisposable DeferRefresh() => throw new NotImplementedException(); + + public IEnumerator GetEnumerator() => throw new NotImplementedException(); + + public bool MoveCurrentTo(object item) => throw new NotImplementedException(); + + public bool MoveCurrentToFirst() => throw new NotImplementedException(); + + public bool MoveCurrentToLast() => throw new NotImplementedException(); + + public bool MoveCurrentToNext() => throw new NotImplementedException(); + + public bool MoveCurrentToPosition(int position) => throw new NotImplementedException(); + + public bool MoveCurrentToPrevious() => throw new NotImplementedException(); + + public void Refresh() => throw new NotImplementedException(); + + public void OnCurrentChanging(object sender, CurrentChangingEventArgs e) => CurrentChanging?.Invoke(sender, e); + } + + private class CustomWeakEventListener : IWeakEventListener + { + public Func? ReceiveWeakEventAction { get; set; } + + public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (ReceiveWeakEventAction is null) + { + return true; + } + + return ReceiveWeakEventAction(managerType, sender, e); + } + + public Action? HandlerAction { get; set; } + + public void Handler(object sender, CurrentChangingEventArgs e) + { + HandlerAction?.Invoke(sender, e); + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/DependencyPropertyDescriptorTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/DependencyPropertyDescriptorTests.cs new file mode 100644 index 00000000000..834c1bed942 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/DependencyPropertyDescriptorTests.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using System.Windows; + +namespace System.ComponentModel.Tests; + +public class DependencyPropertyDescriptorTests +{ + // TODO: + // FromProperty - success + + [Fact] + public void FromProperty_InvokeNotDependencyObjectPropertyDescriptor_Success() + { + var component = new NotDependencyObject(); + PropertyDescriptor property = TypeDescriptor.GetProperties(component)[nameof(NotDependencyObject.Property)]!; + + // Get descriptor. + Assert.Null(DependencyPropertyDescriptor.FromProperty(property)); + + // Get descriptor again. + Assert.Null(DependencyPropertyDescriptor.FromProperty(property)); + } + + [Fact] + public void FromProperty_NullProperty_ThrowsArgumentNullException() + { + Assert.Throws("property", () => DependencyPropertyDescriptor.FromProperty(null)); + } + + [Fact] + public void FromProperty_InvokeTypeDependencyProperty_ReturnsNull() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyPropertyDescriptorTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + + // Get descriptor. + Assert.Null(DependencyPropertyDescriptor.FromProperty(property, typeof(DependencyProperty))); + + // Get descriptor again. + Assert.Null(DependencyPropertyDescriptor.FromProperty(property, typeof(DependencyProperty))); + } + + [Fact] + public void FromProperty_InvokeTypeNotDependencyProperty_ReturnsNull() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyPropertyDescriptorTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + + // Get descriptor. + Assert.Null(DependencyPropertyDescriptor.FromProperty(property, typeof(int))); + + // Get descriptor again. + Assert.Null(DependencyPropertyDescriptor.FromProperty(property, typeof(int))); + } + + [Fact] + public void FromProperty_NullDependencyProperty_ThrowsArgumentNullException() + { + Assert.Throws("dependencyProperty", () => DependencyPropertyDescriptor.FromProperty(null, typeof(object))); + } + + [Fact] + public void FromProperty_NullTargetType_ThrowsArgumentNullException() + { + DependencyProperty property = DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + Assert.Throws("targetType", () => DependencyPropertyDescriptor.FromProperty(property, null)); + } + + public class NotDependencyObject + { + public int Property { get; set; } + } + + private class SubDependencyObject : DependencyObject + { + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/ErrorsChangedEventManagerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/ErrorsChangedEventManagerTests.cs new file mode 100644 index 00000000000..9ec413fc584 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/ErrorsChangedEventManagerTests.cs @@ -0,0 +1,293 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Windows; + +namespace System.ComponentModel.Tests; + +public class ErrorsChangedEventManagerTests +{ + public static IEnumerable AddHandler_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new DataErrorsChangedEventArgs(null) }; + yield return new object?[] { new DataErrorsChangedEventArgs("") }; + yield return new object?[] { new DataErrorsChangedEventArgs("propertyName") }; + } + + [Theory] + [MemberData(nameof(AddHandler_TestData))] + public void AddHandler_InvokeWithHandler_CallsErrorsChanged(DataErrorsChangedEventArgs e) + { + var source = new CustomNotifyDataErrorInfo(); + int callCount1 = 0; + var listener1 = new CustomWeakEventListener(); + listener1.HandlerAction += (actualSender, actualE) => + { + Assert.Same(source, actualSender); + Assert.Same(e, actualE); + callCount1++; + }; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), listener1, nameof(CustomWeakEventListener.Handler)); + int callCount2 = 0; + var listener2 = new CustomWeakEventListener(); + listener2.HandlerAction += (actualSender, actualE) => + { + Assert.Same(source, actualSender); + Assert.Same(e, actualE); + callCount2++; + }; + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), listener2, nameof(CustomWeakEventListener.Handler)); + + // Add. + ErrorsChangedEventManager.AddHandler(source, handler1); + + // Call. + source.OnErrorsChanged(source, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Call invalid source. + source.OnErrorsChanged(listener1, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source.OnErrorsChanged(new object(), e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source.OnErrorsChanged(null!, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Add again. + ErrorsChangedEventManager.AddHandler(source, handler1); + source.OnErrorsChanged(source, e); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + // Add another. + ErrorsChangedEventManager.AddHandler(source, handler2); + source.OnErrorsChanged(source, e); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + + // Remove second. + ErrorsChangedEventManager.RemoveHandler(source, handler2); + source.OnErrorsChanged(source, e); + Assert.Equal(7, callCount1); + Assert.Equal(1, callCount2); + + // Remove first. + ErrorsChangedEventManager.RemoveHandler(source, handler1); + source.OnErrorsChanged(source, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + + // Remove again. + ErrorsChangedEventManager.RemoveHandler(source, handler1); + source.OnErrorsChanged(source, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + } + + [Fact] + public void AddHandler_InvokeMultipleTimes_Success() + { + var source1 = new CustomNotifyDataErrorInfo(); + var source2 = new CustomNotifyDataErrorInfo(); + var target1 = new CustomWeakEventListener(); + int callCount1 = 0; + target1.HandlerAction += (sender, e) => callCount1++; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + int callCount2 = 0; + target2.HandlerAction += (sender, e) => callCount2++; + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + var target3 = new CustomWeakEventListener(); + int callCount3 = 0; + target3.HandlerAction += (sender, e) => callCount3++; + EventHandler handler3 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target3, nameof(CustomWeakEventListener.Handler)); + + // Add. + ErrorsChangedEventManager.AddHandler(source1, handler1); + source1.OnErrorsChanged(source1, new DataErrorsChangedEventArgs(null)); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + source2.OnErrorsChanged(source2, new DataErrorsChangedEventArgs(null)); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + + // Add again. + ErrorsChangedEventManager.AddHandler(source1, handler1); + source1.OnErrorsChanged(source1, new DataErrorsChangedEventArgs(null)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + source2.OnErrorsChanged(source2, new DataErrorsChangedEventArgs(null)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + + // Add another handler. + ErrorsChangedEventManager.AddHandler(source1, handler2); + source1.OnErrorsChanged(source1, new DataErrorsChangedEventArgs(null)); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + source2.OnErrorsChanged(source2, new DataErrorsChangedEventArgs(null)); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + + // Add another source. + ErrorsChangedEventManager.AddHandler(source2, handler3); + source1.OnErrorsChanged(source1, new DataErrorsChangedEventArgs(null)); + Assert.Equal(7, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(0, callCount3); + source2.OnErrorsChanged(source2, new DataErrorsChangedEventArgs(null)); + Assert.Equal(7, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(1, callCount3); + } + + [Fact] + public void AddHandler_NullSource_ThrowsArgumentNullException() + { + var target = new CustomWeakEventListener(); + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + Assert.Throws("source", () => ErrorsChangedEventManager.AddHandler(null, handler)); + } + + [Fact] + public void AddHandler_NullHandler_ThrowsArgumentNullException() + { + var source = new CustomNotifyDataErrorInfo(); + Assert.Throws("handler", () => ErrorsChangedEventManager.AddHandler(source, null)); + } + + [Fact] + public void RemoveHandler_Invoke_Success() + { + var source = new CustomNotifyDataErrorInfo(); + var target = new CustomWeakEventListener(); + int callCount = 0; + target.HandlerAction += (sender, e) => callCount++; + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + ErrorsChangedEventManager.AddHandler(source, handler); + + // Remove. + ErrorsChangedEventManager.RemoveHandler(source, handler); + source.OnErrorsChanged(source, new DataErrorsChangedEventArgs(null)); + Assert.Equal(0, callCount); + + // Remove again. + ErrorsChangedEventManager.RemoveHandler(source, handler); + source.OnErrorsChanged(source, new DataErrorsChangedEventArgs(null)); + Assert.Equal(0, callCount); + } + + [Fact] + public void RemoveHandler_InvokeNoSuchSource_Nop() + { + var source1 = new CustomNotifyDataErrorInfo(); + var source2 = new CustomNotifyDataErrorInfo(); + var target = new CustomWeakEventListener(); + int callCount = 0; + target.HandlerAction += (sender, e) => callCount++; + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + ErrorsChangedEventManager.AddHandler(source1, handler); + + // Remove. + ErrorsChangedEventManager.RemoveHandler(source2, handler); + source1.OnErrorsChanged(source1, new DataErrorsChangedEventArgs(null)); + Assert.Equal(1, callCount); + source2.OnErrorsChanged(source1, new DataErrorsChangedEventArgs(null)); + Assert.Equal(1, callCount); + + // Remove again. + ErrorsChangedEventManager.RemoveHandler(source2, handler); + source1.OnErrorsChanged(source1, new DataErrorsChangedEventArgs(null)); + Assert.Equal(2, callCount); + source2.OnErrorsChanged(source1, new DataErrorsChangedEventArgs(null)); + Assert.Equal(2, callCount); + } + + [Fact] + public void RemoveHandler_InvokeNoSuchHandler_Nop() + { + var source = new CustomNotifyDataErrorInfo(); + var target1 = new CustomWeakEventListener(); + int callCount = 0; + target1.HandlerAction += (sender, e) => callCount++; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + ErrorsChangedEventManager.AddHandler(source, handler1); + + // Remove. + ErrorsChangedEventManager.RemoveHandler(source, handler2); + source.OnErrorsChanged(source, new DataErrorsChangedEventArgs(null)); + Assert.Equal(1, callCount); + + // Remove again. + ErrorsChangedEventManager.RemoveHandler(source, handler2); + source.OnErrorsChanged(source, new DataErrorsChangedEventArgs(null)); + Assert.Equal(2, callCount); + } + + [Fact] + public void RemoveHandler_NullSource_ThrowsArgumentNullException() + { + var target = new CustomWeakEventListener(); + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + Assert.Throws("source", () => ErrorsChangedEventManager.RemoveHandler(null, handler)); + } + + [Fact] + public void RemoveHandler_NullHandler_ThrowsArgumentNullException() + { + var source = new CustomNotifyDataErrorInfo(); + Assert.Throws("handler", () => ErrorsChangedEventManager.RemoveHandler(source, null)); + } + + private class CustomNotifyDataErrorInfo : INotifyDataErrorInfo + { + public bool HasErrors => throw new NotImplementedException(); + + #pragma warning disable CS0067 + public event EventHandler? ErrorsChanged; + #pragma warning restore CS0067 + + public IEnumerable GetErrors(string? propertyName) => throw new NotImplementedException(); + + public void OnErrorsChanged(object sender, DataErrorsChangedEventArgs e) => ErrorsChanged?.Invoke(sender, e); + } + + private class CustomWeakEventListener : IWeakEventListener + { + public Func? ReceiveWeakEventAction { get; set; } + + public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (ReceiveWeakEventAction is null) + { + return true; + } + + return ReceiveWeakEventAction(managerType, sender, e); + } + + public Action? HandlerAction { get; set; } + + public void Handler(object sender, DataErrorsChangedEventArgs e) + { + HandlerAction?.Invoke(sender, e); + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/GroupDescriptionTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/GroupDescriptionTests.cs new file mode 100644 index 00000000000..c6e6c1532a1 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/GroupDescriptionTests.cs @@ -0,0 +1,625 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; + +namespace System.ComponentModel.Tests; + +public class GroupDescriptionTests +{ + [Fact] + public void Ctor_Default() + { + var description = new SubGroupDescription(); + Assert.Null(description.CustomSort); + Assert.Empty(description.GroupNames); + Assert.Same(description.GroupNames, description.GroupNames); + Assert.Empty(description.SortDescriptions); + Assert.Same(description.SortDescriptions, description.SortDescriptions); + } + + public static IEnumerable CustomSort_Set_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { StringComparer.CurrentCulture }; + } + + [Theory] + [MemberData(nameof(CustomSort_Set_TestData))] + public void CustomSort_Set_GetReturnsExpected(IComparer? value) + { + var description = new SubGroupDescription + { + CustomSort = value + }; + Assert.Same(value, description.CustomSort); + Assert.Empty(description.SortDescriptions); + + // Set same. + description.CustomSort = value; + Assert.Same(value, description.CustomSort); + Assert.Empty(description.SortDescriptions); + } + + [Theory] + [MemberData(nameof(CustomSort_Set_TestData))] + public void CustomSort_SetNonNullOldValue_GetReturnsExpected(IComparer? value) + { + var description = new SubGroupDescription + { + CustomSort = StringComparer.Ordinal + }; + + description.CustomSort = value; + Assert.Same(value, description.CustomSort); + Assert.Empty(description.SortDescriptions); + + // Set same. + description.CustomSort = value; + Assert.Same(value, description.CustomSort); + Assert.Empty(description.SortDescriptions); + } + + [Theory] + [MemberData(nameof(CustomSort_Set_TestData))] + public void CustomSort_SetWithSortDescriptions_GetReturnsExpected(IComparer? value) + { + var description = new SubGroupDescription(); + description.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending)); + + // Set. + description.CustomSort = value; + Assert.Same(value, description.CustomSort); + Assert.Empty(description.SortDescriptions); + + // Set same. + description.CustomSort = value; + Assert.Same(value, description.CustomSort); + Assert.Empty(description.SortDescriptions); + } + + [Theory] + [MemberData(nameof(CustomSort_Set_TestData))] + public void CustomSort_SetWithEmptySortDescriptions_GetReturnsExpected(IComparer? value) + { + var description = new SubGroupDescription(); + Assert.Empty(description.SortDescriptions); + + // Set. + description.CustomSort = value; + Assert.Same(value, description.CustomSort); + Assert.Empty(description.SortDescriptions); + + // Set same. + description.CustomSort = value; + Assert.Same(value, description.CustomSort); + Assert.Empty(description.SortDescriptions); + } + + [Fact] + public void CustomSort_SetWithHandler_CallsPropertyChanged() + { + var description = new SubGroupDescription(); + int callCount = 0; + PropertyChangedEventHandler handler = (sender, e) => + { + Assert.Same(description, sender); + Assert.Equal("CustomSort", e.PropertyName); + callCount++; + }; ; + ((INotifyPropertyChanged)description).PropertyChanged += handler; + + // Set. + description.CustomSort = StringComparer.CurrentCulture; + Assert.Equal(StringComparer.CurrentCulture, description.CustomSort); + Assert.Equal(1, callCount); + + // Set same. + description.CustomSort = StringComparer.CurrentCulture; + Assert.Equal(StringComparer.CurrentCulture, description.CustomSort); + Assert.Equal(2, callCount); + + // Remove handler. + ((INotifyPropertyChanged)description).PropertyChanged -= handler; + description.CustomSort = null; + Assert.Null(description.CustomSort); + Assert.Equal(2, callCount); + } + + [Fact] + public void CustomSort_SetWithSortDescriptionsWithHandler_CallsPropertyChanged() + { + var description = new SubGroupDescription(); + description.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending)); + + int callCount = 0; + var properties = new List(); + PropertyChangedEventHandler handler = (sender, e) => + { + Assert.Same(description, sender); + properties.Add(e.PropertyName!); + callCount++; + }; ; + ((INotifyPropertyChanged)description).PropertyChanged += handler; + + // Set. + description.CustomSort = StringComparer.CurrentCulture; + Assert.Equal(StringComparer.CurrentCulture, description.CustomSort); + Assert.Equal(2, callCount); + Assert.Equal(new[] { "SortDescriptions", "CustomSort" }, properties); + + // Set same. + description.CustomSort = StringComparer.CurrentCulture; + Assert.Equal(StringComparer.CurrentCulture, description.CustomSort); + Assert.Equal(3, callCount); + Assert.Equal(new[] { "SortDescriptions", "CustomSort", "CustomSort" }, properties); + + // Remove handler. + ((INotifyPropertyChanged)description).PropertyChanged -= handler; + description.CustomSort = null; + Assert.Null(description.CustomSort); + Assert.Equal(3, callCount); + Assert.Equal(new[] { "SortDescriptions", "CustomSort", "CustomSort" }, properties); + } + + [Fact] + public void CustomSort_SetWithEmptySortDescriptionsWithHandler_CallsPropertyChanged() + { + var description = new SubGroupDescription(); + Assert.Empty(description.SortDescriptions); + + int callCount = 0; + var properties = new List(); + PropertyChangedEventHandler handler = (sender, e) => + { + Assert.Same(description, sender); + properties.Add(e.PropertyName!); + callCount++; + }; ; + ((INotifyPropertyChanged)description).PropertyChanged += handler; + + // Set. + description.CustomSort = StringComparer.CurrentCulture; + Assert.Equal(StringComparer.CurrentCulture, description.CustomSort); + Assert.Equal(2, callCount); + Assert.Equal(new[] { "SortDescriptions", "CustomSort" }, properties); + + // Set same. + description.CustomSort = StringComparer.CurrentCulture; + Assert.Equal(StringComparer.CurrentCulture, description.CustomSort); + Assert.Equal(3, callCount); + Assert.Equal(new[] { "SortDescriptions", "CustomSort", "CustomSort" }, properties); + + // Remove handler. + ((INotifyPropertyChanged)description).PropertyChanged -= handler; + description.CustomSort = null; + Assert.Null(description.CustomSort); + Assert.Equal(3, callCount); + Assert.Equal(new[] { "SortDescriptions", "CustomSort", "CustomSort" }, properties); + } + + [Fact] + public void CustomSort_GetFirstTime_DoesNotCallPropertyChanged() + { + var description = new SubGroupDescription(); + int callCount = 0; + ((INotifyPropertyChanged)description).PropertyChanged += (sender, e) => callCount++; + + // Get. + Assert.Null(description.CustomSort); + Assert.Equal(0, callCount); + + // Get again. + Assert.Null(description.CustomSort); + Assert.Equal(0, callCount); + } + + [Fact] + public void GroupNames_GetFirstTime_DoesNotCallPropertyChanged() + { + var description = new SubGroupDescription(); + int callCount = 0; + ((INotifyPropertyChanged)description).PropertyChanged += (sender, e) => callCount++; + + // Get. + ObservableCollection collection = description.GroupNames; + Assert.Empty(collection); + Assert.Same(collection, description.GroupNames); + Assert.Equal(0, callCount); + + // Get again. + Assert.Empty(description.GroupNames); + Assert.Equal(0, callCount); + } + + [Fact] + public void GroupNames_Change_CallsPropertyChanged() + { + var description = new SubGroupDescription(); + int callCount = 0; + PropertyChangedEventHandler handler = (sender, e) => + { + Assert.Same(description, sender); + Assert.Equal("GroupNames", e.PropertyName); + callCount++; + }; + ((INotifyPropertyChanged)description).PropertyChanged += handler; + + // Clear. + description.GroupNames.Clear(); + Assert.Empty(description.GroupNames); + Assert.Equal(1, callCount); + + // Clear again. + description.GroupNames.Clear(); + Assert.Empty(description.GroupNames); + Assert.Equal(2, callCount); + + // Remove handler. + ((INotifyPropertyChanged)description).PropertyChanged -= handler; + description.GroupNames.Clear(); + Assert.Empty(description.GroupNames); + Assert.Equal(2, callCount); + } + + [Fact] + public void GroupNames_ShouldSerialize_ReturnsExpected() + { + var description = new SubGroupDescription(); + PropertyDescriptor property = TypeDescriptor.GetProperties(typeof(GroupDescription))[nameof(GroupDescription.GroupNames)]!; + Assert.False(property.ShouldSerializeValue(description)); + + Assert.Empty(description.GroupNames); + Assert.False(property.ShouldSerializeValue(description)); + + description.GroupNames.Add("Name"); + Assert.True(property.ShouldSerializeValue(description)); + + description.GroupNames.Clear(); + Assert.False(property.ShouldSerializeValue(description)); + } + + [Fact] + public void SortDescriptions_GetFirstTime_CallsPropertyChanged() + { + var description = new SubGroupDescription(); + int callCount = 0; + ((INotifyPropertyChanged)description).PropertyChanged += (sender, e) => + { + Assert.Same(description, sender); + Assert.Equal("SortDescriptions", e.PropertyName); + callCount++; + }; + + // Get. + SortDescriptionCollection collection = description.SortDescriptions; + Assert.Empty(collection); + Assert.Same(collection, description.SortDescriptions); + Assert.Equal(1, callCount); + + // Get again. + Assert.Empty(description.SortDescriptions); + Assert.Equal(1, callCount); + } + + [Fact] + public void SortDescriptions_GetFirstTimeWithCustomSort_CallsPropertyChanged() + { + var description = new SubGroupDescription(); + description.CustomSort = StringComparer.CurrentCulture; + Assert.Equal(StringComparer.CurrentCulture, description.CustomSort); + + int callCount = 0; + ((INotifyPropertyChanged)description).PropertyChanged += (sender, e) => + { + Assert.Same(description, sender); + Assert.Equal("SortDescriptions", e.PropertyName); + callCount++; + }; + + // Get. + SortDescriptionCollection collection = description.SortDescriptions; + Assert.Empty(collection); + Assert.Same(collection, description.SortDescriptions); + Assert.Equal(1, callCount); + + // Get again. + Assert.Empty(description.SortDescriptions); + Assert.Equal(1, callCount); + } + + [Fact] + public void SortDescriptions_GetRemovedHandler_DoesNotCallPropertyChanged() + { + var description = new SubGroupDescription(); + int callCount = 0; + PropertyChangedEventHandler handler = (sender, e) => callCount++; + ((INotifyPropertyChanged)description).PropertyChanged += handler; + ((INotifyPropertyChanged)description).PropertyChanged -= handler; + + // Get. + SortDescriptionCollection collection = description.SortDescriptions; + Assert.Empty(collection); + Assert.Same(collection, description.SortDescriptions); + Assert.Equal(0, callCount); + + // Get again. + Assert.Empty(description.SortDescriptions); + Assert.Equal(0, callCount); + } + + [Fact] + public void SortDescriptions_ChangeCount_ClearsCustomSort() + { + var description = new SubGroupDescription(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Ascending); + + description.CustomSort = StringComparer.CurrentCulture; + Assert.Equal(StringComparer.CurrentCulture, description.CustomSort); + + // Add. + description.SortDescriptions.Add(description1); + Assert.Equal(new[] { description1 }, description.SortDescriptions.Cast()); + Assert.Null(description.CustomSort); + } + + [Fact] + public void SortDescriptions_ChangeNoCount_DoesNotClearCustomSort() + { + var description = new SubGroupDescription(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Ascending); + + description.CustomSort = StringComparer.CurrentCulture; + Assert.Equal(StringComparer.CurrentCulture, description.CustomSort); + + // Clear. + description.SortDescriptions.Clear(); + Assert.Empty(description.SortDescriptions); + Assert.Equal(StringComparer.CurrentCulture, description.CustomSort); + } + + [Fact] + public void SortDescriptions_Change_CallsPropertyChanged() + { + var description = new SubGroupDescription(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Ascending); + Assert.Empty(description.SortDescriptions); + + int callCount = 0; + PropertyChangedEventHandler handler = (sender, e) => + { + Assert.Same(description, sender); + Assert.Equal("SortDescriptions", e.PropertyName); + callCount++; + }; + ((INotifyPropertyChanged)description).PropertyChanged += handler; + + // Add. + description.SortDescriptions.Add(description1); + Assert.Equal(new[] { description1 }, description.SortDescriptions.Cast()); + Assert.Null(description.CustomSort); + Assert.Equal(1, callCount); + + // Clear. + description.SortDescriptions.Clear(); + Assert.Empty(description.SortDescriptions); + Assert.Null(description.CustomSort); + Assert.Equal(2, callCount); + + // Remove handler. + ((INotifyPropertyChanged)description).PropertyChanged -= handler; + description.SortDescriptions.Add(description2); + Assert.Equal(new[] { description2 }, description.SortDescriptions.Cast()); + Assert.Null(description.CustomSort); + Assert.Equal(2, callCount); + } + + [Fact] + public void SortDescriptions_ChangeWithCustomSort_CallsPropertyChanged() + { + var description = new SubGroupDescription(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Ascending); + description.CustomSort = StringComparer.CurrentCulture; + Assert.Empty(description.SortDescriptions); + + int callCount = 0; + var properties = new List(); + PropertyChangedEventHandler handler = (sender, e) => + { + Assert.Same(description, sender); + properties.Add(e.PropertyName!); + callCount++; + }; + ((INotifyPropertyChanged)description).PropertyChanged += handler; + + // Add. + description.SortDescriptions.Add(description1); + Assert.Equal(new[] { description1 }, description.SortDescriptions.Cast()); + Assert.Null(description.CustomSort); + Assert.Equal(2, callCount); + Assert.Equal(new[] { "CustomSort", "SortDescriptions" }, properties); + + // Clear. + description.SortDescriptions.Clear(); + Assert.Empty(description.SortDescriptions); + Assert.Null(description.CustomSort); + Assert.Equal(3, callCount); + Assert.Equal(new[] { "CustomSort", "SortDescriptions", "SortDescriptions" }, properties); + + // Remove handler. + ((INotifyPropertyChanged)description).PropertyChanged -= handler; + description.SortDescriptions.Add(description2); + Assert.Equal(new[] { description2 }, description.SortDescriptions.Cast()); + Assert.Null(description.CustomSort); + Assert.Equal(3, callCount); + Assert.Equal(new[] { "CustomSort", "SortDescriptions", "SortDescriptions" }, properties); + } + + [Fact] + public void SortDescriptions_ShouldSerialize_ReturnsExpected() + { + var description = new SubGroupDescription(); + PropertyDescriptor property = TypeDescriptor.GetProperties(typeof(GroupDescription))[nameof(GroupDescription.SortDescriptions)]!; + Assert.False(property.ShouldSerializeValue(description)); + + Assert.Empty(description.SortDescriptions); + Assert.False(property.ShouldSerializeValue(description)); + + description.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending)); + Assert.True(property.ShouldSerializeValue(description)); + + description.SortDescriptions.Clear(); + Assert.False(property.ShouldSerializeValue(description)); + } + + [Fact] + public void PropertyChanged_INotifyPropertyChangedAddRemove_Success() + { + INotifyPropertyChanged description = new SubGroupDescription(); + + int callCount = 0; + PropertyChangedEventHandler handler = (s, e) => callCount++; + ((INotifyPropertyChanged)description).PropertyChanged += handler; + Assert.Equal(0, callCount); + + ((INotifyPropertyChanged)description).PropertyChanged -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + ((INotifyPropertyChanged)description).PropertyChanged -= handler; + Assert.Equal(0, callCount); + + // Add null. + ((INotifyPropertyChanged)description).PropertyChanged += null; + Assert.Equal(0, callCount); + + // Remove null. + ((INotifyPropertyChanged)description).PropertyChanged -= null; + Assert.Equal(0, callCount); + } + + public static IEnumerable NamesMatch_TestData() + { + yield return new object?[] { null, null, true }; + + var obj = new object(); + yield return new object?[] { obj, obj, true }; + yield return new object?[] { obj, new object(), false }; + yield return new object?[] { obj, null, false }; + yield return new object?[] { null, null, true }; + yield return new object?[] { null, new object(), false }; + } + + [Theory] + [MemberData(nameof(NamesMatch_TestData))] + public void NamesMatch_Invoke_ReturnsExpected(object? groupName1, object? groupName2, bool expected) + { + var description = new SubGroupDescription(); + Assert.Equal(expected, description.NamesMatch(groupName1, groupName2)); + } + + public static IEnumerable OnPropertyChanged_TestData() + { + yield return new object?[] { new PropertyChangedEventArgs("Name") }; + yield return new object?[] { null }; + } + + [Theory] + [MemberData(nameof(OnPropertyChanged_TestData))] + public void OnPropertyChanged_Invoke_CallsPropertyChangedEvent(PropertyChangedEventArgs eventArgs) + { + var description = new SubGroupDescription(); + int callCount = 0; + PropertyChangedEventHandler handler = (sender, e) => + { + Assert.Same(description, sender); + Assert.Same(eventArgs, e); + callCount++; + }; + + // Call with handler. + ((INotifyPropertyChanged)description).PropertyChanged += handler; + description.OnPropertyChanged(eventArgs); + Assert.Equal(1, callCount); + + // Remove handler. + ((INotifyPropertyChanged)description).PropertyChanged -= handler; + description.OnPropertyChanged(eventArgs); + Assert.Equal(1, callCount); + } + + [Fact] + public void ShouldSerializeGroupNames_Invoke_ReturnsExpected() + { + var description = new SubGroupDescription(); + PropertyDescriptor property = TypeDescriptor.GetProperties(typeof(GroupDescription))[nameof(GroupDescription.GroupNames)]!; + Assert.False(description.ShouldSerializeGroupNames()); + + Assert.Empty(description.GroupNames); + Assert.False(description.ShouldSerializeGroupNames()); + + description.GroupNames.Add("Name"); + Assert.True(description.ShouldSerializeGroupNames()); + + description.GroupNames.Clear(); + Assert.False(description.ShouldSerializeGroupNames()); + } + + [Fact] + public void ShouldSerializeGroupNames_Invoke_DoesNotCallPropertyChanged() + { + var description = new SubGroupDescription(); + int callCount = 0; + ((INotifyPropertyChanged)description).PropertyChanged += (sender, e) => callCount++; + + Assert.False(description.ShouldSerializeGroupNames()); + Assert.Equal(0, callCount); + } + + [Fact] + public void ShouldSerializeSortDescriptions_Invoke_ReturnsExpected() + { + var description = new SubGroupDescription(); + Assert.False(description.ShouldSerializeSortDescriptions()); + + Assert.Empty(description.SortDescriptions); + Assert.False(description.ShouldSerializeSortDescriptions()); + + description.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending)); + Assert.True(description.ShouldSerializeSortDescriptions()); + + description.SortDescriptions.Clear(); + Assert.False(description.ShouldSerializeSortDescriptions()); + } + + [Fact] + public void ShouldSerializeSortDescriptions_Invoke_DoesNotCallPropertyChanged() + { + var description = new SubGroupDescription(); + int callCount = 0; + ((INotifyPropertyChanged)description).PropertyChanged += (sender, e) => callCount++; + + Assert.False(description.ShouldSerializeSortDescriptions()); + Assert.Equal(0, callCount); + } + + private class SubGroupDescription : GroupDescription + { + public SubGroupDescription() : base() + { + } + + public override object GroupNameFromItem(object item, int level, CultureInfo culture) + => throw new NotImplementedException(); + + public new void OnPropertyChanged(PropertyChangedEventArgs e) => base.OnPropertyChanged(e); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/ItemPropertyInfoTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/ItemPropertyInfoTests.cs new file mode 100644 index 00000000000..74ae82ff1ba --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/ItemPropertyInfoTests.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel.Tests; + +public class ItemPropertyInfoTests +{ + public static IEnumerable Ctor_String_Type_Object_TestData() + { + yield return new object?[] { null, null, null }; + yield return new object?[] { string.Empty, typeof(int), new object() }; + yield return new object?[] { "name", typeof(object), 1 }; + } + + [Theory] + [MemberData(nameof(Ctor_String_Type_Object_TestData))] + public void Ctor_String_Type_Object(string name, Type type, object descriptor) + { + var propertyInfo = new ItemPropertyInfo(name, type, descriptor); + Assert.Equal(descriptor, propertyInfo.Descriptor); + Assert.Equal(name, propertyInfo.Name); + Assert.Equal(type, propertyInfo.PropertyType); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/PropertyChangedEventManagerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/PropertyChangedEventManagerTests.cs new file mode 100644 index 00000000000..e7fbb20d533 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/PropertyChangedEventManagerTests.cs @@ -0,0 +1,1274 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Windows; + +namespace System.ComponentModel.Tests; + +public class PropertyChangedEventManagerTests +{ + [Fact] + public void AddHandler_InvokeWithHandler_CallsErrorsChanged() + { + var source = new CustomNotifyPropertyChanged(); + int callCount1 = 0; + var listener1 = new CustomWeakEventListener(); + listener1.HandlerAction += (actualSender, actualE) => + { + Assert.Same(source, actualSender); + callCount1++; + }; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), listener1, nameof(CustomWeakEventListener.Handler)); + int callCount2 = 0; + var listener2 = new CustomWeakEventListener(); + listener2.HandlerAction += (actualSender, actualE) => + { + Assert.Same(source, actualSender); + callCount2++; + }; + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), listener2, nameof(CustomWeakEventListener.Handler)); + + // Add. + PropertyChangedEventManager.AddHandler(source, handler1, "propertyName"); + + // Call. + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Call null property name. + source.OnPropertyChanged(source, new PropertyChangedEventArgs(null)); + Assert.Equal(2, callCount1); + Assert.Equal(0, callCount2); + + // Call empty property name. + source.OnPropertyChanged(source, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + // Call invalid source. + source.OnPropertyChanged(listener1, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + source.OnPropertyChanged(new object(), new PropertyChangedEventArgs("propertyName")); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + source.OnPropertyChanged(null!, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + // Add again. + PropertyChangedEventManager.AddHandler(source, handler1, "propertyName"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(5, callCount1); + Assert.Equal(0, callCount2); + + // Add another. + PropertyChangedEventManager.AddHandler(source, handler2, "propertyName"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(7, callCount1); + Assert.Equal(1, callCount2); + + // Remove second. + PropertyChangedEventManager.RemoveHandler(source, handler2, "propertyName"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(9, callCount1); + Assert.Equal(1, callCount2); + + // Remove first. + PropertyChangedEventManager.RemoveHandler(source, handler1, "propertyName"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(10, callCount1); + Assert.Equal(1, callCount2); + + // Remove again. + PropertyChangedEventManager.RemoveHandler(source, handler1, "propertyName"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(10, callCount1); + Assert.Equal(1, callCount2); + } + + [Theory] + [InlineData("propertyName")] + [InlineData("PROPERTYNAME")] + public void AddHandler_InvokeMultipleTimes_Success(string propertyName) + { + var source1 = new CustomNotifyPropertyChanged(); + var source2 = new CustomNotifyPropertyChanged(); + var target1 = new CustomWeakEventListener(); + int callCount1 = 0; + target1.HandlerAction += (s, e) => callCount1++; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + int callCount2 = 0; + target2.HandlerAction += (s, e) => callCount2++; + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + var target3 = new CustomWeakEventListener(); + int callCount3 = 0; + target3.HandlerAction += (s, e) => callCount3++; + EventHandler handler3 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target3, nameof(CustomWeakEventListener.Handler)); + var target4 = new CustomWeakEventListener(); + int callCount4 = 0; + target4.HandlerAction += (s, e) => callCount4++; + EventHandler handler4 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target4, nameof(CustomWeakEventListener.Handler)); + var target5 = new CustomWeakEventListener(); + int callCount5 = 0; + target5.HandlerAction += (s, e) => callCount5++; + EventHandler handler5 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target5, nameof(CustomWeakEventListener.Handler)); + + // Add. + PropertyChangedEventManager.AddHandler(source1, handler1, "propertyName"); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName2")); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(2, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(null)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(null)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + + // Add again. + PropertyChangedEventManager.AddHandler(source1, handler1, "propertyName"); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(5, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName2")); + Assert.Equal(5, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(7, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(null)); + Assert.Equal(9, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(9, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(9, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(null)); + Assert.Equal(9, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + + // Add another handler. + PropertyChangedEventManager.AddHandler(source1, handler2, "propertyName"); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(11, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName2")); + Assert.Equal(11, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(13, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(null)); + Assert.Equal(15, callCount1); + Assert.Equal(3, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(15, callCount1); + Assert.Equal(3, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(15, callCount1); + Assert.Equal(3, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(null)); + Assert.Equal(15, callCount1); + Assert.Equal(3, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + + // Add another property name. + PropertyChangedEventManager.AddHandler(source1, handler3, "propertyName2"); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(17, callCount1); + Assert.Equal(4, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName2")); + Assert.Equal(17, callCount1); + Assert.Equal(4, callCount2); + Assert.Equal(1, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(19, callCount1); + Assert.Equal(5, callCount2); + Assert.Equal(2, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(null)); + Assert.Equal(21, callCount1); + Assert.Equal(6, callCount2); + Assert.Equal(3, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(21, callCount1); + Assert.Equal(6, callCount2); + Assert.Equal(3, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(21, callCount1); + Assert.Equal(6, callCount2); + Assert.Equal(3, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(null)); + Assert.Equal(21, callCount1); + Assert.Equal(6, callCount2); + Assert.Equal(3, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + + // Add another source. + PropertyChangedEventManager.AddHandler(source2, handler4, "propertyName"); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(23, callCount1); + Assert.Equal(7, callCount2); + Assert.Equal(3, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName2")); + Assert.Equal(23, callCount1); + Assert.Equal(7, callCount2); + Assert.Equal(4, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(25, callCount1); + Assert.Equal(8, callCount2); + Assert.Equal(5, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(null)); + Assert.Equal(27, callCount1); + Assert.Equal(9, callCount2); + Assert.Equal(6, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(27, callCount1); + Assert.Equal(9, callCount2); + Assert.Equal(6, callCount3); + Assert.Equal(1, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(27, callCount1); + Assert.Equal(9, callCount2); + Assert.Equal(6, callCount3); + Assert.Equal(2, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(null)); + Assert.Equal(27, callCount1); + Assert.Equal(9, callCount2); + Assert.Equal(6, callCount3); + Assert.Equal(3, callCount4); + Assert.Equal(0, callCount5); + + // Add another source (empty). + PropertyChangedEventManager.AddHandler(source2, handler5, ""); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(29, callCount1); + Assert.Equal(10, callCount2); + Assert.Equal(6, callCount3); + Assert.Equal(3, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName2")); + Assert.Equal(29, callCount1); + Assert.Equal(10, callCount2); + Assert.Equal(7, callCount3); + Assert.Equal(3, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(31, callCount1); + Assert.Equal(11, callCount2); + Assert.Equal(8, callCount3); + Assert.Equal(3, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(null)); + Assert.Equal(33, callCount1); + Assert.Equal(12, callCount2); + Assert.Equal(9, callCount3); + Assert.Equal(3, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(33, callCount1); + Assert.Equal(12, callCount2); + Assert.Equal(9, callCount3); + Assert.Equal(4, callCount4); + Assert.Equal(1, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(33, callCount1); + Assert.Equal(12, callCount2); + Assert.Equal(9, callCount3); + Assert.Equal(5, callCount4); + Assert.Equal(2, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(null)); + Assert.Equal(33, callCount1); + Assert.Equal(12, callCount2); + Assert.Equal(9, callCount3); + Assert.Equal(6, callCount4); + Assert.Equal(3, callCount5); + } + + [Fact] + public void AddHandler_NullSource_ThrowsArgumentNullException() + { + var target = new CustomWeakEventListener(); + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + // TODO: this should be NullReferenceException. + Assert.Throws(() => PropertyChangedEventManager.AddHandler(null, handler, "propertyName")); + + // Add again. + Assert.Throws(() => PropertyChangedEventManager.AddHandler(null, handler, "propertyName")); + } + + [Fact] + public void AddHandler_NullHandler_ThrowsArgumentNullException() + { + var source = new CustomNotifyPropertyChanged(); + Assert.Throws("handler", () => PropertyChangedEventManager.AddHandler(source, null, "propertyName")); + } + + [Fact] + public void AddHandler_NullPropertyName_ThrowsArgumentNullException() + { + var source = new CustomNotifyPropertyChanged(); + var target = new CustomWeakEventListener(); + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + // TODO: incorrect paramName. + Assert.Throws("key", () => PropertyChangedEventManager.AddHandler(source, handler, null)); + } + [Fact] + public void AddListener_InvokeWithHandler_CallsErrorsChanged() + { + var source = new CustomNotifyPropertyChanged(); + int callCount1 = 0; + var listener1 = new CustomWeakEventListener(); + listener1.ReceiveWeakEventAction += (actualManagerType, actualSender, actualE) => + { + Assert.Equal(typeof(PropertyChangedEventManager), actualManagerType); + Assert.Same(source, actualSender); + callCount1++; + return true; + }; + int callCount2 = 0; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction += (actualManagerType, actualSender, actualE) => + { + Assert.Equal(typeof(PropertyChangedEventManager), actualManagerType); + Assert.Same(source, actualSender); + callCount2++; + return true; + }; + + // Add. + PropertyChangedEventManager.AddListener(source, listener1, "propertyName"); + + // Call. + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Call null property name. + source.OnPropertyChanged(source, new PropertyChangedEventArgs(null)); + Assert.Equal(2, callCount1); + Assert.Equal(0, callCount2); + + // Call empty property name. + source.OnPropertyChanged(source, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + // Call invalid source. + source.OnPropertyChanged(listener1, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + source.OnPropertyChanged(new object(), new PropertyChangedEventArgs("propertyName")); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + source.OnPropertyChanged(null!, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + // Add again. + PropertyChangedEventManager.AddListener(source, listener1, "propertyName"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(5, callCount1); + Assert.Equal(0, callCount2); + + // Add another. + PropertyChangedEventManager.AddListener(source, listener2, "propertyName"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(7, callCount1); + Assert.Equal(1, callCount2); + + // Remove second. + PropertyChangedEventManager.RemoveListener(source, listener2, "propertyName"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(9, callCount1); + Assert.Equal(1, callCount2); + + // Remove first. + PropertyChangedEventManager.RemoveListener(source, listener1, "propertyName"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(10, callCount1); + Assert.Equal(1, callCount2); + + // Remove again. + PropertyChangedEventManager.RemoveListener(source, listener1, "propertyName"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(10, callCount1); + Assert.Equal(1, callCount2); + } + + [Theory] + [InlineData("propertyName")] + [InlineData("PROPERTYNAME")] + public void AddListener_InvokeMultipleTimes_Success(string propertyName) + { + var source1 = new CustomNotifyPropertyChanged(); + var source2 = new CustomNotifyPropertyChanged(); + var listener1 = new CustomWeakEventListener(); + int callCount1 = 0; + listener1.ReceiveWeakEventAction += (t, s, e) => + { + callCount1++; + return true; + }; + var listener2 = new CustomWeakEventListener(); + int callCount2 = 0; + listener2.ReceiveWeakEventAction += (t, s, e) => + { + callCount2++; + return true; + }; + var listener3 = new CustomWeakEventListener(); + int callCount3 = 0; + listener3.ReceiveWeakEventAction += (t, s, e) => + { + callCount3++; + return true; + }; + var listener4 = new CustomWeakEventListener(); + int callCount4 = 0; + listener4.ReceiveWeakEventAction += (t, s, e) => + { + callCount4++; + return true; + }; + var listener5 = new CustomWeakEventListener(); + int callCount5 = 0; + listener5.ReceiveWeakEventAction += (t, s, e) => + { + callCount5++; + return true; + }; + + // Add. + PropertyChangedEventManager.AddListener(source1, listener1, "propertyName"); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName2")); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(2, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(null)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(null)); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + + // Add again. + PropertyChangedEventManager.AddListener(source1, listener1, "propertyName"); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(5, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName2")); + Assert.Equal(5, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(7, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(null)); + Assert.Equal(9, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(9, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(9, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(null)); + Assert.Equal(9, callCount1); + Assert.Equal(0, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + + // Add another listener. + PropertyChangedEventManager.AddListener(source1, listener2, "propertyName"); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(11, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName2")); + Assert.Equal(11, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(13, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(null)); + Assert.Equal(15, callCount1); + Assert.Equal(3, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(15, callCount1); + Assert.Equal(3, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(15, callCount1); + Assert.Equal(3, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(null)); + Assert.Equal(15, callCount1); + Assert.Equal(3, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + + // Add another property name. + PropertyChangedEventManager.AddListener(source1, listener3, "propertyName2"); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(17, callCount1); + Assert.Equal(4, callCount2); + Assert.Equal(0, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName2")); + Assert.Equal(17, callCount1); + Assert.Equal(4, callCount2); + Assert.Equal(1, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(19, callCount1); + Assert.Equal(5, callCount2); + Assert.Equal(2, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(null)); + Assert.Equal(21, callCount1); + Assert.Equal(6, callCount2); + Assert.Equal(3, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(21, callCount1); + Assert.Equal(6, callCount2); + Assert.Equal(3, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(21, callCount1); + Assert.Equal(6, callCount2); + Assert.Equal(3, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(null)); + Assert.Equal(21, callCount1); + Assert.Equal(6, callCount2); + Assert.Equal(3, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + + // Add another source. + PropertyChangedEventManager.AddListener(source2, listener4, "propertyName"); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(23, callCount1); + Assert.Equal(7, callCount2); + Assert.Equal(3, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName2")); + Assert.Equal(23, callCount1); + Assert.Equal(7, callCount2); + Assert.Equal(4, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(25, callCount1); + Assert.Equal(8, callCount2); + Assert.Equal(5, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(null)); + Assert.Equal(27, callCount1); + Assert.Equal(9, callCount2); + Assert.Equal(6, callCount3); + Assert.Equal(0, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(27, callCount1); + Assert.Equal(9, callCount2); + Assert.Equal(6, callCount3); + Assert.Equal(1, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(27, callCount1); + Assert.Equal(9, callCount2); + Assert.Equal(6, callCount3); + Assert.Equal(2, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(null)); + Assert.Equal(27, callCount1); + Assert.Equal(9, callCount2); + Assert.Equal(6, callCount3); + Assert.Equal(3, callCount4); + Assert.Equal(0, callCount5); + + // Add another source (empty). + PropertyChangedEventManager.AddListener(source2, listener5, ""); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(29, callCount1); + Assert.Equal(10, callCount2); + Assert.Equal(6, callCount3); + Assert.Equal(3, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName2")); + Assert.Equal(29, callCount1); + Assert.Equal(10, callCount2); + Assert.Equal(7, callCount3); + Assert.Equal(3, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(31, callCount1); + Assert.Equal(11, callCount2); + Assert.Equal(8, callCount3); + Assert.Equal(3, callCount4); + Assert.Equal(0, callCount5); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs(null)); + Assert.Equal(33, callCount1); + Assert.Equal(12, callCount2); + Assert.Equal(9, callCount3); + Assert.Equal(3, callCount4); + Assert.Equal(0, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(propertyName)); + Assert.Equal(33, callCount1); + Assert.Equal(12, callCount2); + Assert.Equal(9, callCount3); + Assert.Equal(4, callCount4); + Assert.Equal(1, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(string.Empty)); + Assert.Equal(33, callCount1); + Assert.Equal(12, callCount2); + Assert.Equal(9, callCount3); + Assert.Equal(5, callCount4); + Assert.Equal(2, callCount5); + source2.OnPropertyChanged(source2, new PropertyChangedEventArgs(null)); + Assert.Equal(33, callCount1); + Assert.Equal(12, callCount2); + Assert.Equal(9, callCount3); + Assert.Equal(6, callCount4); + Assert.Equal(3, callCount5); + } + + [Fact] + public void AddListener_NullSource_ThrowsArgumentNullException() + { + var listener = new CustomWeakEventListener(); + Assert.Throws("source", () => PropertyChangedEventManager.AddListener(null, listener, "propertyName")); + + // Add again. + Assert.Throws("source", () => PropertyChangedEventManager.AddListener(null, listener, "propertyName")); + } + + [Fact] + public void AddListener_NullListener_ThrowsArgumentNullException() + { + var source = new CustomNotifyPropertyChanged(); + Assert.Throws("listener", () => PropertyChangedEventManager.AddListener(source, null, "propertyName")); + } + + [Fact] + public void AddListener_NullPropertyName_ThrowsArgumentNullException() + { + var source = new CustomNotifyPropertyChanged(); + var listener = new CustomWeakEventListener(); + // TODO: incorrect paramName. + Assert.Throws("key", () => PropertyChangedEventManager.AddListener(source, listener, null)); + } + + [Theory] + [InlineData("propertyName")] + [InlineData("PROPERTYNAME")] + public void RemoveHandler_Invoke_Success(string propertyName) + { + var source = new CustomNotifyPropertyChanged(); + var target = new CustomWeakEventListener(); + int callCount = 0; + target.HandlerAction += (s, e) => callCount++; + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + PropertyChangedEventManager.AddHandler(source, handler, "propertyName"); + + // Remove. + PropertyChangedEventManager.RemoveHandler(source, handler, propertyName); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(0, callCount); + + // Remove again. + PropertyChangedEventManager.RemoveHandler(source, handler, propertyName); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(0, callCount); + } + + [Fact] + public void RemoveHandler_InvokeMultipleNames_Success() + { + var source = new CustomNotifyPropertyChanged(); + var target1 = new CustomWeakEventListener(); + int callCount1 = 0; + target1.HandlerAction += (s, e) => callCount1++; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + int callCount2 = 0; + target2.HandlerAction += (s, e) => callCount2++; + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + var target3 = new CustomWeakEventListener(); + int callCount3 = 0; + target3.HandlerAction += (s, e) => callCount3++; + EventHandler handler3 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target3, nameof(CustomWeakEventListener.Handler)); + PropertyChangedEventManager.AddHandler(source, handler1, "propertyName1"); + PropertyChangedEventManager.AddHandler(source, handler2, "propertyName2"); + PropertyChangedEventManager.AddHandler(source, handler3, ""); + + // Remove. + PropertyChangedEventManager.RemoveHandler(source, handler1, "propertyName1"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs(null)); + Assert.Equal(0, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(1, callCount3); + + // Remove again. + PropertyChangedEventManager.RemoveHandler(source, handler1, "propertyName1"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs(null)); + Assert.Equal(0, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(2, callCount3); + + // Remove another. + PropertyChangedEventManager.RemoveHandler(source, handler3, ""); + source.OnPropertyChanged(source, new PropertyChangedEventArgs(null)); + Assert.Equal(0, callCount1); + Assert.Equal(3, callCount2); + Assert.Equal(2, callCount3); + + // Remove last. + PropertyChangedEventManager.RemoveHandler(source, handler2, "propertyName2"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs(null)); + Assert.Equal(0, callCount1); + Assert.Equal(3, callCount2); + Assert.Equal(2, callCount3); + } + + [Fact] + public void RemoveHandler_InvokeNoSource_Success() + { + var target = new CustomWeakEventListener(); + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + + // Remove. + PropertyChangedEventManager.RemoveHandler(null, handler, "propertyName"); + + // Remove again. + PropertyChangedEventManager.RemoveHandler(null, handler, "propertyName"); + } + + [Theory] + [InlineData("propertyName")] + [InlineData("")] + [InlineData(null)] + public void RemoveHandler_InvokeNoSuchSource_Nop(string? propertyName) + { + var source1 = new CustomNotifyPropertyChanged(); + var source2 = new CustomNotifyPropertyChanged(); + var target = new CustomWeakEventListener(); + int callCount = 0; + target.HandlerAction += (s, e) => callCount++; + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + PropertyChangedEventManager.AddHandler(source1, handler, "propertyName"); + + // Remove. + PropertyChangedEventManager.RemoveHandler(source2, handler, propertyName); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName")); + + // Remove again. + PropertyChangedEventManager.RemoveHandler(source2, handler, propertyName); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName")); + } + + [Fact] + public void RemoveHandler_InvokeNoSuchHandler_Nop() + { + var source = new CustomNotifyPropertyChanged(); + var target1 = new CustomWeakEventListener(); + int callCount1 = 0; + target1.HandlerAction += (s, e) => callCount1++; + EventHandler handler1 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + int callCount2 = 0; + target2.HandlerAction += (s, e) => callCount2++; + EventHandler handler2 = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + PropertyChangedEventManager.AddHandler(source, handler1, "propertyName"); + + // Remove. + PropertyChangedEventManager.RemoveHandler(source, handler2, "propertyName"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Remove again. + PropertyChangedEventManager.RemoveHandler(source, handler2, "propertyName"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(2, callCount1); + } + + [Theory] + [InlineData("propertyName2")] + [InlineData("")] + public void RemoveHandler_InvokeNoSuchPropertyName_Nop(string propertyName) + { + var source = new CustomNotifyPropertyChanged(); + var target = new CustomWeakEventListener(); + int callCount = 0; + target.HandlerAction += (s, e) => callCount++; + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + PropertyChangedEventManager.AddHandler(source, handler, "propertyName"); + + // Remove. + PropertyChangedEventManager.RemoveHandler(source, handler, propertyName); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(1, callCount); + + // Remove again. + PropertyChangedEventManager.RemoveHandler(source, handler, propertyName); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(2, callCount); + } + + [Fact] + public void RemoveHandler_NullPropertyName_ThrowsArgumentNullException() + { + var source = new CustomNotifyPropertyChanged(); + var target = new CustomWeakEventListener(); + int callCount = 0; + target.HandlerAction += (s, e) => callCount++; + EventHandler handler = (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + PropertyChangedEventManager.AddHandler(source, handler, "propertyName"); + + // Remove. + // TODO: incorrect paramName. + Assert.Throws("key", () => PropertyChangedEventManager.RemoveHandler(source, handler, null)); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(1, callCount); + + // Remove again. + // TODO: incorrect paramName. + Assert.Throws("key", () => PropertyChangedEventManager.RemoveHandler(source, handler, null)); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(2, callCount); + } + + [Fact] + public void RemoveHandler_NullHandler_ThrowsArgumentNullException() + { + var source = new CustomNotifyPropertyChanged(); + Assert.Throws("handler", () => PropertyChangedEventManager.RemoveHandler(source, null, "propertyName")); + } + + [Theory] + [InlineData("propertyName")] + [InlineData("PROPERTYNAME")] + public void RemoveListener_Invoke_Success(string propertyName) + { + var source = new CustomNotifyPropertyChanged(); + var listener = new CustomWeakEventListener(); + int callCount = 0; + listener.ReceiveWeakEventAction += (t, s, e) => + { + callCount++; + return true; + }; + PropertyChangedEventManager.AddListener(source, listener, "propertyName"); + + // Remove. + PropertyChangedEventManager.RemoveListener(source, listener, propertyName); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(0, callCount); + + // Remove again. + PropertyChangedEventManager.RemoveListener(source, listener, propertyName); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(0, callCount); + } + + [Fact] + public void RemoveListener_InvokeMultipleNames_Success() + { + var source = new CustomNotifyPropertyChanged(); + var listener1 = new CustomWeakEventListener(); + int callCount1 = 0; + listener1.ReceiveWeakEventAction += (t, s, e) => + { + callCount1++; + return true; + }; + var listener2 = new CustomWeakEventListener(); + int callCount2 = 0; + listener2.ReceiveWeakEventAction += (t, s, e) => + { + callCount2++; + return true; + }; + var listener3 = new CustomWeakEventListener(); + int callCount3 = 0; + listener3.ReceiveWeakEventAction += (t, s, e) => + { + callCount3++; + return true; + }; + PropertyChangedEventManager.AddListener(source, listener1, "propertyName1"); + PropertyChangedEventManager.AddListener(source, listener2, "propertyName2"); + PropertyChangedEventManager.AddListener(source, listener3, ""); + + // Remove. + PropertyChangedEventManager.RemoveListener(source, listener1, "propertyName1"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs(null)); + Assert.Equal(0, callCount1); + Assert.Equal(1, callCount2); + Assert.Equal(1, callCount3); + + // Remove again. + PropertyChangedEventManager.RemoveListener(source, listener1, "propertyName1"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs(null)); + Assert.Equal(0, callCount1); + Assert.Equal(2, callCount2); + Assert.Equal(2, callCount3); + + // Remove another. + PropertyChangedEventManager.RemoveListener(source, listener3, ""); + source.OnPropertyChanged(source, new PropertyChangedEventArgs(null)); + Assert.Equal(0, callCount1); + Assert.Equal(3, callCount2); + Assert.Equal(2, callCount3); + + // Remove last. + PropertyChangedEventManager.RemoveListener(source, listener2, "propertyName2"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs(null)); + Assert.Equal(0, callCount1); + Assert.Equal(3, callCount2); + Assert.Equal(2, callCount3); + } + + [Fact] + public void RemoveListener_InvokeNoSource_Success() + { + var listener = new CustomWeakEventListener(); + + // Remove. + PropertyChangedEventManager.RemoveListener(null, listener, "propertyName"); + + // Remove again. + PropertyChangedEventManager.RemoveListener(null, listener, "propertyName"); + } + + [Theory] + [InlineData("propertyName")] + [InlineData("")] + [InlineData(null)] + public void RemoveListener_InvokeNoSuchSource_Nop(string? propertyName) + { + var source1 = new CustomNotifyPropertyChanged(); + var source2 = new CustomNotifyPropertyChanged(); + var listener = new CustomWeakEventListener(); + int callCount = 0; + listener.ReceiveWeakEventAction += (t, s, e) => + { + callCount++; + return true; + }; + PropertyChangedEventManager.AddListener(source1, listener, "propertyName"); + + // Remove. + PropertyChangedEventManager.RemoveListener(source2, listener, propertyName); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName")); + + // Remove again. + PropertyChangedEventManager.RemoveListener(source2, listener, propertyName); + source1.OnPropertyChanged(source1, new PropertyChangedEventArgs("propertyName")); + } + + [Fact] + public void RemoveListener_InvokeNoSuchListener_Nop() + { + var source = new CustomNotifyPropertyChanged(); + var listener1 = new CustomWeakEventListener(); + int callCount1 = 0; + listener1.ReceiveWeakEventAction += (t, s, e) => + { + callCount1++; + return true; + }; + var listener2 = new CustomWeakEventListener(); + int callCount2 = 0; + listener2.ReceiveWeakEventAction += (t, s, e) => + { + callCount2++; + return true; + }; + PropertyChangedEventManager.AddListener(source, listener1, "propertyName"); + + // Remove. + PropertyChangedEventManager.RemoveListener(source, listener2, "propertyName"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Remove again. + PropertyChangedEventManager.RemoveListener(source, listener2, "propertyName"); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(2, callCount1); + } + + [Theory] + [InlineData("propertyName2")] + [InlineData("")] + public void RemoveListener_InvokeNoSuchPropertyName_Nop(string propertyName) + { + var source = new CustomNotifyPropertyChanged(); + var listener = new CustomWeakEventListener(); + int callCount = 0; + listener.ReceiveWeakEventAction += (t, s, e) => + { + callCount++; + return true; + }; + PropertyChangedEventManager.AddListener(source, listener, "propertyName"); + + // Remove. + PropertyChangedEventManager.RemoveListener(source, listener, propertyName); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(1, callCount); + + // Remove again. + PropertyChangedEventManager.RemoveListener(source, listener, propertyName); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(2, callCount); + } + + [Fact] + public void RemoveListener_NullPropertyName_ThrowsArgumentNullException() + { + var source = new CustomNotifyPropertyChanged(); + var listener = new CustomWeakEventListener(); + int callCount = 0; + listener.ReceiveWeakEventAction += (t, s, e) => + { + callCount++; + return true; + }; + PropertyChangedEventManager.AddListener(source, listener, "propertyName"); + + // Remove. + // TODO: incorrect paramName. + Assert.Throws("key", () => PropertyChangedEventManager.RemoveListener(source, listener, null)); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(1, callCount); + + // Remove again. + // TODO: incorrect paramName. + Assert.Throws("key", () => PropertyChangedEventManager.RemoveListener(source, listener, null)); + source.OnPropertyChanged(source, new PropertyChangedEventArgs("propertyName")); + Assert.Equal(2, callCount); + } + + [Fact] + public void RemoveListener_NullListener_ThrowsArgumentNullException() + { + var source = new CustomNotifyPropertyChanged(); + Assert.Throws("listener", () => PropertyChangedEventManager.RemoveListener(source, null, "propertyName")); + } + + private class CustomNotifyPropertyChanged : INotifyPropertyChanged + { + public event PropertyChangedEventHandler? PropertyChanged; + + public void OnPropertyChanged(object sender, PropertyChangedEventArgs e) => PropertyChanged?.Invoke(sender, e); + } + + private class CustomWeakEventListener : IWeakEventListener + { + public Func? ReceiveWeakEventAction { get; set; } + + public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (ReceiveWeakEventAction is null) + { + return true; + } + + return ReceiveWeakEventAction(managerType, sender, e); + } + + public Action? HandlerAction { get; set; } + + public void Handler(object sender, PropertyChangedEventArgs e) + { + HandlerAction?.Invoke(sender, e); + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/PropertyFilterAttributeTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/PropertyFilterAttributeTests.cs new file mode 100644 index 00000000000..6fdc2a65e02 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/PropertyFilterAttributeTests.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel.Tests; + +public class PropertyFilterAttributeTests +{ + [Theory] + [InlineData(PropertyFilterOptions.All)] + [InlineData(PropertyFilterOptions.SetValues)] + [InlineData(PropertyFilterOptions.Invalid)] + [InlineData(PropertyFilterOptions.None)] + [InlineData(PropertyFilterOptions.None - 1)] + public void Ctor_PropertyFilterOptions(PropertyFilterOptions filter) + { + var attribute = new PropertyFilterAttribute(filter); + Assert.Equal(filter, attribute.Filter); + } + + [Fact] + public void Default_Get_ReturnsExpected() + { + PropertyFilterAttribute attribute = PropertyFilterAttribute.Default; + Assert.Equal(PropertyFilterOptions.All, attribute.Filter); + Assert.Same(attribute, PropertyFilterAttribute.Default); + } + + public static IEnumerable Equals_TestData() + { + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.None), new PropertyFilterAttribute(PropertyFilterOptions.None), true }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.None), new PropertyFilterAttribute(PropertyFilterOptions.All), false }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.None), new PropertyFilterAttribute(PropertyFilterOptions.SetValues), false }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues), new PropertyFilterAttribute(PropertyFilterOptions.SetValues), true }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues), new PropertyFilterAttribute(PropertyFilterOptions.All), false }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues), new PropertyFilterAttribute(PropertyFilterOptions.None), false }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.All), new PropertyFilterAttribute(PropertyFilterOptions.All), true }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.All), new PropertyFilterAttribute(PropertyFilterOptions.None), false }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.All), new PropertyFilterAttribute(PropertyFilterOptions.SetValues), false }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.None), new object(), false }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.None), null, false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void Equals_Object_ReturnsExpected(PropertyFilterAttribute attribute, object other, bool expected) + { + Assert.Equal(expected, attribute.Equals(other)); + if (other is PropertyFilterAttribute) + { + Assert.Equal(expected, attribute.GetHashCode().Equals(other.GetHashCode())); + } + } + + public static IEnumerable Match_TestData() + { + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.None), new PropertyFilterAttribute(PropertyFilterOptions.None), true }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.None), new PropertyFilterAttribute(PropertyFilterOptions.All), true }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.None), new PropertyFilterAttribute(PropertyFilterOptions.SetValues), true }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.All), new PropertyFilterAttribute(PropertyFilterOptions.All), true }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.All), new PropertyFilterAttribute(PropertyFilterOptions.None), false }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.All), new PropertyFilterAttribute(PropertyFilterOptions.SetValues), false }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues), new PropertyFilterAttribute(PropertyFilterOptions.SetValues), true }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues), new PropertyFilterAttribute(PropertyFilterOptions.All), true }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues), new PropertyFilterAttribute(PropertyFilterOptions.None), false }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.None), new object(), false }; + yield return new object?[] { new PropertyFilterAttribute(PropertyFilterOptions.None), null, false }; + } + + [Theory] + [MemberData(nameof(Match_TestData))] + public void Match_Object_ReturnsExpected(PropertyFilterAttribute attribute, object value, bool expected) + { + Assert.Equal(expected, attribute.Match(value)); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/SortDescriptionCollectionTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/SortDescriptionCollectionTests.cs new file mode 100644 index 00000000000..ea9fdd6f4ff --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/SortDescriptionCollectionTests.cs @@ -0,0 +1,807 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Collections.Specialized; +using System.Linq; + +namespace System.ComponentModel.Tests; + +public class SortDescriptionCollectionTests +{ + [Fact] + public void Ctor_Default() + { + var collection = new SortDescriptionCollection(); + Assert.Empty(collection); + Assert.False(((IList)collection).IsFixedSize); + Assert.False(((IList)collection).IsReadOnly); + Assert.False(((IList)collection).IsSynchronized); + } + + [Fact] + public void CollectionChanged_INotifyCollectionChangedAddRemove_Success() + { + INotifyCollectionChanged collection = new SortDescriptionCollection(); + + int callCount = 0; + NotifyCollectionChangedEventHandler handler = (s, e) => callCount++; + ((INotifyCollectionChanged)collection).CollectionChanged += handler; + Assert.Equal(0, callCount); + + ((INotifyCollectionChanged)collection).CollectionChanged -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + ((INotifyCollectionChanged)collection).CollectionChanged -= handler; + Assert.Equal(0, callCount); + + // Add null. + ((INotifyCollectionChanged)collection).CollectionChanged += null; + Assert.Equal(0, callCount); + + // Remove null. + ((INotifyCollectionChanged)collection).CollectionChanged -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void Item_Set_GetReturnsExpected() + { + var collection = new SortDescriptionCollection(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Descending); + + collection.Add(description1); + + // Set. + collection[0] = description2; + Assert.Single(collection); + Assert.Equal(description2, collection[0]); + Assert.True(collection[0].IsSealed); + } + + [Fact] + public void Item_SetWithHandler_GetReturnsExpected() + { + var collection = new SortDescriptionCollection(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Descending); + var description3 = new SortDescription("Name3", ListSortDirection.Descending); + + collection.Add(description1); + + var events = new List(); + int callCount = 0; + NotifyCollectionChangedEventHandler handler = (s, e) => + { + Assert.Same(collection, s); + events.Add(e); + callCount++; + }; + ((INotifyCollectionChanged)collection).CollectionChanged += handler; + + // Set. + collection[0] = description2; + Assert.Single(collection); + Assert.Equal(description2, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(2, callCount); + Assert.Equal(NotifyCollectionChangedAction.Remove, events[0].Action); + Assert.Null(events[0].NewItems); + Assert.Equal(new[] { description1 }, events[0].OldItems!.Cast()); + Assert.Equal(-1, events[0].NewStartingIndex); + Assert.Equal(0, events[0].OldStartingIndex); + Assert.Equal(NotifyCollectionChangedAction.Add, events[1].Action); + Assert.Equal(new[] { description2 }, events[1].NewItems!.Cast()); + Assert.Equal(0, events[1].NewStartingIndex); + Assert.Equal(-1, events[1].OldStartingIndex); + + // Remove handler. + ((INotifyCollectionChanged)collection).CollectionChanged -= handler; + collection[0] = description3; + Assert.Single(collection); + Assert.Equal(description3, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(2, callCount); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + public void Item_GetInvalidIndex_ThrowsArgumentOutOfRangeException(int index) + { + var collection = new SortDescriptionCollection(); + Assert.Throws("index", () => collection[index]); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + public void Item_SetInvalidIndex_ThrowsArgumentOutOfRangeException(int index) + { + var collection = new SortDescriptionCollection(); + var description = new SortDescription("Name", ListSortDirection.Ascending); + Assert.Throws("index", () => collection[index] = description); + } + + [Fact] + public void Add_Invoke_Success() + { + var collection = new SortDescriptionCollection(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Descending); + + // Add. + collection.Add(description1); + Assert.Single(collection); + Assert.Equal(description1, collection[0]); + Assert.True(collection[0].IsSealed); + + // Add again. + collection.Add(description2); + Assert.Equal(2, collection.Count); + Assert.Equal(description1, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(description2, collection[1]); + Assert.True(collection[1].IsSealed); + } + + [Fact] + public void Add_InvokeWithHandler_CallsCollectionChanged() + { + var collection = new SortDescriptionCollection(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Descending); + var description3 = new SortDescription("Name3", ListSortDirection.Descending); + + var events = new List(); + int callCount = 0; + NotifyCollectionChangedEventHandler handler = (s, e) => + { + Assert.Same(collection, s); + events.Add(e); + callCount++; + }; + ((INotifyCollectionChanged)collection).CollectionChanged += handler; + + // Add. + collection.Add(description1); + Assert.Single(collection); + Assert.Equal(description1, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(1, callCount); + Assert.Equal(NotifyCollectionChangedAction.Add, events[0].Action); + Assert.Equal(new[] { description1 }, events[0].NewItems!.Cast()); + Assert.Null(events[0].OldItems); + Assert.Equal(0, events[0].NewStartingIndex); + Assert.Equal(-1, events[0].OldStartingIndex); + + // Add again. + collection.Add(description2); + Assert.Equal(2, collection.Count); + Assert.Equal(description1, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(description2, collection[1]); + Assert.True(collection[1].IsSealed); + Assert.Equal(2, callCount); + Assert.Equal(NotifyCollectionChangedAction.Add, events[1].Action); + Assert.Equal(new[] { description1 }, events[0].NewItems!.Cast()); + Assert.Null(events[1].OldItems); + Assert.Equal(1, events[1].NewStartingIndex); + Assert.Equal(-1, events[1].OldStartingIndex); + + // Remove handler. + ((INotifyCollectionChanged)collection).CollectionChanged -= handler; + collection.Add(description3); + Assert.Equal(3, collection.Count); + Assert.Equal(description1, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(description2, collection[1]); + Assert.True(collection[1].IsSealed); + Assert.Equal(description3, collection[2]); + Assert.True(collection[2].IsSealed); + Assert.Equal(2, callCount); + } + + [Fact] + public void Insert_Invoke_Success() + { + var collection = new SortDescriptionCollection(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Descending); + + // Add. + collection.Insert(0, description1); + Assert.Single(collection); + Assert.Equal(description1, collection[0]); + Assert.True(collection[0].IsSealed); + + // Add again. + collection.Insert(0, description2); + Assert.Equal(2, collection.Count); + Assert.Equal(description2, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(description1, collection[1]); + Assert.True(collection[1].IsSealed); + } + + [Fact] + public void Insert_InvokeWithHandler_CallsCollectionChanged() + { + var collection = new SortDescriptionCollection(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Descending); + var description3 = new SortDescription("Name3", ListSortDirection.Descending); + + var events = new List(); + int callCount = 0; + NotifyCollectionChangedEventHandler handler = (s, e) => + { + Assert.Same(collection, s); + events.Add(e); + callCount++; + }; + ((INotifyCollectionChanged)collection).CollectionChanged += handler; + + // Add. + collection.Insert(0, description1); + Assert.Single(collection); + Assert.Equal(description1, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(1, callCount); + Assert.Equal(NotifyCollectionChangedAction.Add, events[0].Action); + Assert.Equal(new[] { description1 }, events[0].NewItems!.Cast()); + Assert.Null(events[0].OldItems); + Assert.Equal(0, events[0].NewStartingIndex); + Assert.Equal(-1, events[0].OldStartingIndex); + + // Add again. + collection.Insert(0, description2); + Assert.Equal(2, collection.Count); + Assert.Equal(description2, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(description1, collection[1]); + Assert.True(collection[1].IsSealed); + Assert.Equal(2, callCount); + Assert.Equal(NotifyCollectionChangedAction.Add, events[1].Action); + Assert.Equal(new[] { description2 }, events[1].NewItems!.Cast()); + Assert.Null(events[1].OldItems); + Assert.Equal(0, events[1].NewStartingIndex); + Assert.Equal(-1, events[1].OldStartingIndex); + + // Remove handler. + ((INotifyCollectionChanged)collection).CollectionChanged -= handler; + collection.Insert(2, description3); + Assert.Equal(3, collection.Count); + Assert.Equal(description2, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(description1, collection[1]); + Assert.True(collection[1].IsSealed); + Assert.Equal(description3, collection[2]); + Assert.True(collection[2].IsSealed); + Assert.Equal(2, callCount); + } + + [Theory] + [InlineData(-1)] + [InlineData(1)] + public void Insert_InvalidIndex_ThrowsArgumentOutOfRangeException(int index) + { + var collection = new SortDescriptionCollection(); + var description = new SortDescription("Name", ListSortDirection.Ascending); + Assert.Throws("index", () => collection.Insert(index, description)); + } + + [Fact] + public void InsertItem_Invoke_Success() + { + var collection = new SubSortDescriptionCollection(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Descending); + + // Add. + collection.InsertItem(0, description1); + Assert.Single(collection); + Assert.Equal(description1, collection[0]); + Assert.True(collection[0].IsSealed); + + // Add again. + collection.InsertItem(0, description2); + Assert.Equal(2, collection.Count); + Assert.Equal(description2, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(description1, collection[1]); + Assert.True(collection[1].IsSealed); + } + + [Fact] + public void InsertItem_InvokeWithHandler_CallsCollectionChanged() + { + var collection = new SubSortDescriptionCollection(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Descending); + var description3 = new SortDescription("Name3", ListSortDirection.Descending); + + var events = new List(); + int callCount = 0; + NotifyCollectionChangedEventHandler handler = (s, e) => + { + Assert.Same(collection, s); + events.Add(e); + callCount++; + }; + ((INotifyCollectionChanged)collection).CollectionChanged += handler; + + // Add. + collection.InsertItem(0, description1); + Assert.Single(collection); + Assert.Equal(description1, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(1, callCount); + Assert.Equal(NotifyCollectionChangedAction.Add, events[0].Action); + Assert.Equal(new[] { description1 }, events[0].NewItems!.Cast()); + Assert.Null(events[0].OldItems); + Assert.Equal(0, events[0].NewStartingIndex); + Assert.Equal(-1, events[0].OldStartingIndex); + + // Add again. + collection.InsertItem(0, description2); + Assert.Equal(2, collection.Count); + Assert.Equal(description2, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(description1, collection[1]); + Assert.True(collection[1].IsSealed); + Assert.Equal(2, callCount); + Assert.Equal(NotifyCollectionChangedAction.Add, events[1].Action); + Assert.Equal(new[] { description2 }, events[1].NewItems!.Cast()); + Assert.Null(events[1].OldItems); + Assert.Equal(0, events[1].NewStartingIndex); + Assert.Equal(-1, events[1].OldStartingIndex); + + // Remove handler. + ((INotifyCollectionChanged)collection).CollectionChanged -= handler; + collection.InsertItem(2, description3); + Assert.Equal(3, collection.Count); + Assert.Equal(description2, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(description1, collection[1]); + Assert.True(collection[1].IsSealed); + Assert.Equal(description3, collection[2]); + Assert.True(collection[2].IsSealed); + Assert.Equal(2, callCount); + } + + [Theory] + [InlineData(-1)] + [InlineData(1)] + public void InsertItem_InvalidIndex_ThrowsArgumentOutOfRangeException(int index) + { + var collection = new SubSortDescriptionCollection(); + var description = new SortDescription("Name", ListSortDirection.Ascending); + Assert.Throws("index", () => collection.InsertItem(index, description)); + } + + [Fact] + public void Clear_Invoke_Success() + { + var collection = new SortDescriptionCollection(); + + // Clear empty. + collection.Clear(); + Assert.Empty(collection); + + collection.Add(new SortDescription("Name", ListSortDirection.Ascending)); + + // Clear. + collection.Clear(); + Assert.Empty(collection); + + // Clear again. + collection.Clear(); + } + + [Fact] + public void Clear_InvokeWithHandler_CallsCollectionChanged() + { + var collection = new SortDescriptionCollection(); + var events = new List(); + int callCount = 0; + NotifyCollectionChangedEventHandler handler = (s, e) => + { + Assert.Same(collection, s); + events.Add(e); + callCount++; + }; + ((INotifyCollectionChanged)collection).CollectionChanged += handler; + + // Clear empty. + collection.Clear(); + Assert.Empty(collection); + Assert.Equal(1, callCount); + Assert.Equal(NotifyCollectionChangedAction.Reset, events[0].Action); + Assert.Null(events[0].NewItems); + Assert.Null(events[0].OldItems); + Assert.Equal(-1, events[0].NewStartingIndex); + Assert.Equal(-1, events[0].OldStartingIndex); + + collection.Add(new SortDescription("Name", ListSortDirection.Ascending)); + Assert.Equal(2, callCount); + + // Clear. + collection.Clear(); + Assert.Empty(collection); + Assert.Equal(3, callCount); + Assert.Equal(NotifyCollectionChangedAction.Reset, events[2].Action); + Assert.Null(events[2].NewItems); + Assert.Null(events[2].OldItems); + Assert.Equal(-1, events[2].NewStartingIndex); + Assert.Equal(-1, events[2].OldStartingIndex); + + // Clear again. + collection.Clear(); + Assert.Empty(collection); + Assert.Equal(4, callCount); + Assert.Equal(NotifyCollectionChangedAction.Reset, events[3].Action); + Assert.Null(events[3].NewItems); + Assert.Null(events[3].OldItems); + Assert.Equal(-1, events[3].NewStartingIndex); + Assert.Equal(-1, events[3].OldStartingIndex); + + // Remove handler. + ((INotifyCollectionChanged)collection).CollectionChanged -= handler; + collection.Clear(); + Assert.Equal(4, callCount); + } + + [Fact] + public void ClearItems_Invoke_Success() + { + var collection = new SubSortDescriptionCollection(); + + // Clear empty. + collection.ClearItems(); + Assert.Empty(collection); + + collection.Add(new SortDescription("Name", ListSortDirection.Ascending)); + + // Clear. + collection.ClearItems(); + Assert.Empty(collection); + + // Clear again. + collection.ClearItems(); + } + + [Fact] + public void ClearItems_InvokeWithHandler_CallsCollectionChanged() + { + var collection = new SubSortDescriptionCollection(); + var events = new List(); + int callCount = 0; + NotifyCollectionChangedEventHandler handler = (s, e) => + { + Assert.Same(collection, s); + events.Add(e); + callCount++; + }; + ((INotifyCollectionChanged)collection).CollectionChanged += handler; + + // Clear empty. + collection.ClearItems(); + Assert.Empty(collection); + Assert.Equal(1, callCount); + Assert.Equal(NotifyCollectionChangedAction.Reset, events[0].Action); + Assert.Null(events[0].NewItems); + Assert.Null(events[0].OldItems); + Assert.Equal(-1, events[0].NewStartingIndex); + Assert.Equal(-1, events[0].OldStartingIndex); + + collection.Add(new SortDescription("Name", ListSortDirection.Ascending)); + Assert.Equal(2, callCount); + + // Clear. + collection.ClearItems(); + Assert.Empty(collection); + Assert.Equal(3, callCount); + Assert.Equal(NotifyCollectionChangedAction.Reset, events[2].Action); + Assert.Null(events[2].NewItems); + Assert.Null(events[2].OldItems); + Assert.Equal(-1, events[2].NewStartingIndex); + Assert.Equal(-1, events[2].OldStartingIndex); + + // Clear again. + collection.ClearItems(); + Assert.Empty(collection); + Assert.Equal(4, callCount); + Assert.Equal(NotifyCollectionChangedAction.Reset, events[3].Action); + Assert.Null(events[3].NewItems); + Assert.Null(events[3].OldItems); + Assert.Equal(-1, events[3].NewStartingIndex); + Assert.Equal(-1, events[3].OldStartingIndex); + + // Remove handler. + ((INotifyCollectionChanged)collection).CollectionChanged -= handler; + collection.ClearItems(); + Assert.Equal(4, callCount); + } + + [Fact] + public void RemoveAt_Invoke_Success() + { + var collection = new SubSortDescriptionCollection(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Descending); + + collection.Add(description1); + collection.Add(description2); + + // Remove last. + collection.RemoveAt(1); + Assert.Single(collection); + Assert.Equal(description1, collection[0]); + Assert.True(collection[0].IsSealed); + + // Remove first. + collection.RemoveAt(0); + Assert.Empty(collection); + } + + [Fact] + public void RemoveAt_InvokeWithHandler_CallsCollectionChanged() + { + var collection = new SubSortDescriptionCollection(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Descending); + var description3 = new SortDescription("Name3", ListSortDirection.Descending); + + collection.Add(description1); + collection.Add(description2); + collection.Add(description3); + + var events = new List(); + int callCount = 0; + NotifyCollectionChangedEventHandler handler = (s, e) => + { + Assert.Same(collection, s); + events.Add(e); + callCount++; + }; + ((INotifyCollectionChanged)collection).CollectionChanged += handler; + + // Remove middle. + collection.RemoveAt(1); + Assert.Equal(2, collection.Count); + Assert.Equal(description1, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(description3, collection[1]); + Assert.True(collection[1].IsSealed); + Assert.Equal(1, callCount); + Assert.Equal(NotifyCollectionChangedAction.Remove, events[0].Action); + Assert.Null(events[0].NewItems); + Assert.Equal(new[] { description2 }, events[0].OldItems!.Cast()); + Assert.Equal(-1, events[0].NewStartingIndex); + Assert.Equal(1, events[0].OldStartingIndex); + + // Remove last. + collection.RemoveAt(1); + Assert.Single(collection); + Assert.Equal(description1, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(2, callCount); + Assert.Equal(NotifyCollectionChangedAction.Remove, events[1].Action); + Assert.Null(events[1].NewItems); + Assert.Equal(new[] { description3 }, events[1].OldItems!.Cast()); + Assert.Equal(-1, events[1].NewStartingIndex); + Assert.Equal(1, events[1].OldStartingIndex); + + // Remove handler. + ((INotifyCollectionChanged)collection).CollectionChanged -= handler; + collection.RemoveAt(0); + Assert.Empty(collection); + Assert.Equal(2, callCount); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + public void RemoveAt_InvalidIndex_ThrowsArgumentOutOfRangeException(int index) + { + var collection = new SubSortDescriptionCollection(); + Assert.Throws("index", () => collection.RemoveAt(index)); + } + + [Fact] + public void RemoveItem_Invoke_Success() + { + var collection = new SubSortDescriptionCollection(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Descending); + + collection.Add(description1); + collection.Add(description2); + + // Remove last. + collection.RemoveItem(1); + Assert.Single(collection); + Assert.Equal(description1, collection[0]); + Assert.True(collection[0].IsSealed); + + // Remove first. + collection.RemoveItem(0); + Assert.Empty(collection); + } + + [Fact] + public void RemoveItem_InvokeWithHandler_CallsCollectionChanged() + { + var collection = new SubSortDescriptionCollection(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Descending); + var description3 = new SortDescription("Name3", ListSortDirection.Descending); + + collection.Add(description1); + collection.Add(description2); + collection.Add(description3); + + var events = new List(); + int callCount = 0; + NotifyCollectionChangedEventHandler handler = (s, e) => + { + Assert.Same(collection, s); + events.Add(e); + callCount++; + }; + ((INotifyCollectionChanged)collection).CollectionChanged += handler; + + // Remove middle. + collection.RemoveItem(1); + Assert.Equal(2, collection.Count); + Assert.Equal(description1, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(description3, collection[1]); + Assert.True(collection[1].IsSealed); + Assert.Equal(1, callCount); + Assert.Equal(NotifyCollectionChangedAction.Remove, events[0].Action); + Assert.Null(events[0].NewItems); + Assert.Equal(new[] { description2 }, events[0].OldItems!.Cast()); + Assert.Equal(-1, events[0].NewStartingIndex); + Assert.Equal(1, events[0].OldStartingIndex); + + // Remove last. + collection.RemoveItem(1); + Assert.Single(collection); + Assert.Equal(description1, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(2, callCount); + Assert.Equal(NotifyCollectionChangedAction.Remove, events[1].Action); + Assert.Null(events[1].NewItems); + Assert.Equal(new[] { description3 }, events[1].OldItems!.Cast()); + Assert.Equal(-1, events[1].NewStartingIndex); + Assert.Equal(1, events[1].OldStartingIndex); + + // Remove handler. + ((INotifyCollectionChanged)collection).CollectionChanged -= handler; + collection.RemoveItem(0); + Assert.Empty(collection); + Assert.Equal(2, callCount); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + public void RemoveItem_InvalidIndex_ThrowsArgumentOutOfRangeException(int index) + { + var collection = new SubSortDescriptionCollection(); + Assert.Throws("index", () => collection.RemoveItem(index)); + } + + + [Fact] + public void SetItem_Invoke_GetReturnsExpected() + { + var collection = new SubSortDescriptionCollection(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Descending); + + collection.Add(description1); + + // Set. + collection.SetItem(0, description2); + Assert.Single(collection); + Assert.Equal(description2, collection[0]); + Assert.True(collection[0].IsSealed); + } + + [Fact] + public void SetItem_WithHandler_GetReturnsExpected() + { + var collection = new SubSortDescriptionCollection(); + var description1 = new SortDescription("Name1", ListSortDirection.Ascending); + var description2 = new SortDescription("Name2", ListSortDirection.Descending); + var description3 = new SortDescription("Name3", ListSortDirection.Descending); + + collection.Add(description1); + + var events = new List(); + int callCount = 0; + NotifyCollectionChangedEventHandler handler = (s, e) => + { + Assert.Same(collection, s); + events.Add(e); + callCount++; + }; + ((INotifyCollectionChanged)collection).CollectionChanged += handler; + + // Set. + collection.SetItem(0, description2); + Assert.Single(collection); + Assert.Equal(description2, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(2, callCount); + Assert.Equal(NotifyCollectionChangedAction.Remove, events[0].Action); + Assert.Null(events[0].NewItems); + Assert.Equal(new[] { description1 }, events[0].OldItems!.Cast()); + Assert.Equal(-1, events[0].NewStartingIndex); + Assert.Equal(0, events[0].OldStartingIndex); + Assert.Equal(NotifyCollectionChangedAction.Add, events[1].Action); + Assert.Equal(new[] { description2 }, events[1].NewItems!.Cast()); + Assert.Equal(0, events[1].NewStartingIndex); + Assert.Equal(-1, events[1].OldStartingIndex); + + // Remove handler. + ((INotifyCollectionChanged)collection).CollectionChanged -= handler; + collection.SetItem(0, description3); + Assert.Single(collection); + Assert.Equal(description3, collection[0]); + Assert.True(collection[0].IsSealed); + Assert.Equal(2, callCount); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + public void SetItem_InvalidIndex_ThrowsArgumentOutOfRangeException(int index) + { + var collection = new SubSortDescriptionCollection(); + var description = new SortDescription("Name", ListSortDirection.Ascending); + Assert.Throws("index", () => collection.SetItem(index, description)); + } + + [Fact] + public void Empty_Get_ReturnsExpected() + { + SortDescriptionCollection collection = SortDescriptionCollection.Empty; + Assert.Empty(collection); + Assert.Same(SortDescriptionCollection.Empty, collection); + Assert.True(((IList)collection).IsFixedSize); + Assert.True(((IList)collection).IsReadOnly); + Assert.False(((IList)collection).IsSynchronized); + } + + [Fact] + public void Empty_Modify_ThrowsNotSupportedException() + { + SortDescriptionCollection collection = SortDescriptionCollection.Empty; + Assert.Throws(() => collection.Add(new SortDescription("Name", ListSortDirection.Ascending))); + Assert.Throws(() => collection.Insert(0, new SortDescription("Name", ListSortDirection.Ascending))); + Assert.Throws(() => collection.Clear()); + } + + private class SubSortDescriptionCollection : SortDescriptionCollection + { + public new void ClearItems() => base.ClearItems(); + + public new void InsertItem(int index, SortDescription item) => base.InsertItem(index, item); + + public new void RemoveItem(int index) => base.RemoveItem(index); + + public new void SetItem(int index, SortDescription item) => base.SetItem(index, item); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/SortDescriptionTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/SortDescriptionTests.cs new file mode 100644 index 00000000000..8bcd23879d3 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/ComponentModel/SortDescriptionTests.cs @@ -0,0 +1,134 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel.Tests; + +public class SortDescriptionTests +{ + [Fact] + public void Ctor_Default() + { + var description = new SortDescription(); + Assert.Null(description.PropertyName); + Assert.Equal(ListSortDirection.Ascending, description.Direction); + Assert.False(description.IsSealed); + } + + [Theory] + [InlineData(null, ListSortDirection.Ascending)] + [InlineData("", ListSortDirection.Ascending)] + [InlineData("Name", ListSortDirection.Ascending)] + [InlineData("Name", ListSortDirection.Descending)] + public void Ctor_String_ListSortDirection(string? propertyName, ListSortDirection direction) + { + var description = new SortDescription(propertyName, direction); + Assert.Equal(direction, description.Direction); + Assert.Equal(propertyName, description.PropertyName); + Assert.False(description.IsSealed); + } + + [Theory] + [InlineData(ListSortDirection.Ascending - 1)] + [InlineData(ListSortDirection.Descending + 1)] + public void Ctor_InvalidDirection_ThrowsInvalidEnumArgumentException(ListSortDirection direction) + { + Assert.Throws("direction", () => new SortDescription("Name", direction)); + } + + [Theory] + [InlineData(ListSortDirection.Ascending)] + [InlineData(ListSortDirection.Descending)] + public void Direction_Set_GetReturnsExpected(ListSortDirection value) + { + var description = new SortDescription + { + Direction = value + }; + Assert.Equal(value, description.Direction); + + // Set same. + description.Direction = value; + Assert.Equal(value, description.Direction); + } + + [Theory] + [InlineData(ListSortDirection.Ascending - 1)] + [InlineData(ListSortDirection.Descending + 1)] + public void Direction_SetInvalid_ThrowsInvalidEnumArgumentException(ListSortDirection value) + { + var description = new SortDescription(); + Assert.Throws("value", () => description.Direction = value); + } + + [Theory] + [InlineData(ListSortDirection.Ascending - 1)] + [InlineData(ListSortDirection.Ascending)] + [InlineData(ListSortDirection.Descending)] + [InlineData(ListSortDirection.Descending + 1)] + public void Direction_SetSealed_ThrowsInvalidOperationException(ListSortDirection value) + { + var collection = new SortDescriptionCollection(); + collection.Add(new SortDescription()); + + SortDescription description = collection[0]; + Assert.Throws(() => description.Direction = value); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("Name")] + public void PropertyName_Set_GetReturnsExpected(string? value) + { + var description = new SortDescription + { + PropertyName = value + }; + Assert.Equal(value, description.PropertyName); + + // Set same. + description.PropertyName = value; + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("Name")] + public void PropertyName_SetSealed_ThrowsInvalidOperationException(string? value) + { + var collection = new SortDescriptionCollection(); + collection.Add(new SortDescription()); + + SortDescription description = collection[0]; + Assert.Throws(() => description.PropertyName = value); + } + + public static IEnumerable Equals_TestData() + { + yield return new object?[] { new SortDescription("Name", ListSortDirection.Ascending), new SortDescription("Name", ListSortDirection.Ascending), true }; + yield return new object?[] { new SortDescription("Name", ListSortDirection.Ascending), new SortDescription(null, ListSortDirection.Ascending), false }; + yield return new object?[] { new SortDescription("Name", ListSortDirection.Ascending), new SortDescription("", ListSortDirection.Ascending), false }; + yield return new object?[] { new SortDescription("Name", ListSortDirection.Ascending), new SortDescription("Name2", ListSortDirection.Ascending), false }; + yield return new object?[] { new SortDescription("Name", ListSortDirection.Ascending), new SortDescription("Name", ListSortDirection.Descending), false }; + yield return new object?[] { new SortDescription(null, ListSortDirection.Descending), new SortDescription(null, ListSortDirection.Descending), true }; + yield return new object?[] { new SortDescription(null, ListSortDirection.Descending), new SortDescription("", ListSortDirection.Descending), false }; + yield return new object?[] { new SortDescription(null, ListSortDirection.Descending), new SortDescription("Name2", ListSortDirection.Descending), false }; + yield return new object?[] { new SortDescription(null, ListSortDirection.Descending), new SortDescription(null, ListSortDirection.Ascending), false }; + yield return new object?[] { new SortDescription("Name", ListSortDirection.Ascending), new object(), false }; + yield return new object?[] { new SortDescription("Name", ListSortDirection.Ascending), null, false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void Equals_Object_ReturnsExpected(SortDescription description, object other, bool expected) + { + Assert.Equal(expected, description.Equals(other)); + if (other is SortDescription otherDescription) + { + Assert.Equal(expected, description.GetHashCode().Equals(otherDescription.GetHashCode())); + Assert.Equal(expected, description == otherDescription); + Assert.Equal(!expected, description != otherDescription); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Diagnostics/PresentationTraceSourcesTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Diagnostics/PresentationTraceSourcesTests.cs new file mode 100644 index 00000000000..fae01cb07eb --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Diagnostics/PresentationTraceSourcesTests.cs @@ -0,0 +1,178 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Windows; + +namespace System.Diagnostics.Tests; + +public class PresentationTraceSourcesTests +{ + [Fact] + public void TraceLevelProperty_Get_ReturnsExpected() + { + DependencyProperty property = PresentationTraceSources.TraceLevelProperty; + Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Equal(PresentationTraceLevel.None, property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal("TraceLevel", property.Name); + Assert.Equal(typeof(PresentationTraceSources), property.OwnerType); + Assert.Equal(typeof(PresentationTraceLevel), property.PropertyType); + Assert.False(property.ReadOnly); + Assert.Null(property.ValidateValueCallback); + Assert.Same(property, PresentationTraceSources.TraceLevelProperty); + } + + public static IEnumerable Element_TestData() + { + yield return new object?[] { new object() }; + yield return new object?[] { null }; + } + + [Theory] + [MemberData(nameof(Element_TestData))] + public void GetTraceLevel_Get_ReturnsExpected(object element) + { + PresentationTraceLevel value = PresentationTraceSources.GetTraceLevel(element); + Assert.True(Enum.IsDefined(value)); + Assert.Equal(value, PresentationTraceSources.GetTraceLevel(element)); + } + + [Theory] + [MemberData(nameof(Element_TestData))] + public void Refresh_Invoke_Success(object element) + { + PresentationTraceLevel value = PresentationTraceSources.GetTraceLevel(element); + PresentationTraceSources.Refresh(); + Assert.Equal(value, PresentationTraceSources.GetTraceLevel(element)); + } + + public static IEnumerable SetTraceLevel_TestData() + { + yield return new object?[] { null, PresentationTraceLevel.None, PresentationTraceLevel.None }; + yield return new object?[] { new object(), PresentationTraceLevel.Low, PresentationTraceLevel.Low }; + yield return new object?[] { new object(), PresentationTraceLevel.None - 1, PresentationTraceLevel.None }; + yield return new object?[] { new object(), PresentationTraceLevel.High + 1, PresentationTraceLevel.High + 1 }; + } + + [Theory] + [MemberData(nameof(SetTraceLevel_TestData))] + public void SetTraceLevel_Invoke_GetReturnsExpected(object element, PresentationTraceLevel traceLevel, PresentationTraceLevel expected) + { + PresentationTraceSources.SetTraceLevel(element, traceLevel); + Assert.Equal(expected, PresentationTraceSources.GetTraceLevel(element)); + } + + [Fact] + public void AnimationSource_Get_ReturnsExpected() + { + TraceSource source = PresentationTraceSources.AnimationSource; + Assert.NotNull(source); + Assert.Same(source, PresentationTraceSources.AnimationSource); + Assert.Equal("System.Windows.Media.Animation", source.Name); + Assert.True(Enum.IsDefined(source.Switch.Level)); + } + + [Fact] + public void DataBindingSource_Get_ReturnsExpected() + { + TraceSource source = PresentationTraceSources.DataBindingSource; + Assert.NotNull(source); + Assert.Same(source, PresentationTraceSources.DataBindingSource); + Assert.Equal("System.Windows.Data", source.Name); + Assert.True(Enum.IsDefined(source.Switch.Level)); + } + + [Fact] + public void DependencyPropertySource_Get_ReturnsExpected() + { + TraceSource source = PresentationTraceSources.DependencyPropertySource; + Assert.NotNull(source); + Assert.Same(source, PresentationTraceSources.DependencyPropertySource); + Assert.Equal("System.Windows.DependencyProperty", source.Name); + Assert.True(Enum.IsDefined(source.Switch.Level)); + } + + [Fact] + public void DocumentsSource_Get_ReturnsExpected() + { + TraceSource source = PresentationTraceSources.DocumentsSource; + Assert.NotNull(source); + Assert.Same(source, PresentationTraceSources.DocumentsSource); + Assert.Equal("System.Windows.Documents", source.Name); + Assert.True(Enum.IsDefined(source.Switch.Level)); + } + + [Fact] + public void FreezableSource_Get_ReturnsExpected() + { + TraceSource source = PresentationTraceSources.FreezableSource; + Assert.NotNull(source); + Assert.Same(source, PresentationTraceSources.FreezableSource); + Assert.Equal("System.Windows.Freezable", source.Name); + Assert.True(Enum.IsDefined(source.Switch.Level)); + } + + [Fact] + public void HwndHostSource_Get_ReturnsExpected() + { + TraceSource source = PresentationTraceSources.HwndHostSource; + Assert.NotNull(source); + Assert.Same(source, PresentationTraceSources.HwndHostSource); + Assert.Equal("System.Windows.Interop.HwndHost", source.Name); + Assert.True(Enum.IsDefined(source.Switch.Level)); + } + + [Fact] + public void MarkupSource_Get_ReturnsExpected() + { + TraceSource source = PresentationTraceSources.MarkupSource; + Assert.NotNull(source); + Assert.Same(source, PresentationTraceSources.MarkupSource); + Assert.Equal("System.Windows.Markup", source.Name); + Assert.True(Enum.IsDefined(source.Switch.Level)); + } + + [Fact] + public void NameScopeSource_Get_ReturnsExpected() + { + TraceSource source = PresentationTraceSources.NameScopeSource; + Assert.NotNull(source); + Assert.Same(source, PresentationTraceSources.NameScopeSource); + Assert.Equal("System.Windows.NameScope", source.Name); + Assert.True(Enum.IsDefined(source.Switch.Level)); + } + + [Fact] + public void ResourceDictionarySource_Get_ReturnsExpected() + { + TraceSource source = PresentationTraceSources.ResourceDictionarySource; + Assert.NotNull(source); + Assert.Same(source, PresentationTraceSources.ResourceDictionarySource); + Assert.Equal("System.Windows.ResourceDictionary", source.Name); + Assert.True(Enum.IsDefined(source.Switch.Level)); + } + + [Fact] + public void RoutedEventSource_Get_ReturnsExpected() + { + TraceSource source = PresentationTraceSources.RoutedEventSource; + Assert.NotNull(source); + Assert.Same(source, PresentationTraceSources.RoutedEventSource); + Assert.Equal("System.Windows.RoutedEvent", source.Name); + Assert.True(Enum.IsDefined(source.Switch.Level)); + } + + [Fact] + public void ShellSource_Get_ReturnsExpected() + { + TraceSource source = PresentationTraceSources.ShellSource; + Assert.NotNull(source); + Assert.Same(source, PresentationTraceSources.ShellSource); + Assert.Equal("System.Windows.Shell", source.Name); + Assert.True(Enum.IsDefined(source.Switch.Level)); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/IO/Packaging/EncryptedPackageEnvelopeTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/IO/Packaging/EncryptedPackageEnvelopeTests.cs new file mode 100644 index 00000000000..3d52675718d --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/IO/Packaging/EncryptedPackageEnvelopeTests.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Security.RightsManagement; + +namespace System.IO.Packaging.Tests; + +public class EncryptedPackageEnvelopeTests +{ + [Fact] + public void Open_NullStream_ThrowsArgumentNullException() + { + Assert.Throws("envelopeStream", () => EncryptedPackageEnvelope.Open((Stream)null!)); + } + + [Fact] + public void Open_WriteOnlyStream_ThrowsArgumentException() + { + using var stream = new WriteOnlyStream(); + Assert.Throws(() => EncryptedPackageEnvelope.Open(stream)); + } + + [Fact] + public void Open_InvalidStream_ThrowsIOException() + { + using var stream = new MemoryStream(); + Assert.Throws(() => EncryptedPackageEnvelope.Open(stream)); + } + + [Fact] + public void Open_NullEnvelopeFileName_ThrowsArgumentNullException() + { + Assert.Throws("envelopeFileName", () => EncryptedPackageEnvelope.Open((string)null!)); + Assert.Throws("envelopeFileName", () => EncryptedPackageEnvelope.Open(null!, FileAccess.Read)); + Assert.Throws("envelopeFileName", () => EncryptedPackageEnvelope.Open(null!, FileAccess.Read, FileShare.Read)); + } + + [Fact] + public void Open_DocumentDoesNotContainPackage_ThrowsFileFormatException() + { + string path = Helpers.GetResourcePath("test-ole-file.doc"); + using var stream = File.OpenRead(path); + Assert.Throws(() => EncryptedPackageEnvelope.Open(stream)); + } + + [Fact] + public void Open_DocumentDoesNotContainRightsManagementProtectedStream_ThrowsFileFormatException() + { + string path = Helpers.GetResourcePath("Invalid_1.xps"); + using var stream = File.OpenRead(path); + Assert.Throws(() => EncryptedPackageEnvelope.Open(stream)); + } + + private class ReadOnlyStream : MemoryStream + { + public override bool CanWrite => false; + } + + private class WriteOnlyStream : MemoryStream + { + public override bool CanRead => false; + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/IO/Packaging/PackageDigitalSignatureManagerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/IO/Packaging/PackageDigitalSignatureManagerTests.cs new file mode 100644 index 00000000000..88cb5386141 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/IO/Packaging/PackageDigitalSignatureManagerTests.cs @@ -0,0 +1,367 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Security.Cryptography.X509Certificates; + +namespace System.IO.Packaging.Tests; + +public class PackageDigitalSignatureManagerTests +{ + // TODO: + // Signatures - signed. + // SignatureOrigin - signed. + // Countersign - signed. + // GetSignature - signed. + // RemoveAllSignatures - signed. + // RemoveSignature - signed. + // Sign + // VerifySignatures - signed. + + [Fact] + public void Ctor_Package() + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + Assert.Equal(CertificateEmbeddingOption.InCertificatePart, manager.CertificateOption); + Assert.Equal("http://www.w3.org/2001/04/xmlenc#sha256", manager.HashAlgorithm); + Assert.False(manager.IsSigned); + Assert.Equal(IntPtr.Zero, manager.ParentWindow); + Assert.Equal(new Uri("/package/services/digital-signature/origin.psdsor", UriKind.Relative), manager.SignatureOrigin); + Assert.Empty(manager.Signatures); + Assert.Same(manager.Signatures, manager.Signatures); + Assert.Equal("YYYY-MM-DDThh:mm:ss.sTZD", manager.TimeFormat); + Assert.NotEmpty(manager.TransformMapping); + Assert.Same(manager.TransformMapping, manager.TransformMapping); + Assert.Equal(2, manager.TransformMapping.Count); + Assert.Equal("http://www.w3.org/TR/2001/REC-xml-c14n-20010315", manager.TransformMapping["application/vnd.openxmlformats-package.relationships+xml"]); + Assert.Equal("http://www.w3.org/TR/2001/REC-xml-c14n-20010315", manager.TransformMapping["application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"]); + } + + [Fact] + public void Ctor_NullPackage_ThrowsArgumentNullException() + { + Assert.Throws("package", () => new PackageDigitalSignatureManager(null)); + } + + [Fact] + public void DefaultHashAlgorithm_Get_ReturnsExpected() + { + Assert.Equal("http://www.w3.org/2001/04/xmlenc#sha256", PackageDigitalSignatureManager.DefaultHashAlgorithm); + } + + [Fact] + public void SignatureOriginRelationshipType_Get_ReturnsExpected() + { + Assert.Equal("http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin", PackageDigitalSignatureManager.SignatureOriginRelationshipType); + } + + [Fact] + public void VerifyCertificate_NullCertificate_ThrowsArgumentNullException() + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + Assert.Throws("certificate", () => PackageDigitalSignatureManager.VerifyCertificate(null)); + } + + [Fact] + public void VerifyCertificate_InvokeDefaultCertificate_ThrowsArgumentException() + { +#pragma warning disable SYSLIB0026 + var c = new X509Certificate(); +#pragma warning restore 0618 + Assert.Throws("handle", () => PackageDigitalSignatureManager.VerifyCertificate(c)); + } + + [Theory] + [InlineData(CertificateEmbeddingOption.InCertificatePart)] + [InlineData(CertificateEmbeddingOption.InSignaturePart)] + [InlineData(CertificateEmbeddingOption.NotEmbedded)] + public void CertificateOption_Set_GetReturnsExpected(CertificateEmbeddingOption value) + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + + // Set. + manager.CertificateOption = value; + Assert.Equal(value, manager.CertificateOption); + + // Set same. + manager.CertificateOption = value; + Assert.Equal(value, manager.CertificateOption); + } + + [Theory] + [InlineData(CertificateEmbeddingOption.InCertificatePart - 1)] + [InlineData(CertificateEmbeddingOption.NotEmbedded + 1)] + public void CertificateOption_SetInvalid_ThrowsArgumentOutOfRangeException(CertificateEmbeddingOption value) + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + Assert.Throws("value", () => manager.CertificateOption = value); + } + + [Theory] + [InlineData("http://www.w3.org/2001/04/xmlenc#sha256")] + [InlineData("value")] + [InlineData(" ")] + public void HashAlgorithm_Set_GetReturnsExpected(string value) + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + + // Set. + manager.HashAlgorithm = value; + Assert.Equal(value, manager.HashAlgorithm); + + // Set same. + manager.HashAlgorithm = value; + Assert.Equal(value, manager.HashAlgorithm); + } + + [Fact] + public void HashAlgortithm_SetNull_ThrowsArgumentNullException() + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + Assert.Throws("value", () => manager.HashAlgorithm = null); + } + + [Fact] + public void HashAlgorithm_SetEmpty_ThrowsArgumentException() + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + Assert.Throws("value", () => manager.HashAlgorithm = string.Empty); + } + + public static IEnumerable ParentWindow_Set_TestData() + { + yield return new object[] { (IntPtr)(-1) }; + yield return new object[] { IntPtr.Zero }; + yield return new object[] { (IntPtr)1 }; + } + + [Theory] + [MemberData(nameof(ParentWindow_Set_TestData))] + public void ParentWindow_Set_GetReturnsExpected(IntPtr value) + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + + // Set. + manager.ParentWindow = value; + Assert.Equal(value, manager.ParentWindow); + + // Set same. + manager.ParentWindow = value; + Assert.Equal(value, manager.ParentWindow); + } + + [Theory] + [InlineData("YYYY-MM-DDThh:mm:ss.sTZD")] + [InlineData("YYYY-MM-DDThh:mm:ssTZD")] + [InlineData("YYYY-MM-DDThh:mmTZD")] + [InlineData("YYYY-MM-DD")] + [InlineData("YYYY-MM")] + [InlineData("YYYY")] + public void TimeFormat_Set_GetReturnsExpected(string value) + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + + // Set. + manager.TimeFormat = value; + Assert.Equal(value, manager.TimeFormat); + + // Set same. + manager.TimeFormat = value; + Assert.Equal(value, manager.TimeFormat); + } + + [Fact] + public void TimeFormat_SetNull_ThrowsArgumentNullException() + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + Assert.Throws("value", () => manager.TimeFormat = null); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData("value")] + [InlineData("yyyy-MM-ddTHH:mm:ss.fzzz")] + [InlineData("yyyy-MM-ddTHH:mm:sszzz")] + [InlineData("yyyy-MM-ddTHH:mmzzz")] + [InlineData("yyyy-MM-dd")] + [InlineData("yyyy-MM")] + [InlineData("yyyy")] + [InlineData("yyyy-MM-ddTHH:mm:ss.fZ")] + [InlineData("yyyy-MM-ddTHH:mm:ssZ")] + [InlineData("yyyy-MM-ddTHH:mmZ")] + [InlineData(" YYYY ")] + public void TimeFormat_SetInvalid_ThrowsFormatException(string value) + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + Assert.Throws(() => manager.TimeFormat = value); + } + + [Fact] + public void Countersign_NotSigned_ThrowsInvalidOperationException() + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + Assert.Throws(() => manager.Countersign()); + +#pragma warning disable SYSLIB0026 + var c = new X509Certificate(); +#pragma warning restore 0618 + Assert.Throws(() => manager.Countersign(c)); + Assert.Throws(() => manager.Countersign(c, new Uri[0])); + } + + [Fact] + public void Countersign_NullCertificate_ThrowsArgumentNullException() + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + Assert.Throws("certificate", () => manager.Countersign(null)); + Assert.Throws("certificate", () => manager.Countersign(null, new Uri[0])); + } + + [Fact] + public void Countersign_NullSignatures_ThrowsArgumentNullException() + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + +#pragma warning disable SYSLIB0026 + var c = new X509Certificate(); +#pragma warning restore 0618 + Assert.Throws("signatures", () => manager.Countersign(c, null)); + } + + [Fact] + public void GetSignature_InvokeNotSigned_ReturnsNull() + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + Assert.Null(manager.GetSignature(new Uri("http://microsoft.com"))); + } + + [Fact] + public void GetSignature_NullSignatureUri_ThrowsArgumentNullException() + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + Assert.Throws("signatureUri", () => manager.GetSignature(null)); + } + + [Fact] + public void RemoveAllSignatures_InvokeNotSigned_Nop() + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + int callCount = 0; + var parts = new List(); + package.DeletePartCoreAction = (uri) => + { + parts.Add(uri); + callCount++; + }; + + // Remove. + manager.RemoveAllSignatures(); + Assert.Equal(2, callCount); + Assert.Equal(new Uri[] { new Uri("/package/services/digital-signature/origin.psdsor", UriKind.Relative), new Uri("/package/services/digital-signature/_rels/origin.psdsor.rels", UriKind.Relative) }, parts); + Assert.Empty(manager.Signatures); + Assert.Same(manager.Signatures, manager.Signatures); + + // Remove again. + manager.RemoveAllSignatures(); + Assert.Equal(4, callCount); + Assert.Equal(new Uri[] { new Uri("/package/services/digital-signature/origin.psdsor", UriKind.Relative), new Uri("/package/services/digital-signature/_rels/origin.psdsor.rels", UriKind.Relative), new Uri("/package/services/digital-signature/origin.psdsor", UriKind.Relative), new Uri("/package/services/digital-signature/_rels/origin.psdsor.rels", UriKind.Relative) }, parts); + Assert.Empty(manager.Signatures); + Assert.Same(manager.Signatures, manager.Signatures); + } + + [Fact] + public void RemoveSignature_InvokeNotSigned_Nop() + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + int callCount = 0; + package.DeletePartCoreAction = (uri) => callCount++; + + // Remove. + manager.RemoveSignature(new Uri("https://microsoft.com")); + Assert.Equal(0, callCount); + Assert.Empty(manager.Signatures); + Assert.Same(manager.Signatures, manager.Signatures); + + // Remove again. + manager.RemoveSignature(new Uri("https://microsoft.com")); + Assert.Equal(0, callCount); + Assert.Empty(manager.Signatures); + Assert.Same(manager.Signatures, manager.Signatures); + } + + [Fact] + public void RemoveSignature_NullSignatureUri_ThrowsArgumentNullException() + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + Assert.Throws("signatureUri", () => manager.RemoveSignature(null)); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void VerifySignatures_InvokeNotSigned_ReturnsNotSigned(bool exitOnFailure) + { + var package = new CustomPackage(FileAccess.ReadWrite); + var manager = new PackageDigitalSignatureManager(package); + Assert.Equal(VerifyResult.NotSigned, manager.VerifySignatures(exitOnFailure)); + } + + private class CustomPackage : Package + { + public CustomPackage(FileAccess openFileAccess) : base(openFileAccess) + { + } + + protected override PackagePart CreatePartCore(Uri partUri, string contentType, CompressionOption compressionOption) + { + throw new NotImplementedException(); + } + + public Action? DeletePartCoreAction { get; set; } + + protected override void DeletePartCore(Uri partUri) + { + if (DeletePartCoreAction is null) + { + throw new NotImplementedException(); + } + + DeletePartCoreAction(partUri); + } + + protected override void FlushCore() + { + throw new NotImplementedException(); + } + + protected override PackagePart? GetPartCore(Uri partUri) + { + throw new NotImplementedException(); + } + + protected override PackagePart[] GetPartsCore() + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/ContentGrantTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/ContentGrantTests.cs new file mode 100644 index 00000000000..1913000ce87 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/ContentGrantTests.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Security.RightsManagement.Tests; + +public class ContentGrantTests +{ + public static IEnumerable Ctor_ContentUser_ContentRight_TestData() + { + yield return new object[] { ContentUser.AnyoneUser, ContentRight.View }; + yield return new object[] { ContentUser.OwnerUser, ContentRight.Edit }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Print }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Extract }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.ObjectModel }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Owner }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.ViewRightsData }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Forward }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Reply }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.ReplyAll }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Sign }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.DocumentEdit }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Export }; + } + + [Theory] + [MemberData(nameof(Ctor_ContentUser_ContentRight_TestData))] + public void Ctor_ContentUser_ContentRight(ContentUser user, ContentRight right) + { + var grant = new ContentGrant(user, right); + Assert.Equal(user, grant.User); + Assert.Equal(right, grant.Right); + Assert.Equal(DateTime.MinValue, grant.ValidFrom); + Assert.Equal(DateTime.MaxValue, grant.ValidUntil); + } + public static IEnumerable Ctor_ContentUser_ContentRight_DateTime_DateTime_TestData() + { + yield return new object[] { ContentUser.AnyoneUser, ContentRight.View, DateTime.MinValue, DateTime.MaxValue }; + yield return new object[] { ContentUser.OwnerUser, ContentRight.Edit, DateTime.MinValue, DateTime.MaxValue }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Print, DateTime.MinValue, DateTime.MaxValue }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Extract, DateTime.MinValue, DateTime.MaxValue }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.ObjectModel, DateTime.MinValue, DateTime.MaxValue }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Owner, DateTime.MinValue, DateTime.MaxValue }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.ViewRightsData, DateTime.MinValue, DateTime.MaxValue }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Forward, DateTime.MinValue, DateTime.MaxValue }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Reply, DateTime.MinValue, DateTime.MaxValue }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.ReplyAll, DateTime.MinValue, DateTime.MaxValue }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Sign, DateTime.MinValue, DateTime.MaxValue }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.DocumentEdit, DateTime.MinValue, DateTime.MaxValue }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Export, DateTime.MinValue, DateTime.MaxValue }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Export, DateTime.MinValue, DateTime.MinValue }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Export, DateTime.MaxValue, DateTime.MaxValue }; + yield return new object[] { new ContentUser("name", AuthenticationType.Windows), ContentRight.Export, new DateTime(2023, 01, 01), new DateTime(2023, 01, 02) }; + } + + [Theory] + [MemberData(nameof(Ctor_ContentUser_ContentRight_DateTime_DateTime_TestData))] + public void Ctor_ContentUser_ContentRight_DateTime_DateTime(ContentUser user, ContentRight right, DateTime validFrom, DateTime validUntil) + { + var grant = new ContentGrant(user, right, validFrom, validUntil); + Assert.Equal(user, grant.User); + Assert.Equal(right, grant.Right); + Assert.Equal(validFrom, grant.ValidFrom); + Assert.Equal(validUntil, grant.ValidUntil); + } + + [Fact] + public void Ctor_NullUser_ThrowsArgumentNullException() + { + Assert.Throws("user", () => new ContentGrant(null, ContentRight.View)); + Assert.Throws("user", () => new ContentGrant(null, ContentRight.View, DateTime.MinValue, DateTime.MaxValue)); + } + + [Theory] + [InlineData(ContentRight.View - 1)] + [InlineData(ContentRight.Export + 1)] + public void Ctor_ContentUser_ContentRight_InvalidRight(ContentRight right) + { + var user = new ContentUser("name", AuthenticationType.Windows); + Assert.Throws("right", () => new ContentGrant(user, right)); + } + + [Fact] + public void Ctor_InvalidValidFrom_ThrowsArgumentOutOfRangeException() + { + var user = new ContentUser("name", AuthenticationType.Windows); + Assert.Throws("validFrom", () => new ContentGrant(user, ContentRight.View, new DateTime(2023, 01, 01), new DateTime(2023, 01, 01).AddTicks(-1))); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/ContentUserTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/ContentUserTests.cs new file mode 100644 index 00000000000..c469084ef5e --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/ContentUserTests.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Security.RightsManagement.Tests; + +public class ContentUserTests +{ + [Theory] + [InlineData("name", AuthenticationType.Windows)] + [InlineData("NaMe", AuthenticationType.Passport)] + [InlineData("name", AuthenticationType.WindowsPassport)] + [InlineData(" name ", AuthenticationType.Windows)] + [InlineData("anyone", AuthenticationType.Windows)] + [InlineData("anyone", AuthenticationType.Passport)] + [InlineData("anyone", AuthenticationType.WindowsPassport)] + [InlineData("anyone", AuthenticationType.Internal)] + [InlineData("ANYONE", AuthenticationType.Internal)] + [InlineData("owner", AuthenticationType.Internal)] + [InlineData("OWNER", AuthenticationType.Internal)] + public void Ctor_String_AuthenticationType(string name, AuthenticationType authenticationType) + { + var user = new ContentUser(name, authenticationType); + Assert.Equal(name, user.Name); + Assert.Equal(authenticationType, user.AuthenticationType); + } + + [Fact] + public void Ctor_NullName_ThrowsArgumentNullException() + { + Assert.Throws("name", () => new ContentUser(null!, AuthenticationType.Windows)); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public void Ctor_InvalidName_ThrowsArgumentOutOfRangeException(string name) + { + Assert.Throws("name", () => new ContentUser(name, AuthenticationType.Windows)); + } + + [Theory] + [InlineData(AuthenticationType.Windows - 1)] + [InlineData(AuthenticationType.Internal + 1)] + public void Ctor_InvalidAuthenticationType_ThrowsArgumentOutOfRangeException(AuthenticationType authenticationType) + { + Assert.Throws("authenticationType", () => new ContentUser("name", authenticationType)); + } + + [Theory] + [InlineData("name")] + [InlineData(" anyone ")] + [InlineData(" owner ")] + public void Ctor_InvalidInternalName_ThrowsArgumentOutOfRangeException(string name) + { + Assert.Throws("name", () => new ContentUser(name, AuthenticationType.Internal)); + } + + [Fact] + public void AnyoneUser_Get_ReturnsExpected() + { + ContentUser user = ContentUser.AnyoneUser; + Assert.Same(user, ContentUser.AnyoneUser); + Assert.Equal("Anyone", user.Name); + Assert.Equal(AuthenticationType.Internal, user.AuthenticationType); + } + + [Fact] + public void OwnerUser_Get_ReturnsExpected() + { + ContentUser user = ContentUser.OwnerUser; + Assert.Same(user, ContentUser.OwnerUser); + Assert.Equal("Owner", user.Name); + Assert.Equal(AuthenticationType.Internal, user.AuthenticationType); + } + + public static IEnumerable Equals_TestData() + { + yield return new object?[] { new ContentUser("name", AuthenticationType.Windows), new ContentUser("name", AuthenticationType.Windows), true }; + yield return new object?[] { new ContentUser("name", AuthenticationType.Windows), new SubContentUser("name", AuthenticationType.Windows), false }; + yield return new object?[] { new ContentUser("name", AuthenticationType.Windows), new ContentUser("NAME", AuthenticationType.Windows), true }; + yield return new object?[] { new ContentUser("name", AuthenticationType.Windows), new ContentUser("nAME", AuthenticationType.Windows), true }; + yield return new object?[] { new ContentUser("name", AuthenticationType.Windows), new ContentUser(" name ", AuthenticationType.Windows), false }; + yield return new object?[] { new ContentUser("name", AuthenticationType.Windows), new ContentUser("name2", AuthenticationType.Windows), false }; + yield return new object?[] { new ContentUser("name", AuthenticationType.Windows), new ContentUser("name", AuthenticationType.WindowsPassport), false }; + yield return new object?[] { new ContentUser("name", AuthenticationType.Windows), new object(), false }; + yield return new object?[] { new ContentUser("name", AuthenticationType.Windows), null, false }; + + yield return new object?[] { new SubContentUser("name", AuthenticationType.Windows), new SubContentUser("name", AuthenticationType.Windows), true }; + yield return new object?[] { new SubContentUser("name", AuthenticationType.Windows), new ContentUser("name", AuthenticationType.Windows), false }; + yield return new object?[] { new ContentUser("name", AuthenticationType.Windows), new SubContentUser("name", AuthenticationType.Windows), false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void Equals_Object_ReturnsExpected(ContentUser user, object obj, bool expected) + { + Assert.Equal(expected, user.Equals(obj)); + if (user?.GetType() == obj?.GetType()) + { + Assert.Equal(expected, user!.GetHashCode().Equals(obj!.GetHashCode())); + } + } + + [Fact] + public void IsAuthenticated_InvokeWindows_ReturnsFalse() + { + var user = new ContentUser("name", AuthenticationType.Windows); + Assert.False(user.IsAuthenticated()); + } + + [Fact] + public void IsAuthenticated_InvokePassport_ReturnsFalse() + { + var user = new ContentUser("name", AuthenticationType.Passport); + Assert.False(user.IsAuthenticated()); + } + + [Fact] + public void IsAuthenticated_InvokeWindowsPassport_ReturnsFalse() + { + var user = new ContentUser("name", AuthenticationType.WindowsPassport); + Assert.False(user.IsAuthenticated()); + } + + [Theory] + [InlineData("anyone")] + [InlineData("owner")] + public void IsAuthenticated_InvokeInternal_ReturnsFalse(string name) + { + var user = new ContentUser(name, AuthenticationType.Internal); + Assert.False(user.IsAuthenticated()); + } + + private class SubContentUser : ContentUser + { + public SubContentUser(string name, AuthenticationType authenticationType) : base(name, authenticationType) + { + } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/CryptoProviderTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/CryptoProviderTests.cs new file mode 100644 index 00000000000..c10c51d69a1 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/CryptoProviderTests.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Security.RightsManagement; + +public class CryptoProviderTests +{ +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/LocalizedNameDescriptionPairTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/LocalizedNameDescriptionPairTests.cs new file mode 100644 index 00000000000..8945aff35f4 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/LocalizedNameDescriptionPairTests.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Security.RightsManagement.Tests; + +public class LocalizedNameDescriptionPairTests +{ + [Theory] + [InlineData("", "")] + [InlineData(" ", " ")] + [InlineData("name", "description")] + public void Ctor_String_String(string name, string description) + { + var pair = new LocalizedNameDescriptionPair(name, description); + Assert.Equal(name, pair.Name); + Assert.Equal(description, pair.Description); + } + + public static IEnumerable Equals_TestData() + { + var pair = new LocalizedNameDescriptionPair("name", "description"); + + yield return new object?[] { pair, pair, true, true }; + yield return new object?[] { pair, new LocalizedNameDescriptionPair("name", "description"), true, true }; + yield return new object?[] { pair, new LocalizedNameDescriptionPair("NAME", "description"), false, false }; + yield return new object?[] { pair, new LocalizedNameDescriptionPair("name", "DESCRIPTION"), false, false }; + yield return new object?[] { pair, new LocalizedNameDescriptionPair("name2", "description"), false, false }; + yield return new object?[] { pair, new LocalizedNameDescriptionPair("name", "description2"), false, false }; + yield return new object?[] { pair, new LocalizedNameDescriptionPair("", "description"), false, false }; + yield return new object?[] { pair, new LocalizedNameDescriptionPair("name", ""), false, false }; + yield return new object?[] { pair, new SubLocalizedNameDescriptionPair("name", "description"), false, true }; + yield return new object?[] { pair, new object(), false, false }; + yield return new object?[] { pair, null, false, false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void Equals_Object_ReturnsExpected(LocalizedNameDescriptionPair pair, object obj, bool expected, bool expectedHashCode) + { + Assert.Equal(expected, pair.Equals(obj)); + if (obj is LocalizedNameDescriptionPair otherPair) + { + Assert.Equal(expected, otherPair.Equals(pair)); + Assert.Equal(expectedHashCode, pair.GetHashCode().Equals(otherPair.GetHashCode())); + } + } + + [Fact] + public void GetHashCode_Invoke_ReturnsEqual() + { + var pair = new LocalizedNameDescriptionPair("name", "description"); + Assert.NotEqual(0, pair.GetHashCode()); + Assert.Equal(pair.GetHashCode(), pair.GetHashCode()); + } + + private class SubLocalizedNameDescriptionPair : LocalizedNameDescriptionPair + { + public SubLocalizedNameDescriptionPair(string name, string description) : base(name, description) + { + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/PublishLicenseTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/PublishLicenseTests.cs new file mode 100644 index 00000000000..13db90ab354 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/PublishLicenseTests.cs @@ -0,0 +1,350 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Security.RightsManagement.Tests; + +public class PublishLicenseTests +{ + // Taken from https://github.com/dotnet/dotnet-api-docs/blob/main/snippets/csharp/System.Security.RightsManagement/CryptoProvider/BlockSize/Content/Truffle.png.PublishLicense.xml. + private const string PublishLicenseTemplate = """ + + + 2006-04-19T03:14 + + + S-1-5-21-2127521184-1604012920-1887927527-1723404 + jack.davis@microsoft.com + + + RSA + + 65537 + + + r1rbEmgQhskJ29p8xpX00ZlROS13kHMzv0DrOQOn+4mijO3Eanelw1F+mYdSdrByU9MbamWHalUqku6wESZY2DCpRU2C3xw2lCvPtrYqtJkxSt835Ez4quDYVNQq+15DzA6qyl5gc07XuoQi1AC0Nfb5tMy0R85SocFE195VUq4= + + + + + + + {0F45FD50-383B-43EE-90A4-ED013CD0CFE5} + DRM Server Cluster +
http://ed-drm-red3/_wmcs/licensing
+
+
+ + + + {1d59a4ae-e6ae-4151-b458-afc5251fe0c3} +
http://ed-drm-red3/_wmcs
+
+ + RSA + + 65537 + + + 43b8U8yG5ifu38tkAa8K/2DnMOZqgVdj8OZCY+V0332efhaocT7EGV8JE3Htolc2mqTDdLlHQQoJ9jrG36efYYqo4aivo7ddx5w9NlMo9O4mXb+s70LD1VPaM6TywWYYfho+6vTGI1SwPJVgmwS2Qgha/AXOJrK0t5gEX8CZPMo= + + + + + + OmZReXce7iXuQZ+ySktmUyK0sApe4IxmBTIpzsPaIcYK/ll4SxzxwUO5BLUAV9SY41nPYX+zFMKKOkVC2GdKuKlERXYgR8LvyDIifKm8/OUL2q5XKsW4pRXMfm4ccGokq1lv0pCMS0qIreAmSURdK+FIVjWwPeFQu2N1iKwHigDjHKbva9ICtkxXfZtgEwgakypbFV/T7WqrpWxS8l4bBsIAKcYzuUbLgQOYCc/lJBUWDJqMMPsyV1J65ZHlO3Nd + +
+
+ + + {9257669d-2753-4f8f-94c6-028987c0434b} + + + + + + jack.davis@microsoft.com + + + + + Wy6PnRT6uGKHJ/b3uRktcgNL2bBMEXsSneudY49Oy2tZy44QG5WWIhjGHbRH5CNHC3zAE4H4KIc7MvYde/GvbHb6reWTfFDsw7P7DfERz5ArqKr6+wpxXVrX3CmA+wA0kh5KmPF142/NRrFpno1dF9Wv/+J8nBwvNFaj+T6LayF5kWG9GIl1bimAUaXZQpyJmxnRXP8T78Q2udn66osD2cm0rx0cl8r7d7m2gXR8VxQMSIc76wQ/nveDgGRlKrCXFKXhj8VqLx5j6OFkpSqxwGYBhqkJMd4wVUkb2Jhw4M8kPivGg1JlK2yBLN3hgXYxq0ASeNloUZteReOK9OE7UyKFGhE9Z5T0sDJiVrzeko/a/GcU/LnlfIko12ImlYaKsyYJmY4FM5Pi6QoMyZQ/18NTRTdiPl0JVYA0tRYDa+J5fBadoyCY5WSDXuePmawI0OEcnhxOfLnB1CgUD+c0+AZpCwVlMocmYTpX90xX6yEOTf/aaNCcotIF+YndxZUvqkVT3HqHnRKrr10ZS6YwDS6YgvJX9w0UaDwMvH3UMEBxC8SKZfKQ0hgg8loZ38dhODT/Bpkf3YnBQ2Zo7bGTESKFGhE9Z5T0sDJiVrzeko/a/GcU/LnlfIko12ImlYaKt70q5TxZhcVjJqTQzap0aD9a9wzYi6mMAVBZE3jDLxNMg8ysSrrFTKezKljLb9Yql5sD00pRZOAVmZL88z1JGLNw/zT42yJzqfchXuMNG/XuPETOsndlGBIRbmGfi4WKqaBsZ3Ac+JULrNIqBk2rupkE0HqSHYtTLxmeamFVZWAYK96yps+gtqPHGLNC36f93l/km1Z5cIg+jOXFhuutXPlu/tg/JqkAshwoAJYe6Fk72qRLXFBQtjtJSoAjlfHLODFKwNkX4Ggc0gGw+8VTxN0rnYnGC2ZKlLIpb3M2WEqpSqnG0Voh0tKruAmcTgXWSKwNlUjWTJ7A95tBUUw9g4pXGX5EXHqCSvw6dqt99eW1dUfrCIXupmR+YVIydID7z/vAMvoO5luQnatPZJrM9ETzyPS/OAcUCR7VpSnVqPFntz3Tb9Hzh+4hZ4dex8EEL34kyxOHoI2JYVJzSd73b7N7TahserIAlyaK0S6IUDmmPrituN5Mc9z2osevePbJsVpgRhiAglqIoJ5J2ReYPkaP3tJYlBWvPvaGsGqxVDDSxqxRCotZTlweB+/Prf2tqK8YTXxxTEsBtXD3xCvsWjiA67BMSduPTnKpbvlZDYEUbf8MFSA9UWVc0Xe3urRqtXVH6wiF7qZkfmFSMnSA+5rebPPxn+BLaMZCpbiOp/EKOZG5qTyHxdkvT8dfytZK0Oqu+1nlcTzlEf7zDve5dMPzF0MhNoT0/4vlOEKqx8gEcA9SNo+bY81mh/Vw0R3FqK8YTXxxTEsBtXD3xCvsWmgJGKbpKm7VZP49gYKGfQruR25tBiNopkPeiAK4UjDiQ7lzfsEN9xWy6g6B8Dp/6fEkKTxHlK5EUwPpszCZDhSwaRYhz6j+W7JeO9LC/LJfZ4dS3+iMTdxtodC2Rrm3Da1ZSPRGQiwTGElOAhc5XJYkdIrv3h+G3ObViRXevP0iLpiC8lf3DRRoPAy8fdQwQCU7ezn9Mdsj9MeezTYyEwp3mCXWx9ZrHfBl8Tpuj0enllQIjpcGWHaZZ0Pce31sRoi8vaoQCRu3pbm4cKKwU/sYlQNBX0D1DE2ZQB6C2NvtwdIU2AZcYvGjXDGaZ5YRh1+Kq/vvRISvQFSbl5uUPMpfgTqhDPYwjIfsEoVv9Rki9dFh4xLuk3n8Mo6lWm+BcQUNPG2fz+8MH3Hd2QfCNNynHEJAbT2KFh5VIKwzoughUBZR9r7VmSoYg2l3YXb5hg== + + + RSA PKCS#1-V1.5 + + SHA1 + + surface-coding + + 0zkJi3NJke+OehTek7lsFT675JU= + + VObVcSvMdfw4pE7S2Q1o/oeE3d72yhuu42hZz5vpcg8elVfRPFrpxECWR1f9cNxSX6j32JJ9W1+vD2YNOeykWg9iIcbq0e82d/WTvkOs5eIFIWtqjGidOvnG7UCUYBktBiKJ5IDeo68P6GfsEtby8o54MlIztvWXeB5T21nhz2I= + +
+"""; + + [Fact] + public void Ctor_TruffleExample() + { + var license = new PublishLicense(PublishLicenseTemplate); + Assert.Equal(new Guid("9257669d-2753-4f8f-94c6-028987c0434b"), license.ContentId); + Assert.Null(license.ReferralInfoName); + Assert.Null(license.ReferralInfoUri); + Assert.Equal(new Uri("http://ed-drm-red3/_wmcs/licensing"), license.UseLicenseAcquisitionUrl); + } + + [Fact] + public void Ctor_NullSignedPublishLicense_ThrowsArgumentNullException() + { + Assert.Throws("signedPublishLicense", () => new PublishLicense(null!)); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData("")] + public void Ctor_InvalidSignedPublishLicense_ThrowsRightsManagementException(string signedPublishLicense) + { + Assert.Throws(() => new PublishLicense(signedPublishLicense)); + } + + [Fact] + public void Ctor_EmptyUseLicenseAcquisitionUrl_ThrowsArgumentNullException() + { + const string PublishLicenseTemplate = """ + + +"""; + Assert.Throws("uriString", () => new PublishLicense(PublishLicenseTemplate)); + } + + [Fact] + public void Ctor_InvalidUseLicenseAcquisitionUrl_ThrowsUriFormatException() + { + const string PublishLicenseTemplate = """ + + + + + {0F45FD50-383B-43EE-90A4-ED013CD0CFE5} + DRM Server Cluster +
invalid
+
+
+ +
+"""; + Assert.Throws(() => new PublishLicense(PublishLicenseTemplate)); + } + + [Fact] + public void Ctor_NoQueries_ThrowsRightsManagementException() + { + const string PublishLicenseTemplate = """ + + + + + {0F45FD50-383B-43EE-90A4-ED013CD0CFE5} + DRM Server Cluster +
https://google.com
+
+
+ +
+"""; + Assert.Throws(() => new PublishLicense(PublishLicenseTemplate)); + } + + [Fact] + public void Ctor_InvalidContentId_ThrowsFormatException() + { + const string PublishLicenseTemplate = """ + + + 2006-04-19T03:14 + + + S-1-5-21-2127521184-1604012920-1887927527-1723404 + jack.davis@microsoft.com + + + RSA + + 65537 + + + r1rbEmgQhskJ29p8xpX00ZlROS13kHMzv0DrOQOn+4mijO3Eanelw1F+mYdSdrByU9MbamWHalUqku6wESZY2DCpRU2C3xw2lCvPtrYqtJkxSt835Ez4quDYVNQq+15DzA6qyl5gc07XuoQi1AC0Nfb5tMy0R85SocFE195VUq4= + + + + + + + {0F45FD50-383B-43EE-90A4-ED013CD0CFE5} + DRM Server Cluster +
http://ed-drm-red3/_wmcs/licensing
+
+
+ + + + {1d59a4ae-e6ae-4151-b458-afc5251fe0c3} +
http://ed-drm-red3/_wmcs
+
+ + RSA + + 65537 + + + 43b8U8yG5ifu38tkAa8K/2DnMOZqgVdj8OZCY+V0332efhaocT7EGV8JE3Htolc2mqTDdLlHQQoJ9jrG36efYYqo4aivo7ddx5w9NlMo9O4mXb+s70LD1VPaM6TywWYYfho+6vTGI1SwPJVgmwS2Qgha/AXOJrK0t5gEX8CZPMo= + + + + + + OmZReXce7iXuQZ+ySktmUyK0sApe4IxmBTIpzsPaIcYK/ll4SxzxwUO5BLUAV9SY41nPYX+zFMKKOkVC2GdKuKlERXYgR8LvyDIifKm8/OUL2q5XKsW4pRXMfm4ccGokq1lv0pCMS0qIreAmSURdK+FIVjWwPeFQu2N1iKwHigDjHKbva9ICtkxXfZtgEwgakypbFV/T7WqrpWxS8l4bBsIAKcYzuUbLgQOYCc/lJBUWDJqMMPsyV1J65ZHlO3Nd + +
+
+ + + invalid + + + + + + jack.davis@microsoft.com + + + + + Wy6PnRT6uGKHJ/b3uRktcgNL2bBMEXsSneudY49Oy2tZy44QG5WWIhjGHbRH5CNHC3zAE4H4KIc7MvYde/GvbHb6reWTfFDsw7P7DfERz5ArqKr6+wpxXVrX3CmA+wA0kh5KmPF142/NRrFpno1dF9Wv/+J8nBwvNFaj+T6LayF5kWG9GIl1bimAUaXZQpyJmxnRXP8T78Q2udn66osD2cm0rx0cl8r7d7m2gXR8VxQMSIc76wQ/nveDgGRlKrCXFKXhj8VqLx5j6OFkpSqxwGYBhqkJMd4wVUkb2Jhw4M8kPivGg1JlK2yBLN3hgXYxq0ASeNloUZteReOK9OE7UyKFGhE9Z5T0sDJiVrzeko/a/GcU/LnlfIko12ImlYaKsyYJmY4FM5Pi6QoMyZQ/18NTRTdiPl0JVYA0tRYDa+J5fBadoyCY5WSDXuePmawI0OEcnhxOfLnB1CgUD+c0+AZpCwVlMocmYTpX90xX6yEOTf/aaNCcotIF+YndxZUvqkVT3HqHnRKrr10ZS6YwDS6YgvJX9w0UaDwMvH3UMEBxC8SKZfKQ0hgg8loZ38dhODT/Bpkf3YnBQ2Zo7bGTESKFGhE9Z5T0sDJiVrzeko/a/GcU/LnlfIko12ImlYaKt70q5TxZhcVjJqTQzap0aD9a9wzYi6mMAVBZE3jDLxNMg8ysSrrFTKezKljLb9Yql5sD00pRZOAVmZL88z1JGLNw/zT42yJzqfchXuMNG/XuPETOsndlGBIRbmGfi4WKqaBsZ3Ac+JULrNIqBk2rupkE0HqSHYtTLxmeamFVZWAYK96yps+gtqPHGLNC36f93l/km1Z5cIg+jOXFhuutXPlu/tg/JqkAshwoAJYe6Fk72qRLXFBQtjtJSoAjlfHLODFKwNkX4Ggc0gGw+8VTxN0rnYnGC2ZKlLIpb3M2WEqpSqnG0Voh0tKruAmcTgXWSKwNlUjWTJ7A95tBUUw9g4pXGX5EXHqCSvw6dqt99eW1dUfrCIXupmR+YVIydID7z/vAMvoO5luQnatPZJrM9ETzyPS/OAcUCR7VpSnVqPFntz3Tb9Hzh+4hZ4dex8EEL34kyxOHoI2JYVJzSd73b7N7TahserIAlyaK0S6IUDmmPrituN5Mc9z2osevePbJsVpgRhiAglqIoJ5J2ReYPkaP3tJYlBWvPvaGsGqxVDDSxqxRCotZTlweB+/Prf2tqK8YTXxxTEsBtXD3xCvsWjiA67BMSduPTnKpbvlZDYEUbf8MFSA9UWVc0Xe3urRqtXVH6wiF7qZkfmFSMnSA+5rebPPxn+BLaMZCpbiOp/EKOZG5qTyHxdkvT8dfytZK0Oqu+1nlcTzlEf7zDve5dMPzF0MhNoT0/4vlOEKqx8gEcA9SNo+bY81mh/Vw0R3FqK8YTXxxTEsBtXD3xCvsWmgJGKbpKm7VZP49gYKGfQruR25tBiNopkPeiAK4UjDiQ7lzfsEN9xWy6g6B8Dp/6fEkKTxHlK5EUwPpszCZDhSwaRYhz6j+W7JeO9LC/LJfZ4dS3+iMTdxtodC2Rrm3Da1ZSPRGQiwTGElOAhc5XJYkdIrv3h+G3ObViRXevP0iLpiC8lf3DRRoPAy8fdQwQCU7ezn9Mdsj9MeezTYyEwp3mCXWx9ZrHfBl8Tpuj0enllQIjpcGWHaZZ0Pce31sRoi8vaoQCRu3pbm4cKKwU/sYlQNBX0D1DE2ZQB6C2NvtwdIU2AZcYvGjXDGaZ5YRh1+Kq/vvRISvQFSbl5uUPMpfgTqhDPYwjIfsEoVv9Rki9dFh4xLuk3n8Mo6lWm+BcQUNPG2fz+8MH3Hd2QfCNNynHEJAbT2KFh5VIKwzoughUBZR9r7VmSoYg2l3YXb5hg== + + + RSA PKCS#1-V1.5 + + SHA1 + + surface-coding + + 0zkJi3NJke+OehTek7lsFT675JU= + + VObVcSvMdfw4pE7S2Q1o/oeE3d72yhuu42hZz5vpcg8elVfRPFrpxECWR1f9cNxSX6j32JJ9W1+vD2YNOeykWg9iIcbq0e82d/WTvkOs5eIFIWtqjGidOvnG7UCUYBktBiKJ5IDeo68P6GfsEtby8o54MlIztvWXeB5T21nhz2I= + +
+"""; + Assert.Throws(() => new PublishLicense(PublishLicenseTemplate)); + } + + [Fact] + public void Ctor_NoContentId_ThrowsRightsManagementException() + { + const string PublishLicenseTemplate = """ + + + 2006-04-19T03:14 + + + S-1-5-21-2127521184-1604012920-1887927527-1723404 + jack.davis@microsoft.com + + + RSA + + 65537 + + + r1rbEmgQhskJ29p8xpX00ZlROS13kHMzv0DrOQOn+4mijO3Eanelw1F+mYdSdrByU9MbamWHalUqku6wESZY2DCpRU2C3xw2lCvPtrYqtJkxSt835Ez4quDYVNQq+15DzA6qyl5gc07XuoQi1AC0Nfb5tMy0R85SocFE195VUq4= + + + + + + + {0F45FD50-383B-43EE-90A4-ED013CD0CFE5} + DRM Server Cluster +
http://ed-drm-red3/_wmcs/licensing
+
+
+ + + + {1d59a4ae-e6ae-4151-b458-afc5251fe0c3} +
http://ed-drm-red3/_wmcs
+
+ + RSA + + 65537 + + + 43b8U8yG5ifu38tkAa8K/2DnMOZqgVdj8OZCY+V0332efhaocT7EGV8JE3Htolc2mqTDdLlHQQoJ9jrG36efYYqo4aivo7ddx5w9NlMo9O4mXb+s70LD1VPaM6TywWYYfho+6vTGI1SwPJVgmwS2Qgha/AXOJrK0t5gEX8CZPMo= + + + + + + OmZReXce7iXuQZ+ySktmUyK0sApe4IxmBTIpzsPaIcYK/ll4SxzxwUO5BLUAV9SY41nPYX+zFMKKOkVC2GdKuKlERXYgR8LvyDIifKm8/OUL2q5XKsW4pRXMfm4ccGokq1lv0pCMS0qIreAmSURdK+FIVjWwPeFQu2N1iKwHigDjHKbva9ICtkxXfZtgEwgakypbFV/T7WqrpWxS8l4bBsIAKcYzuUbLgQOYCc/lJBUWDJqMMPsyV1J65ZHlO3Nd + +
+
+ + + + + + jack.davis@microsoft.com + + + + + Wy6PnRT6uGKHJ/b3uRktcgNL2bBMEXsSneudY49Oy2tZy44QG5WWIhjGHbRH5CNHC3zAE4H4KIc7MvYde/GvbHb6reWTfFDsw7P7DfERz5ArqKr6+wpxXVrX3CmA+wA0kh5KmPF142/NRrFpno1dF9Wv/+J8nBwvNFaj+T6LayF5kWG9GIl1bimAUaXZQpyJmxnRXP8T78Q2udn66osD2cm0rx0cl8r7d7m2gXR8VxQMSIc76wQ/nveDgGRlKrCXFKXhj8VqLx5j6OFkpSqxwGYBhqkJMd4wVUkb2Jhw4M8kPivGg1JlK2yBLN3hgXYxq0ASeNloUZteReOK9OE7UyKFGhE9Z5T0sDJiVrzeko/a/GcU/LnlfIko12ImlYaKsyYJmY4FM5Pi6QoMyZQ/18NTRTdiPl0JVYA0tRYDa+J5fBadoyCY5WSDXuePmawI0OEcnhxOfLnB1CgUD+c0+AZpCwVlMocmYTpX90xX6yEOTf/aaNCcotIF+YndxZUvqkVT3HqHnRKrr10ZS6YwDS6YgvJX9w0UaDwMvH3UMEBxC8SKZfKQ0hgg8loZ38dhODT/Bpkf3YnBQ2Zo7bGTESKFGhE9Z5T0sDJiVrzeko/a/GcU/LnlfIko12ImlYaKt70q5TxZhcVjJqTQzap0aD9a9wzYi6mMAVBZE3jDLxNMg8ysSrrFTKezKljLb9Yql5sD00pRZOAVmZL88z1JGLNw/zT42yJzqfchXuMNG/XuPETOsndlGBIRbmGfi4WKqaBsZ3Ac+JULrNIqBk2rupkE0HqSHYtTLxmeamFVZWAYK96yps+gtqPHGLNC36f93l/km1Z5cIg+jOXFhuutXPlu/tg/JqkAshwoAJYe6Fk72qRLXFBQtjtJSoAjlfHLODFKwNkX4Ggc0gGw+8VTxN0rnYnGC2ZKlLIpb3M2WEqpSqnG0Voh0tKruAmcTgXWSKwNlUjWTJ7A95tBUUw9g4pXGX5EXHqCSvw6dqt99eW1dUfrCIXupmR+YVIydID7z/vAMvoO5luQnatPZJrM9ETzyPS/OAcUCR7VpSnVqPFntz3Tb9Hzh+4hZ4dex8EEL34kyxOHoI2JYVJzSd73b7N7TahserIAlyaK0S6IUDmmPrituN5Mc9z2osevePbJsVpgRhiAglqIoJ5J2ReYPkaP3tJYlBWvPvaGsGqxVDDSxqxRCotZTlweB+/Prf2tqK8YTXxxTEsBtXD3xCvsWjiA67BMSduPTnKpbvlZDYEUbf8MFSA9UWVc0Xe3urRqtXVH6wiF7qZkfmFSMnSA+5rebPPxn+BLaMZCpbiOp/EKOZG5qTyHxdkvT8dfytZK0Oqu+1nlcTzlEf7zDve5dMPzF0MhNoT0/4vlOEKqx8gEcA9SNo+bY81mh/Vw0R3FqK8YTXxxTEsBtXD3xCvsWmgJGKbpKm7VZP49gYKGfQruR25tBiNopkPeiAK4UjDiQ7lzfsEN9xWy6g6B8Dp/6fEkKTxHlK5EUwPpszCZDhSwaRYhz6j+W7JeO9LC/LJfZ4dS3+iMTdxtodC2Rrm3Da1ZSPRGQiwTGElOAhc5XJYkdIrv3h+G3ObViRXevP0iLpiC8lf3DRRoPAy8fdQwQCU7ezn9Mdsj9MeezTYyEwp3mCXWx9ZrHfBl8Tpuj0enllQIjpcGWHaZZ0Pce31sRoi8vaoQCRu3pbm4cKKwU/sYlQNBX0D1DE2ZQB6C2NvtwdIU2AZcYvGjXDGaZ5YRh1+Kq/vvRISvQFSbl5uUPMpfgTqhDPYwjIfsEoVv9Rki9dFh4xLuk3n8Mo6lWm+BcQUNPG2fz+8MH3Hd2QfCNNynHEJAbT2KFh5VIKwzoughUBZR9r7VmSoYg2l3YXb5hg== + + + RSA PKCS#1-V1.5 + + SHA1 + + surface-coding + + 0zkJi3NJke+OehTek7lsFT675JU= + + VObVcSvMdfw4pE7S2Q1o/oeE3d72yhuu42hZz5vpcg8elVfRPFrpxECWR1f9cNxSX6j32JJ9W1+vD2YNOeykWg9iIcbq0e82d/WTvkOs5eIFIWtqjGidOvnG7UCUYBktBiKJ5IDeo68P6GfsEtby8o54MlIztvWXeB5T21nhz2I= + +
+"""; + Assert.Throws(() => new PublishLicense(PublishLicenseTemplate)); + } + + [Fact] + public void AcquireUseLicense_NullSecureEnvironment_ThrowsArgumentNullException() + { + var license = new PublishLicense(PublishLicenseTemplate); + Assert.Throws("secureEnvironment", () => license.AcquireUseLicense(null)); + } + + [Fact] + public void AcquireUseLicenseNoUI_NullSecureEnvironment_ThrowsArgumentNullException() + { + var license = new PublishLicense(PublishLicenseTemplate); + Assert.Throws("secureEnvironment", () => license.AcquireUseLicenseNoUI(null)); + } + + [Fact] + public void DecryptUnsignedPublishLicense_Invoke_Success() + { + var license = new PublishLicense(PublishLicenseTemplate); + } + + [Fact] + public void DecryptUnsignedPublishLicense_NullCryptoProvider_ThrowsArgumentNullException() + { + var license = new PublishLicense(PublishLicenseTemplate); + Assert.Throws("cryptoProvider", () => license.DecryptUnsignedPublishLicense(null)); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/RightsManagementExceptionTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/RightsManagementExceptionTests.cs new file mode 100644 index 00000000000..f78730c9902 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/RightsManagementExceptionTests.cs @@ -0,0 +1,267 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.Serialization; + +namespace System.Security.RightsManagement.Tests; + +public class RightsManagementExceptionTests +{ + [Fact] + public void Ctor_Default() + { + var exception = new RightsManagementException(); + Assert.Equal(RightsManagementFailureCode.Success, exception.FailureCode); + Assert.Equal(-2146233088, exception.HResult); + Assert.Null(exception.InnerException); + Assert.NotEmpty(exception.Message); + Assert.Null(exception.Source); + Assert.Null(exception.TargetSite); + } + + [Theory] + [InlineData(RightsManagementFailureCode.Success)] + [InlineData(RightsManagementFailureCode.ServerError)] + [InlineData(RightsManagementFailureCode.Success - 1)] + public void Ctor_RightsManagementFailureCode(RightsManagementFailureCode failureCode) + { + var exception = new RightsManagementException(failureCode); + Assert.Equal(failureCode, exception.FailureCode); + Assert.Equal(-2146233088, exception.HResult); + Assert.Null(exception.InnerException); + Assert.NotEmpty(exception.Message); + Assert.Null(exception.Source); + Assert.Null(exception.TargetSite); + } + + [Theory] + [InlineData("")] + [InlineData("message")] + public void Ctor_String(string message) + { + var exception = new RightsManagementException(message); + Assert.Equal(RightsManagementFailureCode.Success, exception.FailureCode); + Assert.Equal(-2146233088, exception.HResult); + Assert.Null(exception.InnerException); + Assert.Equal(message, exception.Message); + Assert.Null(exception.Source); + Assert.Null(exception.TargetSite); + } + + [Fact] + public void Ctor_String_Null() + { + var exception = new RightsManagementException(null!); + Assert.Equal(RightsManagementFailureCode.Success, exception.FailureCode); + Assert.Equal(-2146233088, exception.HResult); + Assert.Null(exception.InnerException); + Assert.NotEmpty(exception.Message); + Assert.Null(exception.Source); + Assert.Null(exception.TargetSite); + } + + public static IEnumerable Ctor_RightsManagementFailureCode_Exception_TestData() + { + yield return new object?[] { RightsManagementFailureCode.Success, null }; + yield return new object?[] { RightsManagementFailureCode.ServerError, new Exception() }; + yield return new object?[] { RightsManagementFailureCode.Success - 1, new Exception() }; + } + + [Theory] + [MemberData(nameof(Ctor_RightsManagementFailureCode_Exception_TestData))] + public void Ctor_RightsManagementFailureCode_Exception(RightsManagementFailureCode failureCode, Exception innerException) + { + var exception = new RightsManagementException(failureCode, innerException); + Assert.Equal(failureCode, exception.FailureCode); + Assert.Equal(-2146233088, exception.HResult); + Assert.Same(innerException, exception.InnerException); + Assert.NotEmpty(exception.Message); + Assert.Null(exception.Source); + Assert.Null(exception.TargetSite); + } + + [Theory] + [InlineData(RightsManagementFailureCode.Success, "")] + [InlineData(RightsManagementFailureCode.ServerError, "message")] + [InlineData(RightsManagementFailureCode.Success - 1, "message")] + public void Ctor_RightsManagementFailureCode_String(RightsManagementFailureCode failureCode, string message) + { + var exception = new RightsManagementException(failureCode, message); + Assert.Equal(failureCode, exception.FailureCode); + Assert.Equal(-2146233088, exception.HResult); + Assert.Null(exception.InnerException); + Assert.Equal(message, exception.Message); + Assert.Null(exception.Source); + Assert.Null(exception.TargetSite); + } + + [Theory] + [InlineData(RightsManagementFailureCode.Success)] + [InlineData(RightsManagementFailureCode.ServerError)] + [InlineData(RightsManagementFailureCode.Success - 1)] + public void Ctor_RightsManagementFailureCode_String_Null(RightsManagementFailureCode failureCode) + { + var exception = new RightsManagementException(failureCode, (string)null!); + Assert.Equal(failureCode, exception.FailureCode); + Assert.Equal(-2146233088, exception.HResult); + Assert.Null(exception.InnerException); + Assert.NotEmpty(exception.Message); + Assert.Null(exception.Source); + Assert.Null(exception.TargetSite); + } + + public static IEnumerable Ctor_String_Exception_TestData() + { + yield return new object?[] { "", null }; + yield return new object?[] { "message", new Exception() }; + } + + [Theory] + [MemberData(nameof(Ctor_String_Exception_TestData))] + public void Ctor_String_Exception(string message, Exception innerException) + { + var exception = new RightsManagementException(message, innerException); + Assert.Equal(RightsManagementFailureCode.Success, exception.FailureCode); + Assert.Equal(-2146233088, exception.HResult); + Assert.Same(innerException, exception.InnerException); + Assert.Equal(message, exception.Message); + Assert.Null(exception.Source); + Assert.Null(exception.TargetSite); + } + + public static IEnumerable Ctor_String_Exception_Null_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new Exception() }; + } + + [Theory] + [MemberData(nameof(Ctor_String_Exception_Null_TestData))] + public void Ctor_String_Exception_Null(Exception innerException) + { + var exception = new RightsManagementException(null, innerException); + Assert.Equal(RightsManagementFailureCode.Success, exception.FailureCode); + Assert.Equal(-2146233088, exception.HResult); + Assert.Same(innerException, exception.InnerException); + Assert.NotEmpty(exception.Message); + Assert.Null(exception.Source); + Assert.Null(exception.TargetSite); + } + + public static IEnumerable Ctor_RightsManagementFailureCode_String_Exception_TestData() + { + yield return new object?[] { RightsManagementFailureCode.Success, "", null }; + yield return new object?[] { RightsManagementFailureCode.ServerError, "message", new Exception() }; + yield return new object?[] { RightsManagementFailureCode.Success - 1, "message", new Exception() }; + } + + [Theory] + [MemberData(nameof(Ctor_RightsManagementFailureCode_String_Exception_TestData))] + public void Ctor_RightsManagementFailureCode_String_Exception(RightsManagementFailureCode failureCode, string message, Exception innerException) + { + var exception = new RightsManagementException(failureCode, message, innerException); + Assert.Equal(failureCode, exception.FailureCode); + Assert.Equal(-2146233088, exception.HResult); + Assert.Same(innerException, exception.InnerException); + Assert.Equal(message, exception.Message); + Assert.Null(exception.Source); + Assert.Null(exception.TargetSite); + } + + public static IEnumerable Ctor_RightsManagementFailureCode_String_Exception_Null_TestData() + { + yield return new object?[] { RightsManagementFailureCode.Success, null }; + yield return new object?[] { RightsManagementFailureCode.ServerError, new Exception() }; + yield return new object?[] { RightsManagementFailureCode.Success - 1, new Exception() }; + } + + [Theory] + [MemberData(nameof(Ctor_RightsManagementFailureCode_String_Exception_Null_TestData))] + public void Ctor_RightsManagementFailureCode_String_Exception_Null(RightsManagementFailureCode failureCode, Exception innerException) + { + var exception = new RightsManagementException(failureCode, null, innerException); + Assert.Equal(failureCode, exception.FailureCode); + Assert.Equal(-2146233088, exception.HResult); + Assert.Same(innerException, exception.InnerException); + Assert.NotEmpty(exception.Message); + Assert.Null(exception.Source); + Assert.Null(exception.TargetSite); + } + +#pragma warning disable SYSLIB0050, SYSLIB0051 + [Fact] + public void Ctor_SerializationInfo_StreamingContext() + { + var info = new SerializationInfo(typeof(RightsManagementException), new FormatterConverter()); + info.AddValue("FailureCode", RightsManagementFailureCode.ServerError); + info.AddValue("Message", "message"); + info.AddValue("InnerException", new DivideByZeroException()); + info.AddValue("HelpURL", "HelpURL"); + info.AddValue("StackTraceString", "StackTraceString"); + info.AddValue("RemoteStackTraceString", "RemoteStackTraceString"); + info.AddValue("HResult", -2146233088); + info.AddValue("Source", null); + + var exception = new SubRightsManagementException(info, new StreamingContext()); + Assert.Equal(RightsManagementFailureCode.ServerError, exception.FailureCode); + Assert.Equal(-2146233088, exception.HResult); + Assert.NotNull(exception.InnerException); + Assert.Equal("message", exception.Message); + Assert.Null(exception.Source); + Assert.Null(exception.TargetSite); + } + + [Fact] + public void Ctor_NullInfo_ThrowsArgumentNullException() + { + Assert.Throws("info", () => new SubRightsManagementException((SerializationInfo)null!, default)); + } + + [Fact] + public void GetObjectData_Invoke_Success() + { + var exception = new RightsManagementException(); + var info = new SerializationInfo(typeof(RightsManagementException), new FormatterConverter()); + var context = new StreamingContext(); + + exception.GetObjectData(info, context); + Assert.Equal(0, info.GetValue("FailureCode", typeof(int))); + Assert.Equal(-2146233088, info.GetInt32("HResult")); + Assert.Null(exception.InnerException); + Assert.NotEmpty(exception.Message); + Assert.Null(exception.Source); + Assert.Null(exception.TargetSite); + } + + [Fact] + public void GetObjectData_InvokeCustomFailureCode_Success() + { + var exception = new RightsManagementException(RightsManagementFailureCode.ManifestPolicyViolation); + var info = new SerializationInfo(typeof(RightsManagementException), new FormatterConverter()); + var context = new StreamingContext(); + + exception.GetObjectData(info, context); + Assert.Equal(-2147183860, info.GetValue("FailureCode", typeof(int))); + Assert.Equal(-2146233088, info.GetInt32("HResult")); + Assert.Null(exception.InnerException); + Assert.NotEmpty(exception.Message); + Assert.Null(exception.Source); + Assert.Null(exception.TargetSite); + } + + [Fact] + public void GetObjectData_NullInfo_ThrowsArgumentNullException() + { + var exception = new RightsManagementException(); + Assert.Throws("info", () => exception.GetObjectData(null!, default)); + } +#pragma warning restore SYSLIB0050, SYSLIB0051 + + private class SubRightsManagementException : RightsManagementException + { + public SubRightsManagementException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/SecureEnvironmentTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/SecureEnvironmentTests.cs new file mode 100644 index 00000000000..b791d6ed93b --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/SecureEnvironmentTests.cs @@ -0,0 +1,204 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; + +namespace System.Security.RightsManagement.Tests; + +public class SecureEnvironmentTests +{ +#if false + [Fact] + public void Create() + { + const string Manifest = +@" + + + + + + + + + + +"; + SecureEnvironment environment = SecureEnvironment.Create(Manifest, AuthenticationType.Passport, UserActivationMode.Permanent); + } +#endif + + [Fact] + public void Create_NullApplicationManifest_ThrowsArgumentNullException() + { + Assert.Throws("applicationManifest", () => SecureEnvironment.Create(null!, AuthenticationType.Windows, UserActivationMode.Permanent)); + Assert.Throws("applicationManifest", () => SecureEnvironment.Create(null!, new ContentUser("name", AuthenticationType.Windows))); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + public void Create_EmptyApplicationManifest_ThrowsRightsManagementException(string applicationManifest) + { + Assert.Throws(() => SecureEnvironment.Create(applicationManifest, AuthenticationType.Windows, UserActivationMode.Permanent)); + Assert.Throws(() => SecureEnvironment.Create(applicationManifest, new ContentUser("name", AuthenticationType.Windows))); + } + + [Theory] + [InlineData("applicationManifest")] + public void Create_InvokeInvalidApplicationManifest_ThrowsRightsManagementException(string applicationManifest) + { + Assert.Throws(() => SecureEnvironment.Create(applicationManifest, AuthenticationType.Windows, UserActivationMode.Permanent)); + Assert.Throws(() => SecureEnvironment.Create(applicationManifest, new ContentUser("name", AuthenticationType.Windows))); + } + + [Theory] + [InlineData(AuthenticationType.Internal)] + [InlineData(AuthenticationType.WindowsPassport)] + [InlineData(AuthenticationType.Windows - 1)] + [InlineData(AuthenticationType.Internal + 1)] + public void Create_InvalidAuthenticationType_ThrowsArgumentOutOfRangeException(AuthenticationType authentication) + { + Assert.Throws("authentication", () => SecureEnvironment.Create("manifest", authentication, UserActivationMode.Permanent)); + } + + [Theory] + [InlineData(UserActivationMode.Permanent - 1)] + [InlineData(UserActivationMode.Temporary + 1)] + public void Create_InvalidUserActivationMode_ThrowsArgumentOutOfRangeException(UserActivationMode userActivationMode) + { + Assert.Throws("userActivationMode", () => SecureEnvironment.Create("manifest", AuthenticationType.Windows, userActivationMode)); + } + + [Fact] + public void Create_NullUser_ThrowsArgumentNullException() + { + Assert.Throws("user", () => SecureEnvironment.Create("manifest", null!)); + } + + [Fact] + public void Create_UserNotActivatedWindows_ThrowsRightsManagementException() + { + var user = new ContentUser("name", AuthenticationType.Windows); + Assert.Throws(() => SecureEnvironment.Create("", user)); + } + + [Fact] + public void Create_UserNotActivatedPassport_ThrowsRightsManagementException() + { + var user = new ContentUser("name", AuthenticationType.Passport); + Assert.Throws(() => SecureEnvironment.Create("", user)); + } + + [Fact] + public void Create_WindowsPassportUser_ThrowsArgumentOutOfRangeException() + { + var user = new ContentUser("name", AuthenticationType.WindowsPassport); + Assert.Throws("user", () => SecureEnvironment.Create("manifest", user)); + } + + [Theory] + [InlineData("anyone")] + [InlineData("owner")] + public void Create_InternalUser_ThrowsArgumentOutOfRangeException(string name) + { + var user = new ContentUser(name, AuthenticationType.Internal); + Assert.Throws("user", () => SecureEnvironment.Create("manifest", user)); + } + + [Fact] + public void Create_NotActivatedWindowsUser_ThrowsRightsManagementException() + { + var user = new ContentUser("name", AuthenticationType.Windows); + Assert.Throws(() => SecureEnvironment.Create("manifest", user)); + } + + [Fact] + public void Create_NotActivatedPassportUser_ThrowsRightsManagementException() + { + var user = new ContentUser("name", AuthenticationType.Passport); + Assert.Throws(() => SecureEnvironment.Create("manifest", user)); + } + + [Fact] + public void GetActivatedUsers_Invoke_ReturnsExpected() + { + var users = SecureEnvironment.GetActivatedUsers(); + Assert.NotNull(users); + } + + [Fact] + public void IsUserActivated_WindowsUser_ReturnsFalse() + { + var user = new ContentUser("name", AuthenticationType.Windows); + Assert.False(SecureEnvironment.IsUserActivated(user)); + } + + [Fact] + public void IsUserActivated_PassportUser_ReturnsFalse() + { + var user = new ContentUser("name", AuthenticationType.Passport); + Assert.False(SecureEnvironment.IsUserActivated(user)); + } + + [Fact] + public void IsUserActivated_NullUser_ThrowsArgumentNullException() + { + Assert.Throws("user", () => SecureEnvironment.IsUserActivated(null!)); + } + + [Fact] + public void IsUserActivated_WindowsPassportUser_ThrowsArgumentOutOfRangeException() + { + var user = new ContentUser("name", AuthenticationType.WindowsPassport); + Assert.Throws("user", () => SecureEnvironment.IsUserActivated(user)); + } + + [Theory] + [InlineData("anyone")] + [InlineData("owner")] + public void IsUserActivated_InternalUser_ThrowsArgumentOutOfRangeException(string name) + { + var user = new ContentUser(name, AuthenticationType.Internal); + Assert.Throws("user", () => SecureEnvironment.IsUserActivated(user)); + } + + [Fact] + public void RemoveActivatedUser_WindowsUser_ReturnsFalse() + { + var user = new ContentUser("name", AuthenticationType.Windows); + SecureEnvironment.RemoveActivatedUser(user); + Assert.False(SecureEnvironment.IsUserActivated(user)); + } + + [Fact] + public void RemoveActivatedUser_PassportUser_ReturnsFalse() + { + var user = new ContentUser("name", AuthenticationType.Passport); + SecureEnvironment.RemoveActivatedUser(user); + Assert.False(SecureEnvironment.IsUserActivated(user)); + } + + [Fact] + public void RemoveActivatedUser_NullUser_ThrowsArgumentNullException() + { + Assert.Throws("user", () => SecureEnvironment.RemoveActivatedUser(null!)); + } + + [Fact] + public void RemoveActivatedUser_WindowsPassportUser_ThrowsArgumentOutOfRangeException() + { + var user = new ContentUser("name", AuthenticationType.WindowsPassport); + Assert.Throws("user", () => SecureEnvironment.RemoveActivatedUser(user)); + } + + [Theory] + [InlineData("anyone")] + [InlineData("owner")] + public void RemoveActivatedUser_InternalUser_ThrowsArgumentOutOfRangeException(string name) + { + var user = new ContentUser(name, AuthenticationType.Internal); + Assert.Throws("user", () => SecureEnvironment.RemoveActivatedUser(user)); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/UnsignedPublishLicenseTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/UnsignedPublishLicenseTests.cs new file mode 100644 index 00000000000..207f8956133 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/UnsignedPublishLicenseTests.cs @@ -0,0 +1,138 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Security.RightsManagement.Tests; + +public class UnsignedPublishLicenseTests +{ + [Fact] + public void Ctor_Default() + { + var license = new UnsignedPublishLicense(); + Assert.NotEqual(Guid.Empty, license.ContentId); + Assert.Empty(license.Grants); + Assert.Same(license.Grants, license.Grants); + Assert.Empty(license.LocalizedNameDescriptionDictionary); + Assert.Same(license.LocalizedNameDescriptionDictionary, license.LocalizedNameDescriptionDictionary); + Assert.Null(license.Owner); + Assert.Null(license.ReferralInfoName); + Assert.Null(license.ReferralInfoUri); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData("unsignedPublishLicense")] + [InlineData("")] + public void Ctor_InvalidPublishLicenseTemplate_ThrowsRightsManagementException(string unsignedPublishLicense) + { + Assert.Throws(() => new UnsignedPublishLicense(unsignedPublishLicense)); + } + + [Fact] + public void Ctor_NullPublishLicenseTemplate_ThrowsArgumentNullException() + { + Assert.Throws("publishLicenseTemplate", () => new UnsignedPublishLicense(null)); + } + + public static IEnumerable ContentId_TestData() + { + yield return new object[] { Guid.Empty }; + yield return new object[] { Guid.NewGuid() }; + } + + [Theory] + [MemberData(nameof(ContentId_TestData))] + public void ContentId_Set_GetReturnsExpected(Guid value) + { + var license = new UnsignedPublishLicense + { + ContentId = value + }; + Assert.Equal(value, license.ContentId); + + // Set same. + license.ContentId = value; + Assert.Equal(value, license.ContentId); + } + + public static IEnumerable Owner_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new ContentUser("name", AuthenticationType.Windows) }; + yield return new object?[] { new ContentUser("name", AuthenticationType.Passport) }; + yield return new object?[] { new ContentUser("name", AuthenticationType.WindowsPassport) }; + yield return new object?[] { new ContentUser("anyone", AuthenticationType.Internal) }; + yield return new object?[] { new ContentUser("owner", AuthenticationType.Internal) }; + } + + [Theory] + [MemberData(nameof(Owner_TestData))] + public void Owner_Set_GetReturnsExpected(ContentUser? value) + { + var license = new UnsignedPublishLicense + { + Owner = value + }; + Assert.Equal(value, license.Owner); + + // Set same. + license.Owner = value; + Assert.Equal(value, license.Owner); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("referralInfoName")] + public void ReferralInfoName_Set_GetReturnsExpected(string? value) + { + var license = new UnsignedPublishLicense + { + ReferralInfoName = value + }; + Assert.Equal(value, license.ReferralInfoName); + + // Set same. + license.ReferralInfoName = value; + Assert.Equal(value, license.ReferralInfoName); + } + + public static IEnumerable ReferralInfoUri_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new Uri("https://google.com") }; + } + + [Theory] + [MemberData(nameof(ReferralInfoUri_TestData))] + public void ReferralInfoUri_Set_GetReturnsExpected(Uri? value) + { + var license = new UnsignedPublishLicense + { + ReferralInfoUri = value + }; + Assert.Equal(value, license.ReferralInfoUri); + + // Set same. + license.ReferralInfoUri = value; + Assert.Equal(value, license.ReferralInfoUri); + } + + [Fact] + public void Sign_NullSecureEnvironment_ThrowsArgumentNullException() + { + var license = new UnsignedPublishLicense(); + UseLicense? useLicense = null; + Assert.Throws("secureEnvironment", () => license.Sign(null!, out useLicense)); + Assert.Null(useLicense); + } + + [Fact] + public void ToString_Default_ThrowsRightsManagementException() + { + var license = new UnsignedPublishLicense(); + Assert.Throws(() => license.ToString()); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/UseLicenseTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/UseLicenseTests.cs new file mode 100644 index 00000000000..bca0b07a54f --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Security/RightsManagement/UseLicenseTests.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Security.RightsManagement.Tests; + +public class UseLicenseTests +{ + [Fact] + public void Ctor_NullUseLicense_ThrowsArgumentNullException() + { + Assert.Throws("useLicense", () => new UseLicense(null!)); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData("name")] + [InlineData("")] + public void Ctor_InvalidUseLicense_ThrowsRightsManagementException(string useLicense) + { + Assert.Throws(() => new UseLicense(useLicense)); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/AttachedPropertyBrowsableForTypeAttributeTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/AttachedPropertyBrowsableForTypeAttributeTests.cs new file mode 100644 index 00000000000..f0371ea76d5 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/AttachedPropertyBrowsableForTypeAttributeTests.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Tests; + +public class AttachedPropertyBrowsableForTypeAttributeTests +{ + // TODO: + // - IsBrowsable + // - UnionResults + + [Fact] + public void Ctor_Type() + { + var attribute = new AttachedPropertyBrowsableForTypeAttribute(typeof(string)); + Assert.Equal(typeof(string), attribute.TargetType); + Assert.Same(attribute, attribute.TypeId); + } + + [Fact] + public void Ctor_NullTargetType_ThrowsArgumentNullException() + { + Assert.Throws("targetType", () => new AttachedPropertyBrowsableForTypeAttribute(null!)); + } + + public static IEnumerable Equals_TestData() + { + var attribute = new AttachedPropertyBrowsableForTypeAttribute(typeof(string)); + yield return new object?[] { attribute, attribute, true }; + yield return new object?[] { attribute, new AttachedPropertyBrowsableForTypeAttribute(typeof(string)), true }; + yield return new object?[] { attribute, new AttachedPropertyBrowsableForTypeAttribute(typeof(int)), false }; + yield return new object?[] { attribute, new object(), false }; + yield return new object?[] { attribute, null, false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void Equals_Object_ReturnsExpected(AttachedPropertyBrowsableForTypeAttribute attribute, object obj, bool expected) + { + Assert.Equal(expected, attribute.Equals(obj)); + if (obj is AttachedPropertyBrowsableForTypeAttribute otherAttribute) + { + Assert.Equal(expected, otherAttribute.Equals(attribute)); + Assert.Equal(expected, attribute.GetHashCode().Equals(obj.GetHashCode())); + } + } + + [Fact] + public void GetHashCode_Invoke_ReturnsEqual() + { + var attribute = new AttachedPropertyBrowsableForTypeAttribute(typeof(string)); + Assert.NotEqual(0, attribute.GetHashCode()); + Assert.Equal(attribute.GetHashCode(), attribute.GetHashCode()); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/AttachedPropertyBrowsableWhenAttributePresentAttributeTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/AttachedPropertyBrowsableWhenAttributePresentAttributeTests.cs new file mode 100644 index 00000000000..3970ef8eba8 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/AttachedPropertyBrowsableWhenAttributePresentAttributeTests.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Tests; + +public class AttachedPropertyBrowsableWhenAttributePresentAttributeTests +{ + // TODO: + // - IsBrowsable + + [Fact] + public void Ctor_Type() + { + var attribute = new AttachedPropertyBrowsableWhenAttributePresentAttribute(typeof(string)); + Assert.Equal(typeof(string), attribute.AttributeType); + Assert.Equal(typeof(AttachedPropertyBrowsableWhenAttributePresentAttribute), attribute.TypeId); + } + + [Fact] + public void Ctor_NullAttributeType_ThrowsArgumentNullException() + { + Assert.Throws("attributeType", () => new AttachedPropertyBrowsableWhenAttributePresentAttribute(null!)); + } + + public static IEnumerable Equals_TestData() + { + var attribute = new AttachedPropertyBrowsableWhenAttributePresentAttribute(typeof(string)); + yield return new object?[] { attribute, attribute, true }; + yield return new object?[] { attribute, new AttachedPropertyBrowsableWhenAttributePresentAttribute(typeof(string)), true }; + yield return new object?[] { attribute, new AttachedPropertyBrowsableWhenAttributePresentAttribute(typeof(int)), false }; + yield return new object?[] { attribute, new object(), false }; + yield return new object?[] { attribute, null, false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void Equals_Object_ReturnsExpected(AttachedPropertyBrowsableWhenAttributePresentAttribute attribute, object obj, bool expected) + { + Assert.Equal(expected, attribute.Equals(obj)); + if (obj is AttachedPropertyBrowsableWhenAttributePresentAttribute otherAttribute) + { + Assert.Equal(expected, otherAttribute.Equals(attribute)); + Assert.Equal(expected, attribute.GetHashCode().Equals(obj.GetHashCode())); + } + } + + [Fact] + public void GetHashCode_Invoke_ReturnsEqual() + { + var attribute = new AttachedPropertyBrowsableWhenAttributePresentAttribute(typeof(string)); + Assert.NotEqual(0, attribute.GetHashCode()); + Assert.Equal(attribute.GetHashCode(), attribute.GetHashCode()); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/BaseCompatibilityPreferencesTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/BaseCompatibilityPreferencesTests.cs new file mode 100644 index 00000000000..3555537190c --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/BaseCompatibilityPreferencesTests.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Tests; + +public class BaseCompatibilityPreferencesTests +{ + [Fact] + public void GetFlowDirectionFromLayoutRounding_Get_ReturnsExpected() + { + bool value = BaseCompatibilityPreferences.FlowDispatcherSynchronizationContextPriority; + Assert.Equal(value, BaseCompatibilityPreferences.FlowDispatcherSynchronizationContextPriority); + } + + [Fact] + public void HandleDispatcherRequestProcessingFailure_Get_ReturnsExpected() + { + BaseCompatibilityPreferences.HandleDispatcherRequestProcessingFailureOptions value = BaseCompatibilityPreferences.HandleDispatcherRequestProcessingFailure; + Assert.True(Enum.IsDefined(value)); + Assert.Equal(value, BaseCompatibilityPreferences.HandleDispatcherRequestProcessingFailure); + } + + [Fact] + public void InlineDispatcherSynchronizationContextSend_Get_ReturnsExpected() + { + bool value = BaseCompatibilityPreferences.InlineDispatcherSynchronizationContextSend; + Assert.Equal(value, BaseCompatibilityPreferences.InlineDispatcherSynchronizationContextSend); + } + + [Fact] + public void ReuseDispatcherSynchronizationContextInstance_Get_ReturnsExpected() + { + bool value = BaseCompatibilityPreferences.ReuseDispatcherSynchronizationContextInstance; + Assert.Equal(value, BaseCompatibilityPreferences.ReuseDispatcherSynchronizationContextInstance); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Converters/Int32RectValueSerializerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Converters/Int32RectValueSerializerTests.cs new file mode 100644 index 00000000000..985c4589337 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Converters/Int32RectValueSerializerTests.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Windows.Markup; +using System.Windows.Tests; + +namespace System.Windows.Converters.Tests; + +public class Int32RectValueSerializerTests +{ + public static IEnumerable CanConvertToString_TestData() + { + yield return new object?[] { null, false }; + yield return new object?[] { string.Empty, false }; + yield return new object?[] { "value", false }; + yield return new object?[] { new object(), false }; + + yield return new object?[] { new Int32Rect(), true }; + } + + [Theory] + [MemberData(nameof(CanConvertToString_TestData))] + public void CanConvertToString_Invoke_ReturnsExpected(object value, bool expected) + { + var serializer = new Int32RectValueSerializer(); + Assert.Equal(expected, serializer.CanConvertToString(value, null)); + Assert.Equal(expected, serializer.CanConvertToString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(Int32RectTests.ToString_TestData), MemberType = typeof(Int32RectTests))] + public void ConvertToString_Invoke_ReturnsExpected(Int32Rect matrix, string expected) + { + var serializer = new Int32RectValueSerializer(); + Assert.Equal(expected, serializer.ConvertToString(matrix, null)); + Assert.Equal(expected, serializer.ConvertToString(matrix, new CustomValueSerializerContext())); + } + + public static IEnumerable ConvertToString_CantConvert_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + yield return new object?[] { new object() }; + } + + [Theory] + [MemberData(nameof(ConvertToString_CantConvert_TestData))] + public void ConvertToString_CantConvert_ThrowsNotSupportedException(object value) + { + var serializer = new Int32RectValueSerializer(); + Assert.Throws(() => serializer.ConvertToString(value, null)); + Assert.Throws(() => serializer.ConvertToString(value, new CustomValueSerializerContext())); + } + + public static IEnumerable CanConvertFromString_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + } + + [Theory] + [MemberData(nameof(CanConvertFromString_TestData))] + public void CanConvertFromString_Invoke_ReturnsTrue(string value) + { + var serializer = new Int32RectValueSerializer(); + Assert.True(serializer.CanConvertFromString(value, null)); + Assert.True(serializer.CanConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(Int32RectTests.Parse_TestData), MemberType = typeof(Int32RectTests))] + public void ConvertFromString_Invoke_ReturnsExpected(string value, Int32Rect expected) + { + var serializer = new Int32RectValueSerializer(); + Assert.Equal(expected, serializer.ConvertFromString(value, null)); + Assert.Equal(expected, serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Fact] + public void ConvertFromString_NullValue_ThrowsNotSupportedException() + { + var serializer = new Int32RectValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(null, null)); + Assert.Throws(() => serializer.ConvertFromString(null, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(Int32RectTests.Parse_InvalidSource_TestData), MemberType = typeof(Int32RectTests))] + public void ConvertFromString_InvalidValue_ThrowsInvalidOperationException(string value) + { + var serializer = new Int32RectValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(Int32RectTests.Parse_NotInt32_TestData), MemberType = typeof(Int32RectTests))] + public void ConvertFromString_NotInt_ThrowsFormatException(string value) + { + var serializer = new Int32RectValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + private class CustomValueSerializerContext : IValueSerializerContext + { + public IContainer Container => throw new NotImplementedException(); + + public object Instance => throw new NotImplementedException(); + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(PropertyDescriptor descriptor) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(Type type) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Converters/PointValueSerializerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Converters/PointValueSerializerTests.cs new file mode 100644 index 00000000000..525352b473a --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Converters/PointValueSerializerTests.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Windows.Markup; +using System.Windows.Tests; + +namespace System.Windows.Converters.Tests; + +public class PointValueSerializerTests +{ + public static IEnumerable CanConvertToString_TestData() + { + yield return new object?[] { null, false }; + yield return new object?[] { string.Empty, false }; + yield return new object?[] { "value", false }; + yield return new object?[] { new object(), false }; + + yield return new object?[] { new Point(), true }; + } + + [Theory] + [MemberData(nameof(CanConvertToString_TestData))] + public void CanConvertToString_Invoke_ReturnsExpected(object value, bool expected) + { + var serializer = new PointValueSerializer(); + Assert.Equal(expected, serializer.CanConvertToString(value, null)); + Assert.Equal(expected, serializer.CanConvertToString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(PointTests.ToString_TestData), MemberType = typeof(PointTests))] + public void ConvertToString_Invoke_ReturnsExpected(Point matrix, string expected) + { + var serializer = new PointValueSerializer(); + Assert.Equal(expected, serializer.ConvertToString(matrix, null)); + Assert.Equal(expected, serializer.ConvertToString(matrix, new CustomValueSerializerContext())); + } + + public static IEnumerable ConvertToString_CantConvert_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + yield return new object?[] { new object() }; + } + + [Theory] + [MemberData(nameof(ConvertToString_CantConvert_TestData))] + public void ConvertToString_CantConvert_ThrowsNotSupportedException(object value) + { + var serializer = new PointValueSerializer(); + Assert.Throws(() => serializer.ConvertToString(value, null)); + Assert.Throws(() => serializer.ConvertToString(value, new CustomValueSerializerContext())); + } + + public static IEnumerable CanConvertFromString_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + } + + [Theory] + [MemberData(nameof(CanConvertFromString_TestData))] + public void CanConvertFromString_Invoke_ReturnsTrue(string value) + { + var serializer = new PointValueSerializer(); + Assert.True(serializer.CanConvertFromString(value, null)); + Assert.True(serializer.CanConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(PointTests.Parse_TestData), MemberType = typeof(PointTests))] + public void ConvertFromString_Invoke_ReturnsExpected(string value, Point expected) + { + var serializer = new PointValueSerializer(); + Assert.Equal(expected, serializer.ConvertFromString(value, null)); + Assert.Equal(expected, serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Fact] + public void ConvertFromString_NullValue_ThrowsNotSupportedException() + { + var serializer = new PointValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(null, null)); + Assert.Throws(() => serializer.ConvertFromString(null, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(PointTests.Parse_InvalidSource_TestData), MemberType = typeof(PointTests))] + public void ConvertFromString_InvalidValue_ThrowsInvalidOperationException(string value) + { + var serializer = new PointValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(PointTests.Parse_NotDouble_TestData), MemberType = typeof(PointTests))] + public void ConvertFromString_NotDouble_ThrowsFormatException(string value) + { + var serializer = new PointValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + private class CustomValueSerializerContext : IValueSerializerContext + { + public IContainer Container => throw new NotImplementedException(); + + public object Instance => throw new NotImplementedException(); + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(PropertyDescriptor descriptor) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(Type type) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Converters/RectValueSerializerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Converters/RectValueSerializerTests.cs new file mode 100644 index 00000000000..c1cfd9fc4aa --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Converters/RectValueSerializerTests.cs @@ -0,0 +1,136 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Windows.Markup; +using System.Windows.Tests; + +namespace System.Windows.Converters.Tests; + +public class RectValueSerializerTests +{ + public static IEnumerable CanConvertToString_TestData() + { + yield return new object?[] { null, false }; + yield return new object?[] { string.Empty, false }; + yield return new object?[] { "value", false }; + yield return new object?[] { new object(), false }; + + yield return new object?[] { new Rect(), true }; + } + + [Theory] + [MemberData(nameof(CanConvertToString_TestData))] + public void CanConvertToString_Invoke_ReturnsExpected(object value, bool expected) + { + var serializer = new RectValueSerializer(); + Assert.Equal(expected, serializer.CanConvertToString(value, null)); + Assert.Equal(expected, serializer.CanConvertToString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(RectTests.ToString_TestData), MemberType = typeof(RectTests))] + public void ConvertToString_Invoke_ReturnsExpected(Rect matrix, string expected) + { + var serializer = new RectValueSerializer(); + Assert.Equal(expected, serializer.ConvertToString(matrix, null)); + Assert.Equal(expected, serializer.ConvertToString(matrix, new CustomValueSerializerContext())); + } + + public static IEnumerable ConvertToString_CantConvert_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + yield return new object?[] { new object() }; + } + + [Theory] + [MemberData(nameof(ConvertToString_CantConvert_TestData))] + public void ConvertToString_CantConvert_ThrowsNotSupportedException(object value) + { + var serializer = new RectValueSerializer(); + Assert.Throws(() => serializer.ConvertToString(value, null)); + Assert.Throws(() => serializer.ConvertToString(value, new CustomValueSerializerContext())); + } + + public static IEnumerable CanConvertFromString_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + } + + [Theory] + [MemberData(nameof(CanConvertFromString_TestData))] + public void CanConvertFromString_Invoke_ReturnsTrue(string value) + { + var serializer = new RectValueSerializer(); + Assert.True(serializer.CanConvertFromString(value, null)); + Assert.True(serializer.CanConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(RectTests.Parse_TestData), MemberType = typeof(RectTests))] + public void ConvertFromString_Invoke_ReturnsExpected(string value, Rect expected) + { + var serializer = new RectValueSerializer(); + Assert.Equal(expected, serializer.ConvertFromString(value, null)); + Assert.Equal(expected, serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Fact] + public void ConvertFromString_NullValue_ThrowsNotSupportedException() + { + var serializer = new RectValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(null, null)); + Assert.Throws(() => serializer.ConvertFromString(null, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(RectTests.Parse_InvalidSource_TestData), MemberType = typeof(RectTests))] + public void ConvertFromString_InvalidValue_ThrowsInvalidOperationException(string value) + { + var serializer = new RectValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(RectTests.Parse_NotDouble_TestData), MemberType = typeof(RectTests))] + public void ConvertFromString_NotDouble_ThrowsFormatException(string value) + { + var serializer = new RectValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(RectTests.Parse_Negative_TestData), MemberType = typeof(RectTests))] + public void ConvertFromString_Negative_ThrowsArgumentException(string value) + { + var serializer = new RectValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + private class CustomValueSerializerContext : IValueSerializerContext + { + public IContainer Container => throw new NotImplementedException(); + + public object Instance => throw new NotImplementedException(); + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(PropertyDescriptor descriptor) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(Type type) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Converters/SizeValueSerializerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Converters/SizeValueSerializerTests.cs new file mode 100644 index 00000000000..b95482919aa --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Converters/SizeValueSerializerTests.cs @@ -0,0 +1,136 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Windows.Markup; +using System.Windows.Tests; + +namespace System.Windows.Converters.Tests; + +public class SizeValueSerializerTests +{ + public static IEnumerable CanConvertToString_TestData() + { + yield return new object?[] { null, false }; + yield return new object?[] { string.Empty, false }; + yield return new object?[] { "value", false }; + yield return new object?[] { new object(), false }; + + yield return new object?[] { new Size(), true }; + } + + [Theory] + [MemberData(nameof(CanConvertToString_TestData))] + public void CanConvertToString_Invoke_ReturnsExpected(object value, bool expected) + { + var serializer = new SizeValueSerializer(); + Assert.Equal(expected, serializer.CanConvertToString(value, null)); + Assert.Equal(expected, serializer.CanConvertToString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(SizeTests.ToString_TestData), MemberType = typeof(SizeTests))] + public void ConvertToString_Invoke_ReturnsExpected(Size matrix, string expected) + { + var serializer = new SizeValueSerializer(); + Assert.Equal(expected, serializer.ConvertToString(matrix, null)); + Assert.Equal(expected, serializer.ConvertToString(matrix, new CustomValueSerializerContext())); + } + + public static IEnumerable ConvertToString_CantConvert_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + yield return new object?[] { new object() }; + } + + [Theory] + [MemberData(nameof(ConvertToString_CantConvert_TestData))] + public void ConvertToString_CantConvert_ThrowsNotSupportedException(object value) + { + var serializer = new SizeValueSerializer(); + Assert.Throws(() => serializer.ConvertToString(value, null)); + Assert.Throws(() => serializer.ConvertToString(value, new CustomValueSerializerContext())); + } + + public static IEnumerable CanConvertFromString_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + } + + [Theory] + [MemberData(nameof(CanConvertFromString_TestData))] + public void CanConvertFromString_Invoke_ReturnsTrue(string value) + { + var serializer = new SizeValueSerializer(); + Assert.True(serializer.CanConvertFromString(value, null)); + Assert.True(serializer.CanConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(SizeTests.Parse_TestData), MemberType = typeof(SizeTests))] + public void ConvertFromString_Invoke_ReturnsExpected(string value, Size expected) + { + var serializer = new SizeValueSerializer(); + Assert.Equal(expected, serializer.ConvertFromString(value, null)); + Assert.Equal(expected, serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Fact] + public void ConvertFromString_NullValue_ThrowsNotSupportedException() + { + var serializer = new SizeValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(null, null)); + Assert.Throws(() => serializer.ConvertFromString(null, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(SizeTests.Parse_InvalidSource_TestData), MemberType = typeof(SizeTests))] + public void ConvertFromString_InvalidValue_ThrowsInvalidOperationException(string value) + { + var serializer = new SizeValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(SizeTests.Parse_NotDouble_TestData), MemberType = typeof(SizeTests))] + public void ConvertFromString_NotDouble_ThrowsFormatException(string value) + { + var serializer = new SizeValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(SizeTests.Parse_Negative_TestData), MemberType = typeof(SizeTests))] + public void ConvertFromString_Negative_ThrowsArgumentException(string value) + { + var serializer = new SizeValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + private class CustomValueSerializerContext : IValueSerializerContext + { + public IContainer Container => throw new NotImplementedException(); + + public object Instance => throw new NotImplementedException(); + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(PropertyDescriptor descriptor) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(Type type) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Converters/VectorValueSerializerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Converters/VectorValueSerializerTests.cs new file mode 100644 index 00000000000..2527d05ef73 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Converters/VectorValueSerializerTests.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Windows.Markup; +using System.Windows.Tests; + +namespace System.Windows.Converters.Tests; + +public class VectorValueSerializerTests +{ + public static IEnumerable CanConvertToString_TestData() + { + yield return new object?[] { null, false }; + yield return new object?[] { string.Empty, false }; + yield return new object?[] { "value", false }; + yield return new object?[] { new object(), false }; + + yield return new object?[] { new Vector(), true }; + } + + [Theory] + [MemberData(nameof(CanConvertToString_TestData))] + public void CanConvertToString_Invoke_ReturnsExpected(object value, bool expected) + { + var serializer = new VectorValueSerializer(); + Assert.Equal(expected, serializer.CanConvertToString(value, null)); + Assert.Equal(expected, serializer.CanConvertToString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(VectorTests.ToString_TestData), MemberType = typeof(VectorTests))] + public void ConvertToString_Invoke_ReturnsExpected(Vector matrix, string expected) + { + var serializer = new VectorValueSerializer(); + Assert.Equal(expected, serializer.ConvertToString(matrix, null)); + Assert.Equal(expected, serializer.ConvertToString(matrix, new CustomValueSerializerContext())); + } + + public static IEnumerable ConvertToString_CantConvert_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + yield return new object?[] { new object() }; + } + + [Theory] + [MemberData(nameof(ConvertToString_CantConvert_TestData))] + public void ConvertToString_CantConvert_ThrowsNotSupportedException(object value) + { + var serializer = new VectorValueSerializer(); + Assert.Throws(() => serializer.ConvertToString(value, null)); + Assert.Throws(() => serializer.ConvertToString(value, new CustomValueSerializerContext())); + } + + public static IEnumerable CanConvertFromString_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + } + + [Theory] + [MemberData(nameof(CanConvertFromString_TestData))] + public void CanConvertFromString_Invoke_ReturnsTrue(string value) + { + var serializer = new VectorValueSerializer(); + Assert.True(serializer.CanConvertFromString(value, null)); + Assert.True(serializer.CanConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(VectorTests.Parse_TestData), MemberType = typeof(VectorTests))] + public void ConvertFromString_Invoke_ReturnsExpected(string value, Vector expected) + { + var serializer = new VectorValueSerializer(); + Assert.Equal(expected, serializer.ConvertFromString(value, null)); + Assert.Equal(expected, serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Fact] + public void ConvertFromString_NullValue_ThrowsNotSupportedException() + { + var serializer = new VectorValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(null, null)); + Assert.Throws(() => serializer.ConvertFromString(null, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(VectorTests.Parse_InvalidSource_TestData), MemberType = typeof(VectorTests))] + public void ConvertFromString_InvalidValue_ThrowsInvalidOperationException(string value) + { + var serializer = new VectorValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(VectorTests.Parse_NotDouble_TestData), MemberType = typeof(VectorTests))] + public void ConvertFromString_NotDouble_ThrowsFormatException(string value) + { + var serializer = new VectorValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + private class CustomValueSerializerContext : IValueSerializerContext + { + public IContainer Container => throw new NotImplementedException(); + + public object Instance => throw new NotImplementedException(); + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(PropertyDescriptor descriptor) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(Type type) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Data/DataSourceProviderTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Data/DataSourceProviderTests.cs new file mode 100644 index 00000000000..fccba18543b --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Data/DataSourceProviderTests.cs @@ -0,0 +1,1953 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Threading; +using System.Windows.Threading; + +namespace System.Windows.Data.Tests; + +public class DataSourceProviderTests +{ + [Fact] + public void Ctor_Default() + { + var provider = new SubDataSourceProvider(); + Assert.Null(provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Null(provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + } + + [Fact] + public void DataChanged_AddRemove_Success() + { + var provider = new SubDataSourceProvider(); + + int callCount = 0; + EventHandler handler = (s, e) => callCount++; + provider.DataChanged += handler; + Assert.Equal(0, callCount); + + provider.DataChanged -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + provider.DataChanged -= handler; + Assert.Equal(0, callCount); + + // Add null. + provider.DataChanged += null; + Assert.Equal(0, callCount); + + // Remove null. + provider.DataChanged -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void PropertyChanged_INotifyPropertyChangedAddRemove_Success() + { + var provider = new SubDataSourceProvider(); + + int callCount = 0; + PropertyChangedEventHandler handler = (s, e) => callCount++; + ((INotifyPropertyChanged)provider).PropertyChanged += handler; + Assert.Equal(0, callCount); + + ((INotifyPropertyChanged)provider).PropertyChanged -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + ((INotifyPropertyChanged)provider).PropertyChanged -= handler; + Assert.Equal(0, callCount); + + // Add null. + ((INotifyPropertyChanged)provider).PropertyChanged += null; + Assert.Equal(0, callCount); + + // Remove null. + ((INotifyPropertyChanged)provider).PropertyChanged -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void PropertyChanged_AddRemove_Success() + { + var provider = new SubDataSourceProvider(); + + int callCount = 0; + PropertyChangedEventHandler handler = (s, e) => callCount++; + provider.PropertyChanged += handler; + Assert.Equal(0, callCount); + + provider.PropertyChanged -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + provider.PropertyChanged -= handler; + Assert.Equal(0, callCount); + + // Add null. + provider.PropertyChanged += null; + Assert.Equal(0, callCount); + + // Remove null. + provider.PropertyChanged -= null; + Assert.Equal(0, callCount); + } + + public static IEnumerable Dispatcher_Set_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { Dispatcher.CurrentDispatcher }; + } + + [Theory] + [MemberData(nameof(Dispatcher_Set_TestData))] + public void Dispatcher_Set_GetReturnsExpected(Dispatcher value) + { + var provider = new SubDataSourceProvider(); + + // Set. + provider.Dispatcher = value; + Assert.Same(value, provider.Dispatcher); + + // Set same. + provider.Dispatcher = value; + Assert.Same(value, provider.Dispatcher); + } + + [Fact] + public void Dispatcher_SetWithHandler_DoesNotCallPropertyChanged() + { + var provider = new SubDataSourceProvider(); + int callCount = 0; + PropertyChangedEventHandler handler = (sender, e) => callCount++; + ((INotifyPropertyChanged)provider).PropertyChanged += handler; + + // Set. + provider.Dispatcher = null; + Assert.Null(provider.Dispatcher); + Assert.Equal(0, callCount); + + // Set same. + provider.Dispatcher = null; + Assert.Null(provider.Dispatcher); + Assert.Equal(0, callCount); + + // Set different. + provider.Dispatcher = Dispatcher.CurrentDispatcher; + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Equal(0, callCount); + + // Remove handler. + ((INotifyPropertyChanged)provider).PropertyChanged -= handler; + provider.Dispatcher = null; + Assert.Null(provider.Dispatcher); + Assert.Equal(0, callCount); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void IsInitialLoadEnabled_Set_GetReturnsExpected(bool value) + { + var provider = new SubDataSourceProvider(); + + // Set. + provider.IsInitialLoadEnabled = value; + Assert.Equal(value, provider.IsInitialLoadEnabled); + + // Set same. + provider.IsInitialLoadEnabled = value; + Assert.Equal(value, provider.IsInitialLoadEnabled); + + // Set different. + provider.IsInitialLoadEnabled = !value; + Assert.Equal(!value, provider.IsInitialLoadEnabled); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void IsInitialLoadEnabled_SetAlreadyLoaded_GetReturnsExpected(bool value) + { + var provider = new SubDataSourceProvider(); + provider.InitialLoad(); + + // Set. + provider.IsInitialLoadEnabled = value; + Assert.Equal(value, provider.IsInitialLoadEnabled); + + // Set same. + provider.IsInitialLoadEnabled = value; + Assert.Equal(value, provider.IsInitialLoadEnabled); + + // Set different. + provider.IsInitialLoadEnabled = !value; + Assert.Equal(!value, provider.IsInitialLoadEnabled); + } + + [Fact] + public void IsInitialLoadEnabled_SetWithHandler_CallsPropertyChanged() + { + var provider = new SubDataSourceProvider(); + int callCount = 0; + PropertyChangedEventHandler handler = (sender, e) => + { + Assert.Same(provider, sender); + Assert.Equal("IsInitialLoadEnabled", e.PropertyName); + callCount++; + }; + ((INotifyPropertyChanged)provider).PropertyChanged += handler; + + // Set. + provider.IsInitialLoadEnabled = false; + Assert.False(provider.IsInitialLoadEnabled); + Assert.Equal(1, callCount); + + // Set same. + provider.IsInitialLoadEnabled = false; + Assert.False(provider.IsInitialLoadEnabled); + Assert.Equal(2, callCount); + + // Set different. + provider.IsInitialLoadEnabled = true; + Assert.True(provider.IsInitialLoadEnabled); + Assert.Equal(3, callCount); + + // Remove handler. + ((INotifyPropertyChanged)provider).PropertyChanged -= handler; + provider.IsInitialLoadEnabled = false; + Assert.False(provider.IsInitialLoadEnabled); + Assert.Equal(3, callCount); + } + + [Fact] + public void BeginInit_Invoke_EndCallsBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // Begin. + provider.BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + provider.EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void BeginInit_InvokeInitialLoadDisabled_EndCallsBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider + { + IsInitialLoadEnabled = false + }; + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // Begin. + provider.BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + provider.EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void BeginInit_InvokeInitialLoaded_EndCallsBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + provider.InitialLoad(); + Assert.Equal(1, callCount); + + // Begin. + provider.BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // End. + provider.EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(2, callCount); + } + + [Fact] + public void BeginInit_InvokeRefreshed_EndCallsBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + provider.Refresh(); + Assert.Equal(1, callCount); + + // Begin. + provider.BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // End. + provider.EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(2, callCount); + } + + [Fact] + public void BeginInit_InvokeRefreshDefer_EndCallsDoesNotCallBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider + { + IsInitialLoadEnabled = false + }; + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + IDisposable defer = provider.DeferRefresh(); + + // Begin. + provider.BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + provider.EndInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End defer. + defer.Dispose(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void BeginInit_InvokeMultipleTimes_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // Begin. + provider.BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Invoke again. + provider.BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + provider.EndInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End agian. + provider.EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void BeginInit_InvokeMultipleTimesEnded_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // End. + provider.EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Begin. + provider.BeginInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Invoke again. + provider.BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End agian. + provider.EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void ISupportInitializeBeginInit_Invoke_EndCallsBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // Begin. + ((ISupportInitialize)provider).BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + ((ISupportInitialize)provider).EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void ISupportInitializeBeginInit_InvokeInitialLoadDisabled_EndCallsBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider + { + IsInitialLoadEnabled = false + }; + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // Begin. + ((ISupportInitialize)provider).BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + ((ISupportInitialize)provider).EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void ISupportInitializeBeginInit_InvokeInitialLoaded_EndCallsBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + provider.InitialLoad(); + Assert.Equal(1, callCount); + + // Begin. + ((ISupportInitialize)provider).BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // End. + ((ISupportInitialize)provider).EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(2, callCount); + } + + [Fact] + public void ISupportInitializeBeginInit_InvokeRefreshed_EndCallsBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + provider.Refresh(); + Assert.Equal(1, callCount); + + // Begin. + ((ISupportInitialize)provider).BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // End. + ((ISupportInitialize)provider).EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(2, callCount); + } + + [Fact] + public void ISupportInitializeBeginInit_InvokeRefreshDefer_EndCallsDoesNotCallBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider + { + IsInitialLoadEnabled = false + }; + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + IDisposable defer = provider.DeferRefresh(); + + // Begin. + ((ISupportInitialize)provider).BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + ((ISupportInitialize)provider).EndInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End defer. + defer.Dispose(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void ISupportInitializeBeginInit_InvokeMultipleTimes_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // Begin. + ((ISupportInitialize)provider).BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Invoke again. + ((ISupportInitialize)provider).BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + ((ISupportInitialize)provider).EndInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End agian. + ((ISupportInitialize)provider).EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void ISupportInitializeBeginInit_InvokeMultipleTimesEnded_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // End. + ((ISupportInitialize)provider).EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Begin. + ((ISupportInitialize)provider).BeginInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Invoke again. + ((ISupportInitialize)provider).BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End agian. + ((ISupportInitialize)provider).EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void ISupportInitializeBeginInit_Invoke_CallsBeginInit() + { + var provider = new CustomInitDataSourceProvider(); + int callCount = 0; + provider.BeginInitAction += () => callCount++; + + // Invoke. + ((ISupportInitialize)provider).BeginInit(); + Assert.Equal(1, callCount); + + // Invoke again. + ((ISupportInitialize)provider).BeginInit(); + Assert.Equal(2, callCount); + } + + [Fact] + public void BeginQuery_Invoke_Nop() + { + var provider = new SubDataSourceProvider(); + + // Invoke. + provider.BeginQuery(); + Assert.False(provider.IsRefreshDeferred); + + // Invoke again. + provider.BeginQuery(); + Assert.False(provider.IsRefreshDeferred); + } + + [Fact] + public void DeferRefresh_Invoke_Success() + { + var provider = new SubDataSourceProvider(); + + IDisposable result1 = provider.DeferRefresh(); + Assert.NotNull(result1); + Assert.True(provider.IsRefreshDeferred); + + // Clear. + result1.Dispose(); + Assert.False(provider.IsRefreshDeferred); + + // Clear again. + result1.Dispose(); + Assert.False(provider.IsRefreshDeferred); + } + + [Fact] + public void DeferRefresh_InvokeBeganInitialize_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + IDisposable result1 = provider.DeferRefresh(); + Assert.NotNull(result1); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Begin. + provider.BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Clear. + result1.Dispose(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Clear again. + result1.Dispose(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End init. + provider.EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void DeferRefresh_InvokeEndedInitialize_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + IDisposable result1 = provider.DeferRefresh(); + Assert.NotNull(result1); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Begin. + provider.BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + provider.EndInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Clear. + result1.Dispose(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // Clear again. + result1.Dispose(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void DeferRefresh_InvokeMultipleTimes_Success() + { + var provider = new SubDataSourceProvider(); + + // Invoke. + IDisposable result1 = provider.DeferRefresh(); + Assert.NotNull(result1); + Assert.True(provider.IsRefreshDeferred); + + // Invoke again. + IDisposable result2 = provider.DeferRefresh(); + Assert.NotNull(result2); + Assert.NotSame(result1, result2); + Assert.True(provider.IsRefreshDeferred); + + // Clear first. + result1.Dispose(); + Assert.True(provider.IsRefreshDeferred); + + // Clear first again. + result1.Dispose(); + Assert.True(provider.IsRefreshDeferred); + + // Clear second. + result2.Dispose(); + Assert.False(provider.IsRefreshDeferred); + } + + [Fact] + public void EndInit_Invoke_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // Begin. + provider.BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + provider.EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void EndInit_InvokeMultipleTimes_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // Begin. + provider.BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + provider.EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // End again. + provider.EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // Begin. + provider.BeginInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // Begin again. + provider.BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void EndInit_InvokeNotBegan_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // End. + provider.EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Begin. + provider.BeginInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + provider.EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + } + + [Fact] + public void EndInit_InvokeInitialLoaded_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // Begin. + provider.BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Load. + provider.InitialLoad(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // End. + provider.EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(2, callCount); + } + + [Fact] + public void EndInit_InvokeRefreshDeferred_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // Begin. + provider.BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Defer. + IDisposable defer = provider.DeferRefresh(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + provider.EndInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End again. + provider.EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void ISupportInitializeEndInit_Invoke_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // Begin. + ((ISupportInitialize)provider).BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + ((ISupportInitialize)provider).EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void ISupportInitializeEndInit_InvokeMultipleTimes_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // Begin. + ((ISupportInitialize)provider).BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + ((ISupportInitialize)provider).EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // End again. + ((ISupportInitialize)provider).EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // Begin. + ((ISupportInitialize)provider).BeginInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // Begin again. + ((ISupportInitialize)provider).BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void ISupportInitializeEndInit_InvokeNotBegan_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // End. + ((ISupportInitialize)provider).EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Begin. + ((ISupportInitialize)provider).BeginInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + ((ISupportInitialize)provider).EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + } + + [Fact] + public void ISupportInitializeEndInit_InvokeInitialLoaded_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // Begin. + ((ISupportInitialize)provider).BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Load. + provider.InitialLoad(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // End. + ((ISupportInitialize)provider).EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(2, callCount); + } + + [Fact] + public void ISupportInitializeEndInit_InvokeRefreshDeferred_Success() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction += () => callCount++; + + // Begin. + ((ISupportInitialize)provider).BeginInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // Defer. + IDisposable defer = provider.DeferRefresh(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End. + ((ISupportInitialize)provider).EndInit(); + Assert.True(provider.IsRefreshDeferred); + Assert.Equal(0, callCount); + + // End again. + ((ISupportInitialize)provider).EndInit(); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + } + + [Fact] + public void ISupportInitializeEndInit_Invoke_CallsEndInit() + { + var provider = new CustomInitDataSourceProvider(); + int callCount = 0; + provider.EndInitAction += () => callCount++; + + // Invoke. + ((ISupportInitialize)provider).EndInit(); + Assert.Equal(1, callCount); + + // Invoke again. + ((ISupportInitialize)provider).EndInit(); + Assert.Equal(2, callCount); + } + + [Fact] + public void InitialLoad_Invoke_CallsBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction = () => callCount++; + + // Invoke. + provider.InitialLoad(); + Assert.Equal(1, callCount); + Assert.False(provider.IsRefreshDeferred); + + // Invoke again. + provider.InitialLoad(); + Assert.Equal(1, callCount); + Assert.False(provider.IsRefreshDeferred); + } + + [Fact] + public void InitialLoad_InvokeInitialLoadNotEnabled_DoesNotCallBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider + { + IsInitialLoadEnabled = false + }; + int callCount = 0; + provider.BeginQueryAction = () => callCount++; + + // Invoke. + provider.InitialLoad(); + Assert.Equal(0, callCount); + Assert.True(provider.IsRefreshDeferred); + + // Invoke again. + provider.InitialLoad(); + Assert.Equal(0, callCount); + Assert.True(provider.IsRefreshDeferred); + } + + [Fact] + public void InitialLoad_InvokeBeganInitialize_CallsBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction = () => callCount++; + + provider.BeginInit(); + Assert.Equal(0, callCount); + Assert.True(provider.IsRefreshDeferred); + + // Invoke. + provider.InitialLoad(); + Assert.Equal(1, callCount); + Assert.True(provider.IsRefreshDeferred); + + // Invoke again. + provider.InitialLoad(); + Assert.Equal(1, callCount); + Assert.True(provider.IsRefreshDeferred); + } + + [Fact] + public void InitialLoad_InvokeEndedInitialize_DoesNotCallBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction = () => callCount++; + + provider.BeginInit(); + Assert.Equal(0, callCount); + Assert.True(provider.IsRefreshDeferred); + + provider.EndInit(); + Assert.Equal(1, callCount); + Assert.False(provider.IsRefreshDeferred); + + // Invoke. + provider.InitialLoad(); + Assert.Equal(1, callCount); + Assert.False(provider.IsRefreshDeferred); + + // Invoke again. + provider.InitialLoad(); + Assert.Equal(1, callCount); + Assert.False(provider.IsRefreshDeferred); + } + + [Fact] + public void InitialLoad_InvokeRefreshed_DoesNotCallBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction = () => callCount++; + + provider.Refresh(); + Assert.Equal(1, callCount); + Assert.False(provider.IsRefreshDeferred); + + // Invoke. + provider.InitialLoad(); + Assert.Equal(1, callCount); + Assert.False(provider.IsRefreshDeferred); + + // Invoke again. + provider.InitialLoad(); + Assert.Equal(1, callCount); + Assert.False(provider.IsRefreshDeferred); + } + + [Fact] + public void Refresh_Invoke_CallsBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction = () => callCount++; + + // Refresh. + provider.Refresh(); + Assert.Equal(1, callCount); + Assert.False(provider.IsRefreshDeferred); + + // Refresh again. + provider.Refresh(); + Assert.Equal(2, callCount); + Assert.False(provider.IsRefreshDeferred); + } + + [Fact] + public void Refresh_InvokeInitialLoaded_CallsBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction = () => callCount++; + + provider.InitialLoad(); + Assert.Equal(1, callCount); + Assert.False(provider.IsRefreshDeferred); + + // Refresh. + provider.Refresh(); + Assert.Equal(2, callCount); + Assert.False(provider.IsRefreshDeferred); + + // Refresh again. + provider.Refresh(); + Assert.Equal(3, callCount); + Assert.False(provider.IsRefreshDeferred); + } + + [Fact] + public void Refresh_InvokeRefreshDeferred_CallsBeginQuery() + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction = () => callCount++; + + provider.DeferRefresh(); + Assert.Equal(0, callCount); + Assert.True(provider.IsRefreshDeferred); + + // Refresh. + provider.Refresh(); + Assert.Equal(1, callCount); + Assert.True(provider.IsRefreshDeferred); + + // Refresh again. + provider.Refresh(); + Assert.Equal(2, callCount); + Assert.True(provider.IsRefreshDeferred); + } + + public static IEnumerable OnPropertyChanged_TestData() + { + yield return new object?[] { new PropertyChangedEventArgs("Name") }; + yield return new object?[] { null }; + } + + [Theory] + [MemberData(nameof(OnPropertyChanged_TestData))] + public void OnPropertyChanged_Invoke_CallsPropertyChangedEvent(PropertyChangedEventArgs eventArgs) + { + var provider = new SubDataSourceProvider(); + int callCount = 0; + PropertyChangedEventHandler handler = (sender, e) => + { + Assert.Same(provider, sender); + Assert.Same(eventArgs, e); + callCount++; + }; + + // Call with handler. + ((INotifyPropertyChanged)provider).PropertyChanged += handler; + provider.OnPropertyChanged(eventArgs); + Assert.Equal(1, callCount); + + // Remove handler. + ((INotifyPropertyChanged)provider).PropertyChanged -= handler; + provider.OnPropertyChanged(eventArgs); + Assert.Equal(1, callCount); + } + + [Fact] + public void OnQueryFinished_InvokeSimple_Success() + { + var provider = new SubDataSourceProvider(); + var newData = new object(); + + // Invoke. + provider.OnQueryFinished(newData); + Assert.Same(newData, provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Null(provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + + // Invoke again. + provider.OnQueryFinished(newData); + Assert.Same(newData, provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Null(provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + + // Invoke null. + provider.OnQueryFinished(null!); + Assert.Null(provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Null(provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + } + + [Theory] + [InlineData(1)] + [InlineData(null)] + public void OnQueryFinished_InvokeSimpleInitialLoaded_DoesNotResetInitialLoad(object? newData) + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction = () => callCount++; + + provider.InitialLoad(); + Assert.Equal(1, callCount); + + // Invoke. + provider.OnQueryFinished(newData!); + Assert.Equal(newData, provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Null(provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // Load again. + provider.InitialLoad(); + Assert.Equal(1, callCount); + } + + [Theory] + [InlineData(1)] + [InlineData(null)] + public void OnQueryFinished_InvokeSimpleWithError_Success(object? newData) + { + var provider = new SubDataSourceProvider(); + provider.OnQueryFinished(new object(), new Exception(), e => e, new object()); + + // Invoke. + provider.OnQueryFinished(newData!); + Assert.Equal(newData, provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Null(provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + } + + [Fact] + public void OnQueryFinished_InvokeSimpleWithPropertyChangedHandler_CallsPropertyChanged() + { + var provider = new SubDataSourceProvider(); + int callCount = 0; + object? newData = null; + PropertyChangedEventHandler handler = (sender, e) => + { + Assert.Same(provider, sender); + Assert.Equal("Data", e.PropertyName); + Assert.Same(newData, provider.Data); + callCount++; + }; + ((INotifyPropertyChanged)provider).PropertyChanged += handler; + + // Call. + newData = new object(); + provider.OnQueryFinished(newData); + Assert.Equal(1, callCount); + + // Call again. + provider.OnQueryFinished(newData); + Assert.Equal(2, callCount); + + // Call different. + newData = null; + provider.OnQueryFinished(null!); + Assert.Equal(3, callCount); + + // Remove handler. + ((INotifyPropertyChanged)provider).PropertyChanged -= handler; + newData = new object(); + provider.OnQueryFinished(newData); + Assert.Equal(3, callCount); + } + + [Fact] + public void OnQueryFinished_InvokeSimpleWithDataChangedHandler_CallsDataChanged() + { + var provider = new SubDataSourceProvider(); + int callCount = 0; + object? newData = null; + EventHandler handler = (sender, e) => + { + Assert.Same(provider, sender); + Assert.Same(EventArgs.Empty, e); + Assert.Same(newData, provider.Data); + callCount++; + }; + provider.DataChanged += handler; + + // Call. + newData = new object(); + provider.OnQueryFinished(newData); + Assert.Equal(1, callCount); + + // Call again. + provider.OnQueryFinished(newData); + Assert.Equal(2, callCount); + + // Call different. + newData = null; + provider.OnQueryFinished(newData!); + Assert.Equal(3, callCount); + + // Remove handler. + provider.DataChanged -= handler; + newData = new object(); + provider.OnQueryFinished(newData); + Assert.Equal(3, callCount); + } + + [Fact] + public void OnQueryFinished_InvokeSimpleWithPropertyChangedAndDataChangedHandler_CallsPropertyChangedAndDataChanged() + { + var provider = new SubDataSourceProvider(); + int propertyChangedCallCount = 0; + int dataChangedCallCount = 0; + object? newData = null; + PropertyChangedEventHandler propertyChangedHandler = (sender, e) => + { + Assert.Equal(dataChangedCallCount, propertyChangedCallCount); + Assert.Same(provider, sender); + Assert.Equal("Data", e.PropertyName); + propertyChangedCallCount++; + }; + ((INotifyPropertyChanged)provider).PropertyChanged += propertyChangedHandler; + EventHandler dataChangedHandler = (sender, e) => + { + Assert.True(propertyChangedCallCount > dataChangedCallCount); + Assert.Same(provider, sender); + Assert.Same(EventArgs.Empty, e); + dataChangedCallCount++; + }; + provider.DataChanged += dataChangedHandler; + + // Call. + newData = new object(); + provider.OnQueryFinished(newData); + Assert.Equal(1, propertyChangedCallCount); + Assert.Equal(1, dataChangedCallCount); + + // Call again. + provider.OnQueryFinished(newData); + Assert.Equal(2, propertyChangedCallCount); + Assert.Equal(2, dataChangedCallCount); + + // Call different. + newData = null; + provider.OnQueryFinished(newData!); + Assert.Equal(3, propertyChangedCallCount); + Assert.Equal(3, dataChangedCallCount); + + // Remove handler. + ((INotifyPropertyChanged)provider).PropertyChanged -= propertyChangedHandler; + provider.DataChanged -= dataChangedHandler; + newData = new object(); + provider.OnQueryFinished(newData); + Assert.Equal(3, propertyChangedCallCount); + Assert.Equal(3, dataChangedCallCount); + } + + [Fact] + public void OnQueryFinished_InvokeSimpleOnDifferentThread_Success() + { + var provider = new SubDataSourceProvider(); + + bool? success = false; + var thread = new Thread(() => + { + try + { + provider.OnQueryFinished(new object()); + success = true; + } + catch + { + success = false; + } + }); + thread.Start(); + thread.Join(); + Assert.True(success); + } + + [Theory] + [InlineData(1)] + [InlineData(null)] + public void OnQueryFinished_InvokeSimple_CallsComplex(object? newData) + { + var provider = new CustomOnQueryFinishedDataSourceProvider(); + + int callCount = 0; + provider.OnQueryFinishedAction = (data, error, completionWork, callbackArguments) => + { + Assert.Equal(newData, data); + Assert.Null(error); + Assert.Null(completionWork); + Assert.Null(callbackArguments); + callCount++; + }; + + // Call. + provider.OnQueryFinished(newData!); + Assert.Equal(1, callCount); + + // Call again. + provider.OnQueryFinished(newData!); + Assert.Equal(2, callCount); + } + + [Theory] + [InlineData(1)] + [InlineData(null)] + public void OnQueryFinished_InvokeComplex_Success(object? newData) + { + var provider = new SubDataSourceProvider(); + + // Invoke. + provider.OnQueryFinished(newData!, null!, null!, null!); + Assert.Equal(newData, provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Null(provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + + // Invoke again. + provider.OnQueryFinished(newData!, null!, null!, null!); + Assert.Equal(newData, provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Null(provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + } + + [Theory] + [InlineData(1)] + [InlineData(null)] + public void OnQueryFinished_InvokeComplexWithError_Success(object? newData) + { + var provider = new SubDataSourceProvider(); + var error = new Exception(); + + // Invoke. + provider.OnQueryFinished(newData!, error, null!, null!); + Assert.Null(provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Same(error, provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + + // Invoke again. + provider.OnQueryFinished(newData!, error, null!, null!); + Assert.Null(provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Same(error, provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + + // Clear error. + provider.OnQueryFinished(newData!, null!, null!, null!); + Assert.Equal(newData, provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Null(provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + } + + [Theory] + [InlineData(1, 2)] + [InlineData(1, null)] + [InlineData(null, 2)] + [InlineData(null, null)] + public void OnQueryFinished_InvokeComplexWithCallback_CallsCallback(object? newData, object? callbackArg) + { + var provider = new SubDataSourceProvider(); + int callbackCallCount = 0; + DispatcherOperationCallback callback = arg => + { + Assert.Same(callbackArg, arg); + Assert.Equal(newData, provider.Data); + callbackCallCount++; + return null!; + }; + + // Invoke. + provider.OnQueryFinished(newData!, null!, callback, callbackArg!); + Assert.Equal(1, callbackCallCount); + Assert.Equal(newData, provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Null(provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + + // Invoke again. + provider.OnQueryFinished(newData!, null!, callback, callbackArg!); + Assert.Equal(2, callbackCallCount); + Assert.Equal(newData, provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Null(provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + } + + [Theory] + [InlineData(1)] + [InlineData(null)] + public void OnQueryFinished_InvokeComplexInitialLoadedWithError_ResetsInitialLoad(object? newData) + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction = () => callCount++; + var error = new Exception(); + + provider.InitialLoad(); + Assert.Equal(1, callCount); + + // Invoke. + provider.OnQueryFinished(newData!, error, null!, null!); + Assert.Null(provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Same(error, provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // Load again. + provider.InitialLoad(); + Assert.Equal(2, callCount); + + // Invoke again. + provider.OnQueryFinished(newData!, error, null!, null!); + Assert.Null(provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Same(error, provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(2, callCount); + + // Load again. + provider.InitialLoad(); + Assert.Equal(3, callCount); + } + + [Theory] + [InlineData(1)] + [InlineData(null)] + public void OnQueryFinished_InvokeComplexInitialLoadedWithoutError_DoesNotResetInitialLoad(object? newData) + { + var provider = new CustomBeginQueryDataSourceProvider(); + int callCount = 0; + provider.BeginQueryAction = () => callCount++; + + provider.InitialLoad(); + Assert.Equal(1, callCount); + + // Invoke. + provider.OnQueryFinished(newData!, null!, null!, null!); + Assert.Equal(newData, provider.Data); + Assert.NotNull(provider.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, provider.Dispatcher); + Assert.Null(provider.Error); + Assert.True(provider.IsInitialLoadEnabled); + Assert.False(provider.IsRefreshDeferred); + Assert.Equal(1, callCount); + + // Load again. + provider.InitialLoad(); + Assert.Equal(1, callCount); + } + + [Fact] + public void OnQueryFinished_InvokeComplexWithPropertyChangedHandler_CallsPropertyChanged() + { + var provider = new SubDataSourceProvider(); + int callCount = 0; + int callbackCallCount = 0; + object? newData = null; + var callbackArguments = new object(); + PropertyChangedEventHandler handler = (sender, e) => + { + Assert.True(callbackCallCount > callCount); + Assert.Same(provider, sender); + Assert.Equal("Data", e.PropertyName); + Assert.Same(newData, provider.Data); + callCount++; + }; + ((INotifyPropertyChanged)provider).PropertyChanged += handler; + DispatcherOperationCallback callback = (arg) => + { + Assert.Equal(callbackCallCount, callCount); + Assert.Same(callbackArguments, arg); + Assert.Same(newData, provider.Data); + callbackCallCount++; + return null; + }; + + // Call. + newData = new object(); + provider.OnQueryFinished(newData, null!, callback, callbackArguments); + Assert.Equal(1, callCount); + Assert.Equal(1, callbackCallCount); + + // Call again. + provider.OnQueryFinished(newData, null!, callback, callbackArguments); + Assert.Equal(2, callCount); + Assert.Equal(2, callbackCallCount); + + // Call different. + newData = null; + provider.OnQueryFinished(null!, null!, callback, callbackArguments); + Assert.Equal(3, callCount); + Assert.Equal(3, callbackCallCount); + + // Remove handler. + ((INotifyPropertyChanged)provider).PropertyChanged -= handler; + newData = new object(); + provider.OnQueryFinished(newData, null!, callback, callbackArguments); + Assert.Equal(3, callCount); + Assert.Equal(4, callbackCallCount); + } + + [Fact] + public void OnQueryFinished_InvokeComplexWithErrorWithPropertyChangedHandler_CallsPropertyChanged() + { + var provider = new SubDataSourceProvider(); + int callCount = 0; + object? newData = null; + var callbackArguments = new object(); + var propertyNames = new List(); + PropertyChangedEventHandler handler = (sender, e) => + { + Assert.Same(provider, sender); + propertyNames.Add(e.PropertyName!); + callCount++; + }; + ((INotifyPropertyChanged)provider).PropertyChanged += handler; + + var error1 = new Exception(); + var error2 = new Exception(); + + // Call. + newData = new object(); + provider.OnQueryFinished(newData, error1, null!, null!); + Assert.Equal(2, callCount); + Assert.Equal(new[] { "Data", "Error" }, propertyNames); + Assert.Same(error1, provider.Error); + + // Call again. + provider.OnQueryFinished(newData, error1, null!, null!); + Assert.Equal(3, callCount); + Assert.Equal(new[] { "Data", "Error", "Data" }, propertyNames); + Assert.Same(error1, provider.Error); + + // Call different. + newData = null; + provider.OnQueryFinished(null!, error2, null!, null!); + Assert.Equal(5, callCount); + Assert.Equal(new[] { "Data", "Error", "Data", "Data", "Error" }, propertyNames); + Assert.Same(error2, provider.Error); + + // Call different. + provider.OnQueryFinished(null!, null!, null!, null!); + Assert.Equal(7, callCount); + Assert.Equal(new[] { "Data", "Error", "Data", "Data", "Error", "Data", "Error" }, propertyNames); + Assert.Null(provider.Error); + + // Remove handler. + ((INotifyPropertyChanged)provider).PropertyChanged -= handler; + newData = new object(); + provider.OnQueryFinished(newData, error1, null!, null!); + Assert.Equal(7, callCount); + Assert.Equal(new[] { "Data", "Error", "Data", "Data", "Error", "Data", "Error" }, propertyNames); + Assert.Same(error1, provider.Error); + } + + [Fact] + public void OnQueryFinished_InvokeComplexWithDataChangedHandler_CallsDataChanged() + { + var provider = new SubDataSourceProvider(); + int callCount = 0; + int callbackCallCount = 0; + var callbackArguments = new object(); + object? newData = null; + EventHandler handler = (sender, e) => + { + Assert.Same(provider, sender); + Assert.Same(EventArgs.Empty, e); + Assert.Same(newData, provider.Data); + callCount++; + }; + provider.DataChanged += handler; + DispatcherOperationCallback callback = (arg) => + { + Assert.Equal(callbackCallCount, callCount); + Assert.Same(callbackArguments, arg); + Assert.Same(newData, provider.Data); + callbackCallCount++; + return null; + }; + + // Call. + newData = new object(); + provider.OnQueryFinished(newData, null!, callback, callbackArguments); + Assert.Equal(1, callCount); + Assert.Equal(1, callbackCallCount); + + // Call again. + provider.OnQueryFinished(newData, null!, callback, callbackArguments); + Assert.Equal(2, callCount); + Assert.Equal(2, callbackCallCount); + + // Call different. + newData = null; + provider.OnQueryFinished(newData!, null!, callback, callbackArguments); + Assert.Equal(3, callCount); + Assert.Equal(3, callbackCallCount); + + // Remove handler. + provider.DataChanged -= handler; + newData = new object(); + provider.OnQueryFinished(newData, null!, callback, callbackArguments); + Assert.Equal(3, callCount); + Assert.Equal(4, callbackCallCount); + } + + [Fact] + public void OnQueryFinished_InvokeComplexWithPropertyChangedAndDataChangedHandler_CallsPropertyChangedAndDataChanged() + { + var provider = new SubDataSourceProvider(); + int propertyChangedCallCount = 0; + int dataChangedCallCount = 0; + int callbackCallCount = 0; + var callbackArguments = new object(); + object? newData = null; + PropertyChangedEventHandler propertyChangedHandler = (sender, e) => + { + Assert.True(callbackCallCount > propertyChangedCallCount); + Assert.Equal(dataChangedCallCount, propertyChangedCallCount); + Assert.Same(provider, sender); + Assert.Equal("Data", e.PropertyName); + propertyChangedCallCount++; + }; + ((INotifyPropertyChanged)provider).PropertyChanged += propertyChangedHandler; + EventHandler dataChangedHandler = (sender, e) => + { + Assert.True(propertyChangedCallCount > dataChangedCallCount); + Assert.True(callbackCallCount > dataChangedCallCount); + Assert.Same(provider, sender); + Assert.Same(EventArgs.Empty, e); + dataChangedCallCount++; + }; + provider.DataChanged += dataChangedHandler; + DispatcherOperationCallback callback = (arg) => + { + Assert.Equal(callbackCallCount, propertyChangedCallCount); + Assert.Equal(callbackCallCount, dataChangedCallCount); + Assert.Same(callbackArguments, arg); + Assert.Same(newData, provider.Data); + callbackCallCount++; + return null; + }; + + // Call. + newData = new object(); + provider.OnQueryFinished(newData, null!, callback, callbackArguments); + Assert.Equal(1, propertyChangedCallCount); + Assert.Equal(1, dataChangedCallCount); + Assert.Equal(1, callbackCallCount); + + // Call again. + provider.OnQueryFinished(newData, null!, callback, callbackArguments); + Assert.Equal(2, propertyChangedCallCount); + Assert.Equal(2, dataChangedCallCount); + Assert.Equal(2, callbackCallCount); + + // Call different. + newData = null; + provider.OnQueryFinished(newData!, null!, callback, callbackArguments); + Assert.Equal(3, propertyChangedCallCount); + Assert.Equal(3, dataChangedCallCount); + Assert.Equal(3, callbackCallCount); + + // Remove handler. + ((INotifyPropertyChanged)provider).PropertyChanged -= propertyChangedHandler; + provider.DataChanged -= dataChangedHandler; + newData = new object(); + provider.OnQueryFinished(newData, null!, callback, callbackArguments); + Assert.Equal(3, propertyChangedCallCount); + Assert.Equal(3, dataChangedCallCount); + Assert.Equal(4, callbackCallCount); + } + + [Fact] + public void OnQueryFinished_InvokeComplexOnDifferentThread_Success() + { + var provider = new SubDataSourceProvider(); + + bool? success = false; + var thread = new Thread(() => + { + try + { + provider.OnQueryFinished(new object(), new Exception(), e => e, new object()); + success = true; + } + catch + { + success = false; + } + }); + thread.Start(); + thread.Join(); + Assert.True(success); + } + + // TODO: this causes a crash. +#if false + [Fact] + public void OnQueryFinished_NullDispatcher_ThrowsNullReferenceException() + { + var provider = new SubDataSourceProvider(); + provider.Dispatcher = null; + Assert.Throws(() => provider.OnQueryFinished(new object())); + Assert.Throws(() => provider.OnQueryFinished(new object(), new Exception(), e => e, new object())); + } +#endif + + private class CustomBeginQueryDataSourceProvider : DataSourceProvider + { + public new Dispatcher? Dispatcher + { + get => base.Dispatcher; + set => base.Dispatcher = value; + } + + public new bool IsRefreshDeferred => base.IsRefreshDeferred; + + public new void BeginInit() => base.BeginInit(); + + public Action? BeginQueryAction { get; set; } + + protected override void BeginQuery() + { + if (BeginQueryAction is null) + { + throw new NotImplementedException(); + } + + BeginQueryAction(); + } + + public new void EndInit() => base.EndInit(); + + public new void OnQueryFinished(object newData) => base.OnQueryFinished(newData); + + public new void OnQueryFinished(object newData, Exception error, DispatcherOperationCallback completionWork, object callbackArguments) => base.OnQueryFinished(newData, error, completionWork, callbackArguments); + } + + private class CustomInitDataSourceProvider : DataSourceProvider + { + public Action? BeginInitAction { get; set; } + + protected override void BeginInit() + { + if (BeginInitAction is null) + { + throw new NotImplementedException(); + } + + BeginInitAction(); + } + + public Action? EndInitAction { get; set; } + + protected override void EndInit() + { + if (EndInitAction is null) + { + throw new NotImplementedException(); + } + + EndInitAction(); + } + } + + private class CustomOnQueryFinishedDataSourceProvider : DataSourceProvider + { + public new void OnQueryFinished(object newData) => base.OnQueryFinished(newData); + + public Action? OnQueryFinishedAction { get; set; } + + protected override void OnQueryFinished(object newData, Exception error, DispatcherOperationCallback completionWork, object callbackArguments) + { + if (OnQueryFinishedAction is null) + { + throw new NotImplementedException(); + } + + OnQueryFinishedAction(newData, error, completionWork, callbackArguments); + } + } + + private class SubDataSourceProvider : DataSourceProvider + { + public SubDataSourceProvider() : base() + { + } + + public new event PropertyChangedEventHandler PropertyChanged + { + add => base.PropertyChanged += value; + remove => base.PropertyChanged -= value; + } + + public new Dispatcher? Dispatcher + { + get => base.Dispatcher; + set => base.Dispatcher = value; + } + + public new bool IsRefreshDeferred => base.IsRefreshDeferred; + + public new void BeginInit() => base.BeginInit(); + + public new void BeginQuery() => base.BeginQuery(); + + public new void EndInit() => base.EndInit(); + + public new void OnPropertyChanged(PropertyChangedEventArgs e) => base.OnPropertyChanged(e); + + public new void OnQueryFinished(object newData) => base.OnQueryFinished(newData); + + public new void OnQueryFinished(object newData, Exception error, DispatcherOperationCallback completionWork, object callbackArguments) => base.OnQueryFinished(newData, error, completionWork, callbackArguments); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DependencyObjectTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DependencyObjectTests.cs new file mode 100644 index 00000000000..c22e2ae48e5 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DependencyObjectTests.cs @@ -0,0 +1,3018 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.ComponentModel; +using System.Reflection; +using System.Windows.Threading; + +namespace System.Windows.Tests; + +public class DependencyObjectTests +{ + [Fact] + public void Ctor_Default() + { + var obj = new DependencyObject(); + Assert.NotNull(obj.DependencyObjectType); + Assert.Same(obj.DependencyObjectType, obj.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(DependencyObject)), obj.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, obj.Dispatcher); + Assert.False(obj.IsSealed); + } + + [Fact] + public void DependencyObjectType_Get_ReturnsExpected() + { + var obj = new DependencyObject(); + Assert.NotNull(obj.DependencyObjectType); + Assert.Same(obj.DependencyObjectType, obj.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(DependencyObject)), obj.DependencyObjectType); + } + + [Fact] + public void DependencyObjectType_GetOnDifferentThread_ReturnsExpected() + { + var obj = new DependencyObject(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.NotNull(obj.DependencyObjectType); + Assert.Same(obj.DependencyObjectType, obj.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(DependencyObject)), obj.DependencyObjectType); + }); + } + + [Fact] + public void Dispatcher_Get_ReturnsExpected() + { + var obj = new DependencyObject(); + Assert.Same(Dispatcher.CurrentDispatcher, obj.Dispatcher); + } + + [Fact] + public void Dispatcher_GetOnDifferentThread_ReturnsExpected() + { + var obj = new DependencyObject(); + Dispatcher expected = Dispatcher.CurrentDispatcher; + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Same(expected, obj.Dispatcher); + }); + } + + [Fact] + public void IsSealed_Get_ReturnsFalse() + { + var obj = new DependencyObject(); + Assert.False(obj.IsSealed); + } + + [Fact] + public void IsSealed_GetOnDifferentThread_ReturnsFalse() + { + var obj = new DependencyObject(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.False(obj.IsSealed); + }); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyValueType_GetValueReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + var obj = new DependencyObject(); + + // Clear default. + obj.ClearValue(property); + Assert.False((bool)obj.GetValue(property)); + + // Set custom. + obj.SetValue(property, true); + Assert.True((bool)obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(property); + Assert.False((bool)obj.GetValue(property)); + + // Clear again. + obj.ClearValue(property); + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyNullable_GetValueReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool?), typeof(DependencyObject)); + var obj = new DependencyObject(); + + // Clear default. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(property, true); + Assert.True((bool)obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + + // Clear again. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyReferenceType_GetValueReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + + // Clear default. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + + // Clear again. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyObject_GetValueReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(DependencyObject)); + var obj = new DependencyObject(); + + // Clear default. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + + // Clear again. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyCustomDefaultValue_GetValueReturnsExpected() + { + var obj = new DependencyObject(); + var typeMetadata = new PropertyMetadata("default"); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + + // Clear default. + obj.ClearValue(property); + Assert.Equal("default", obj.GetValue(property)); + + // Set custom. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(property); + Assert.Equal("default", obj.GetValue(property)); + + // Clear again. + obj.ClearValue(property); + Assert.Equal("default", obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyCustomPropertyChangedCallback_Success() + { + var obj = new DependencyObject(); + + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata(null, (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Clear default. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + + // Clear custom. + expectedNewValue = null; + expectedOldValue = "value"; + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + + // Clear again. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyCustomCoerceValueCallback_Success() + { + var obj = new DependencyObject(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + }); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Clear default. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, coerceValueCallCount); + + // Set custom. + expectedNewValue = "value"; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Clear custom. + expectedNewValue = null; + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Clear again. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyCustomPropertyChangedAndCoerceValueCallback_Success() + { + var obj = new DependencyObject(); + + int coerceValueCallCount = 0; + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + }); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Clear default. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + Assert.Equal(0, coerceValueCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + + // Clear custom. + expectedNewValue = null; + expectedOldValue = "value"; + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + + // Clear again. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyCustomOnPropertyChanged_CallsOnPropertyChanged() + { + var obj = new CustomDependencyObject(); + + int onPropertyChangedCallCount = 0; + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + obj.OnPropertyChangedAction = (e) => + { + Assert.True(changedCallCount > onPropertyChangedCallCount); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + onPropertyChangedCallCount++; + }; + var typeMetadata = new PropertyMetadata(null, (d, e) => + { + Assert.Equal(changedCallCount, onPropertyChangedCallCount); + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Clear default. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + Assert.Equal(0, onPropertyChangedCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(1, onPropertyChangedCallCount); + + // Clear custom. + expectedNewValue = null; + expectedOldValue = "value"; + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(2, onPropertyChangedCallCount); + + // Clear again. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(2, onPropertyChangedCallCount); + } + + [Fact] + public void ClearValue_DependencyPropertyNullDp_ThrowsArgumentNullException() + { + var obj = new DependencyObject(); + Assert.Throws("dp", () => obj.ClearValue((DependencyProperty)null!)); + } + + [Fact] + public void ClearValue_DependencyPropertyReadOnly_ThrowsInvalidOperationException() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + Assert.Throws(() => obj.ClearValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyOnDifferentThread_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + var obj = new DependencyObject(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => obj.ClearValue(property)); + }); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyValueType_GetValueReturnsExpected() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + // Clear default. + obj.ClearValue(key); + Assert.False((bool)obj.GetValue(property)); + + // Set custom. + obj.SetValue(key, true); + Assert.True((bool)obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(key); + Assert.False((bool)obj.GetValue(property)); + + // Clear again. + obj.ClearValue(key); + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyNullable_GetValueReturnsExpected() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool?), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + // Clear default. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(key, true); + Assert.True((bool)obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + + // Clear again. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyReferenceType_GetValueReturnsExpected() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + // Clear default. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + + // Clear again. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyObject_GetValueReturnsExpected() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + // Clear default. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + + // Clear again. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyCustomDefaultValue_GetValueReturnsExpected() + { + var obj = new DependencyObject(); + var typeMetadata = new PropertyMetadata("default"); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + DependencyProperty property = key.DependencyProperty; + + // Clear default. + obj.ClearValue(key); + Assert.Equal("default", obj.GetValue(property)); + + // Set custom. + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(key); + Assert.Equal("default", obj.GetValue(property)); + + // Clear again. + obj.ClearValue(key); + Assert.Equal("default", obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyCustomPropertyChangedCallback_Success() + { + var obj = new DependencyObject(); + + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata(null, (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + DependencyProperty property = key.DependencyProperty; + expectedProperty = property; + + // Clear default. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + + // Clear custom. + expectedNewValue = null; + expectedOldValue = "value"; + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + + // Clear again. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyCustomCoerceValueCallback_Success() + { + var obj = new DependencyObject(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + }); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + DependencyProperty property = key.DependencyProperty; + expectedProperty = property; + + // Clear default. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, coerceValueCallCount); + + // Set custom. + expectedNewValue = "value"; + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Clear custom. + expectedNewValue = null; + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Clear again. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyCustomPropertyChangedAndCoerceValueCallback_Success() + { + var obj = new DependencyObject(); + + int coerceValueCallCount = 0; + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + }); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + DependencyProperty property = key.DependencyProperty; + expectedProperty = property; + + // Clear default. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + Assert.Equal(0, coerceValueCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + + // Clear custom. + expectedNewValue = null; + expectedOldValue = "value"; + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + + // Clear again. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyCustomOnPropertyChanged_CallsOnPropertyChanged() + { + var obj = new CustomDependencyObject(); + + int onPropertyChangedCallCount = 0; + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + obj.OnPropertyChangedAction = (e) => + { + Assert.True(changedCallCount > onPropertyChangedCallCount); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + onPropertyChangedCallCount++; + }; + var typeMetadata = new PropertyMetadata(null, (d, e) => + { + Assert.Equal(changedCallCount, onPropertyChangedCallCount); + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + DependencyProperty property = key.DependencyProperty; + expectedProperty = property; + + // Clear default. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + Assert.Equal(0, onPropertyChangedCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(1, onPropertyChangedCallCount); + + // Clear custom. + expectedNewValue = null; + expectedOldValue = "value"; + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(2, onPropertyChangedCallCount); + + // Clear again. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(2, onPropertyChangedCallCount); + } + + [Fact] + public void ClearValue_DependencyPropertyNullKey_ThrowsArgumentNullException() + { + var obj = new DependencyObject(); + Assert.Throws("key", () => obj.ClearValue((DependencyPropertyKey)null!)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyOnDifferentThread_ThrowsInvalidOperationException() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => obj.ClearValue(key)); + }); + } + + [Fact] + public void CoerceValue_InvokeValueType_GetValueReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + var obj = new DependencyObject(); + + // Coerce default. + obj.CoerceValue(property); + Assert.False((bool)obj.GetValue(property)); + + // Set true. + obj.SetValue(property, true); + Assert.True((bool)obj.GetValue(property)); + + // Coerce true. + obj.CoerceValue(property); + Assert.True((bool)obj.GetValue(property)); + + // Coerce again. + obj.CoerceValue(property); + Assert.True((bool)obj.GetValue(property)); + } + + [Fact] + public void CoerceValue_InvokeNullable_GetValueReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool?), typeof(DependencyObject)); + var obj = new DependencyObject(); + + // Coerce default. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(property, true); + Assert.True((bool)obj.GetValue(property)); + + // Coerce custom. + obj.CoerceValue(property); + Assert.True((bool)obj.GetValue(property)); + + // Coerce again. + obj.CoerceValue(property); + Assert.True((bool)obj.GetValue(property)); + } + + [Fact] + public void CoerceValue_InvokeReferenceType_GetValueReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + + // Coerce default. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Coerce custom. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + + // Coerce again. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + } + + [Fact] + public void CoerceValue_InvokeObject_GetValueReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(DependencyObject)); + var obj = new DependencyObject(); + + // Coerce default. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Coerce custom. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + + // Coerce again. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + + // Set different. + obj.SetValue(property, 1); + Assert.Equal(1, obj.GetValue(property)); + + // Coerce custom. + obj.CoerceValue(property); + Assert.Equal(1, obj.GetValue(property)); + + // Coerce again. + obj.CoerceValue(property); + Assert.Equal(1, obj.GetValue(property)); + + // Set null. + obj.SetValue(property, null); + Assert.Null(obj.GetValue(property)); + + // Coerce custom. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + + // Coerce again. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void CoerceValue_InvokeCustomPropertyChangeCallback_Success() + { + var obj = new DependencyObject(); + + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Coerce default. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + + // Coerce custom. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + + // Coerce again. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + } + + [Fact] + public void CoerceValue_InvokeCustomCoerceValueCallback_Success() + { + var obj = new DependencyObject(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Coerce default. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Set custom. + expectedNewValue = "value"; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(2, coerceValueCallCount); + + // Coerce custom. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(3, coerceValueCallCount); + + // Coerce again. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(4, coerceValueCallCount); + } + + [Theory] + [InlineData(null)] + [InlineData("customCoercedValue")] + public void CoerceValue_InvokeCustomCoerceValueCallbackReturnsCustom_Success(object? customCoercedValue) + { + var obj = new DependencyObject(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + object? newBaseValue = "coercedValue"; + var typeMetadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return newBaseValue; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name + customCoercedValue?.GetType().Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Coerce default. + obj.CoerceValue(property); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Set custom. + expectedNewValue = "value"; + obj.SetValue(property, "value"); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(2, coerceValueCallCount); + + // Coerce custom. + newBaseValue = customCoercedValue; + obj.CoerceValue(property); + Assert.Equal(customCoercedValue, obj.GetValue(property)); + Assert.Equal(3, coerceValueCallCount); + + // Coerce again. + obj.CoerceValue(property); + Assert.Equal(customCoercedValue, obj.GetValue(property)); + Assert.Equal(4, coerceValueCallCount); + } + + [Fact] + public void CoerceValue_InvokeCustomCoerceValueCallbackReturnsUnset_Success() + { + var obj = new DependencyObject(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedBaseValue = null; + object? newBaseValue = null; + var typeMetadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedBaseValue, baseValue); + coerceValueCallCount++; + return newBaseValue; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Set custom. + expectedBaseValue = "value"; + newBaseValue = "coercedValue"; + obj.SetValue(property, "value"); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Coerce. + expectedBaseValue = "value"; + newBaseValue = DependencyProperty.UnsetValue; + obj.CoerceValue(property); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(2, coerceValueCallCount); + } + + [Fact] + public void CoerceValue_InvokeChangeAndCoerceValueCallback_Success() + { + var obj = new DependencyObject(); + + int changedCallCount = 0; + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Coerce default. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(2, coerceValueCallCount); + + // Coerce custom. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(3, coerceValueCallCount); + + // Coerce again. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(4, coerceValueCallCount); + } + + [Fact] + public void CoerceValue_InvokeReadOnly_GetValueReturnsExpected() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + // Coerce default. + obj.CoerceValue(property); + Assert.False((bool)obj.GetValue(property)); + + // Coerce again. + obj.CoerceValue(property); + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void CoerceValue_DependencyPropertyNullDp_ThrowsArgumentNullException() + { + var obj = new DependencyObject(); + // TODO: this should throw ArgumentNullException + //Assert.Throws("dp", () => obj.CoerceValue(null!)); + Assert.Throws(() => obj.CoerceValue(null!)); + } + + [Theory] + [InlineData(null)] + [InlineData("value")] + public void CoerceValue_InvalidResultDefault_ThrowsArgumentException(object? invalidValue) + { + var obj = new DependencyObject(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + 0, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Equal(expectedNewValue, baseValue); + coerceValueCallCount++; + return invalidValue; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name + invalidValue?.GetType().Name, typeof(int), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Coerce default. + expectedNewValue = 0; + Assert.Throws(() => obj.CoerceValue(property)); + Assert.Equal(0, obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + } + + [Theory] + [InlineData(null)] + [InlineData("value")] + public void CoerceValue_InvalidResultCustom_ThrowsArgumentException(object? invalidValue) + { + var obj = new DependencyObject(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + object? newBaseValue = 1; + var typeMetadata = new PropertyMetadata( + 0, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Equal(expectedNewValue, baseValue); + coerceValueCallCount++; + return newBaseValue; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name + invalidValue?.GetType().Name, typeof(int), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Set custom. + expectedNewValue = 2; + newBaseValue = 2; + obj.SetValue(property, 2); + Assert.Equal(2, obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Coerce custom. + newBaseValue = invalidValue; + Assert.Throws(() => obj.CoerceValue(property)); + Assert.Equal(2, coerceValueCallCount); + } + + [Fact] + public void CoerceValue_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => obj.CoerceValue(property)); + }); + } + + [Fact] + public void Equals_Invoke_ReturnsExpected() + { + var obj = new DependencyObject(); + Assert.True(obj.Equals(obj)); + Assert.False(obj.Equals(new DependencyObject())); + Assert.False(obj.Equals(null)); + } + + [Fact] + public void GetHashCode_Invoke_ReturnsEqual() + { + var obj = new DependencyObject(); + Assert.NotEqual(0, obj.GetHashCode()); + Assert.Equal(obj.GetHashCode(), obj.GetHashCode()); + } + + [Fact] + public void GetLocalValueEnumerator_InvokeNoProperties_ReturnsExpected() + { + var obj = new DependencyObject(); + LocalValueEnumerator enumerator = obj.GetLocalValueEnumerator(); + for (int i = 0; i < 2; i++) + { + Assert.Throws(() => enumerator.Current); + Assert.Throws(() => ((IEnumerator)enumerator).Current); + + // Move. + Assert.False(enumerator.MoveNext()); + Assert.Throws(() => enumerator.Current); + Assert.Throws(() => ((IEnumerator)enumerator).Current); + + // Move end. + Assert.False(enumerator.MoveNext()); + Assert.Throws(() => enumerator.Current); + Assert.Throws(() => ((IEnumerator)enumerator).Current); + + // Move again. + Assert.False(enumerator.MoveNext()); + Assert.Throws(() => enumerator.Current); + Assert.Throws(() => ((IEnumerator)enumerator).Current); + + // Reset. + enumerator.Reset(); + } + } + + [Fact] + public void GetLocalValueEnumerator_InvokeWithProperties_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "value"); + + LocalValueEnumerator enumerator = obj.GetLocalValueEnumerator(); + for (int i = 0; i < 2; i++) + { + Assert.Throws(() => enumerator.Current); + Assert.Throws(() => ((IEnumerator)enumerator).Current); + + // Move. + Assert.True(enumerator.MoveNext()); + LocalValueEntry entry = enumerator.Current; + Assert.Same(property, entry.Property); + Assert.Equal("value", entry.Value); + + entry = Assert.IsType(((IEnumerator)enumerator).Current); + Assert.Same(property, entry.Property); + Assert.Equal("value", entry.Value); + + // Move end. + Assert.False(enumerator.MoveNext()); + Assert.Throws(() => enumerator.Current); + Assert.Throws(() => ((IEnumerator)enumerator).Current); + + // Move again. + Assert.False(enumerator.MoveNext()); + Assert.Throws(() => enumerator.Current); + Assert.Throws(() => ((IEnumerator)enumerator).Current); + + // Reset. + enumerator.Reset(); + } + } + + + [Fact] + public void GetLocalValueEnumerator_InvokeWithPropertiesCoerced_ReturnsExpected() + { + var metadata = new PropertyMetadata( + null, + null, + (d, baseValue) => "coercedValue"); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "value"); + + LocalValueEnumerator enumerator = obj.GetLocalValueEnumerator(); + for (int i = 0; i < 2; i++) + { + Assert.Throws(() => enumerator.Current); + Assert.Throws(() => ((IEnumerator)enumerator).Current); + + // Move. + Assert.True(enumerator.MoveNext()); + LocalValueEntry entry = enumerator.Current; + Assert.Same(property, entry.Property); + Assert.Equal("value", entry.Value); + + entry = Assert.IsType(((IEnumerator)enumerator).Current); + Assert.Same(property, entry.Property); + Assert.Equal("value", entry.Value); + + // Move end. + Assert.False(enumerator.MoveNext()); + Assert.Throws(() => enumerator.Current); + Assert.Throws(() => ((IEnumerator)enumerator).Current); + + // Move again. + Assert.False(enumerator.MoveNext()); + Assert.Throws(() => enumerator.Current); + Assert.Throws(() => ((IEnumerator)enumerator).Current); + + // Reset. + enumerator.Reset(); + } + } + + [Fact] + public void GetLocalValueEnumerator_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + var obj = new DependencyObject(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => obj.GetLocalValueEnumerator()); + }); + } + + [Fact] + public void GetValue_InvokeValueType_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + var obj = new DependencyObject(); + + Assert.False((bool)obj.GetValue(property)); + + // Call again to test caching. + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeNullableType_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool?), typeof(DependencyObject)); + var obj = new DependencyObject(); + + Assert.Null(obj.GetValue(property)); + + // Call again to test caching. + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeReferenceType_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + + Assert.Null(obj.GetValue(property)); + + // Call again to test caching. + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeObjectType_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(DependencyObject)); + var obj = new DependencyObject(); + + Assert.Null(obj.GetValue(property)); + + // Call again to test caching. + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeDefaultValue_ReturnsExpected() + { + var metadata = new PropertyMetadata("value"); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), metadata); + var obj = new DependencyObject(); + + Assert.Equal("value", obj.GetValue(property)); + + // Call again to test caching. + Assert.Equal("value", obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeSetValue_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "value"); + + Assert.Equal("value", obj.GetValue(property)); + + // Call again to test caching. + Assert.Equal("value", obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeSetValueCoerced_ReturnsExpected() + { + object coerceResult = "coercedValue"; + int coerceValueCallbackCallCount = 0; + var metadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + coerceValueCallbackCallCount++; + return coerceResult; + }); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), metadata); + var obj = new DependencyObject(); + obj.SetValue(property, "value"); + Assert.Equal(1, coerceValueCallbackCallCount); + + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallbackCallCount); + + // Call again to test caching. + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallbackCallCount); + + // Change coerce result. + coerceResult = "coercedValue2"; + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallbackCallCount); + + // Set to unset. + coerceResult = DependencyProperty.UnsetValue; + obj.SetValue(property, "value2"); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(2, coerceValueCallbackCallCount); + } + + [Fact] + public void GetValue_InvokeClearedValue_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "value"); + obj.ClearValue(property); + + Assert.Null(obj.GetValue(property)); + + // Call again to test caching. + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeCustomDefaultValue_ReturnsExpected() + { + var obj = new DependencyObject(); + var typeMetadata = new PropertyMetadata("value"); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + + Assert.Equal("value", obj.GetValue(property)); + + // Call again to test caching. + Assert.Equal("value", obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeReadOnlyProperty_ReturnsExpected() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + Assert.False((bool)obj.GetValue(property)); + + // Call again to test caching. + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeReadOnlyPropertySet_ReturnsExpected() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + obj.SetValue(key, "value"); + + Assert.Equal("value", obj.GetValue(property)); + + // Call again to test caching. + Assert.Equal("value", obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeReadOnlyPropertyUIElementIsVisible_ReturnsExpected() + { + // This has a special internal getter - GetReadOnlyValueCallback. + var obj = new DependencyObject(); + DependencyProperty property = UIElement.IsVisibleProperty; + + Assert.False((bool)obj.GetValue(property)); + + // Call again to test caching. + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeReadOnlyPropertyUIElement3DIsVisible_ReturnsExpected() + { + // This has a special internal getter - GetReadOnlyValueCallback. + var obj = new DependencyObject(); + DependencyProperty property = UIElement3D.IsVisibleProperty; + + Assert.False((bool)obj.GetValue(property)); + + // Call again to test caching. + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeReadOnlyPropertyFrameworkElementActualWidth_ReturnsExpected() + { + // This has a special internal getter - GetReadOnlyValueCallback. + var obj = new DependencyObject(); + DependencyProperty property = FrameworkElement.ActualWidthProperty; + + Assert.Equal(0.0, obj.GetValue(property)); + + // Call again to test caching. + Assert.Equal(0.0, obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeReadOnlyPropertyFrameworkElementActualHeight_ReturnsExpected() + { + // This has a special internal getter - GetReadOnlyValueCallback. + var obj = new DependencyObject(); + DependencyProperty property = FrameworkElement.ActualHeightProperty; + + Assert.Equal(0.0, obj.GetValue(property)); + + // Call again to test caching. + Assert.Equal(0.0, obj.GetValue(property)); + } + + [Fact] + public void GetValue_NullProperty_ThrowsArgumentNullException() + { + var obj = new DependencyObject(); + Assert.Throws("dp", () => obj.GetValue(null!)); + } + + [Fact] + public void GetValue_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => obj.GetValue(property)); + }); + } + + [Fact] + public void InvalidateProperty_InvokeNoSuchProperty_Success() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + + // Invalidate. + obj.InvalidateProperty(property); + Assert.Null(obj.GetValue(property)); + + // Invalidate again. + obj.InvalidateProperty(property); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void InvalidateProperty_InvokeSetProperty_Success() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "value"); + + // Invalidate. + obj.InvalidateProperty(property); + Assert.Equal("value", obj.GetValue(property)); + + // Invalidate again. + obj.InvalidateProperty(property); + Assert.Equal("value", obj.GetValue(property)); + } + + [Fact] + public void InvalidateProperty_InvokeClearedProperty_Success() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "value"); + obj.ClearValue(property); + + // Invalidate. + obj.InvalidateProperty(property); + Assert.Null(obj.GetValue(property)); + + // Invalidate again. + obj.InvalidateProperty(property); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void InvalidateProperty_InvokeCoercedProperty_Success() + { + object coerceResult = "baseValue"; + int coerceValueCallbackCallCount = 0; + var metadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + coerceValueCallbackCallCount++; + return coerceResult; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), metadata); + var obj = new DependencyObject(); + obj.SetValue(property, "value"); + Assert.Equal(coerceResult, obj.GetValue(property)); + Assert.Equal(1, coerceValueCallbackCallCount); + + // Change coerce result. + coerceResult = "other"; + Assert.Equal("baseValue", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallbackCallCount); + + // Invalidate. + obj.InvalidateProperty(property); + Assert.Equal("other", obj.GetValue(property)); + Assert.Equal(2, coerceValueCallbackCallCount); + + // Invalidate again. + obj.InvalidateProperty(property); + Assert.Equal("other", obj.GetValue(property)); + Assert.Equal(3, coerceValueCallbackCallCount); + + // Set to unset. + coerceResult = DependencyProperty.UnsetValue; + obj.InvalidateProperty(property); + Assert.Equal("other", obj.GetValue(property)); + Assert.Equal(4, coerceValueCallbackCallCount); + } + + [Fact] + public void InvalidateProperty_NullDp_ThrowsArgumentNullException() + { + var obj = new DependencyObject(); + Assert.Throws("dp", () => obj.InvalidateProperty(null!)); + } + + [Fact] + public void InvalidateProperty_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => obj.InvalidateProperty(property)); + }); + } + + [Fact] + public void InvalidateProperty_InvokeCoercedPropertyInvalid_ThrowsArgumentException() + { + object coerceResult = "baseValue"; + int coerceValueCallbackCallCount = 0; + var metadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + coerceValueCallbackCallCount++; + return coerceResult; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), metadata); + var obj = new DependencyObject(); + obj.SetValue(property, "value"); + Assert.Equal(coerceResult, obj.GetValue(property)); + Assert.Equal(1, coerceValueCallbackCallCount); + + // Change coerce result. + coerceResult = new object(); + Assert.Equal("baseValue", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallbackCallCount); + + // Invalidate. + coerceResult = new object(); + Assert.Throws(() => obj.InvalidateProperty(property)); + Assert.Equal("baseValue", obj.GetValue(property)); + Assert.Equal(2, coerceValueCallbackCallCount); + + // Invalidate again. + Assert.Throws(() => obj.InvalidateProperty(property)); + Assert.Equal("baseValue", obj.GetValue(property)); + Assert.Equal(3, coerceValueCallbackCallCount); + } + + [Fact] + public void ReadLocalValue_InvokeValueType_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + var obj = new DependencyObject(); + + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + + // Call again to test caching. + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + } + + [Fact] + public void ReadLocalValue_InvokeNullableType_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool?), typeof(DependencyObject)); + var obj = new DependencyObject(); + + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + + // Call again to test caching. + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + } + + [Fact] + public void ReadLocalValue_InvokeReferenceType_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + + // Call again to test caching. + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + } + + [Fact] + public void ReadLocalValue_InvokeObjectType_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(DependencyObject)); + var obj = new DependencyObject(); + + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + + // Call again to test caching. + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + } + + [Fact] + public void ReadLocalValue_InvokeDefaultValue_ReturnsExpected() + { + var metadata = new PropertyMetadata("value"); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), metadata); + var obj = new DependencyObject(); + + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + + // Call again to test caching. + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + } + + [Fact] + public void ReadLocalValue_InvokeSetValue_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "value"); + + Assert.Equal("value", obj.ReadLocalValue(property)); + + // Call again to test caching. + Assert.Equal("value", obj.ReadLocalValue(property)); + } + + [Fact] + public void ReadLocalValue_InvokeSetValueCoerced_ReturnsExpected() + { + string coerceResult = "coercedValue"; + int coerceValueCallbackCallCount = 0; + var metadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + coerceValueCallbackCallCount++; + return coerceResult; + }); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), metadata); + var obj = new DependencyObject(); + obj.SetValue(property, "value"); + Assert.Equal(1, coerceValueCallbackCallCount); + + Assert.Equal("value", obj.ReadLocalValue(property)); + Assert.Equal(1, coerceValueCallbackCallCount); + + // Call again to test caching. + Assert.Equal("value", obj.ReadLocalValue(property)); + Assert.Equal(1, coerceValueCallbackCallCount); + + // Change coerce result. + coerceResult = "value"; + Assert.Equal("value", obj.ReadLocalValue(property)); + Assert.Equal(1, coerceValueCallbackCallCount); + } + + [Fact] + public void ReadLocalValue_InvokeSetValueCoercedUnset_ReturnsExpected() + { + object coerceResult = "coercedValue"; + int coerceValueCallbackCallCount = 0; + var metadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + coerceValueCallbackCallCount++; + return coerceResult; + }); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), metadata); + var obj = new DependencyObject(); + obj.SetValue(property, "value"); + Assert.Equal(1, coerceValueCallbackCallCount); + + Assert.Equal("value", obj.ReadLocalValue(property)); + Assert.Equal(1, coerceValueCallbackCallCount); + + // Call again to test caching. + Assert.Equal("value", obj.ReadLocalValue(property)); + Assert.Equal(1, coerceValueCallbackCallCount); + + // Change coerce result. + coerceResult = "value"; + Assert.Equal("value", obj.ReadLocalValue(property)); + Assert.Equal(1, coerceValueCallbackCallCount); + + // Set to unset. + coerceResult = DependencyProperty.UnsetValue; + obj.SetValue(property, "value2"); + Assert.Equal("value2", obj.ReadLocalValue(property)); + Assert.Equal(2, coerceValueCallbackCallCount); + } + + [Fact] + public void ReadLocalValue_InvokeClearedValue_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "value"); + obj.ClearValue(property); + + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + + // Call again to test caching. + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + } + + [Fact] + public void ReadLocalValue_InvokeCustomDefaultValue_ReturnsExpected() + { + var obj = new DependencyObject(); + var typeMetadata = new PropertyMetadata("value"); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + + // Call again to test caching. + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + } + + [Fact] + public void ReadLocalValue_InvokeReadOnlyProperty_ReturnsExpected() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + + // Call again to test caching. + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + } + + [Fact] + public void ReadLocalValue_InvokeReadOnlyPropertySet_ReturnsExpected() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + obj.SetValue(key, "value"); + + Assert.Equal("value", obj.ReadLocalValue(property)); + + // Call again to test caching. + Assert.Equal("value", obj.ReadLocalValue(property)); + } + + [Fact] + public void ReadLocalValue_InvokeReadOnlyPropertyUIElementIsVisible_ReturnsExpected() + { + // This has a special internal getter - GetReadOnlyValueCallback. + var obj = new DependencyObject(); + DependencyProperty property = UIElement.IsVisibleProperty; + + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + + // Call again to test caching. + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + } + + [Fact] + public void ReadLocalValue_InvokeReadOnlyPropertyUIElement3DIsVisible_ReturnsExpected() + { + // This has a special internal getter - GetReadOnlyValueCallback. + var obj = new DependencyObject(); + DependencyProperty property = UIElement3D.IsVisibleProperty; + + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + + // Call again to test caching. + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + } + + [Fact] + public void ReadLocalValue_InvokeReadOnlyPropertyFrameworkElementActualWidth_ReturnsExpected() + { + // This has a special internal getter - GetReadOnlyValueCallback. + var obj = new DependencyObject(); + DependencyProperty property = FrameworkElement.ActualWidthProperty; + + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + + // Call again to test caching. + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + } + + [Fact] + public void ReadLocalValue_InvokeReadOnlyPropertyFrameworkElementActualHeight_ReturnsExpected() + { + // This has a special internal getter - GetReadOnlyValueCallback. + var obj = new DependencyObject(); + DependencyProperty property = FrameworkElement.ActualHeightProperty; + + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + + // Call again to test caching. + Assert.Same(DependencyProperty.UnsetValue, obj.ReadLocalValue(property)); + } + + [Fact] + public void ReadLocalValue_NullProperty_ThrowsArgumentNullException() + { + var obj = new DependencyObject(); + Assert.Throws("dp", () => obj.ReadLocalValue(null!)); + } + + [Fact] + public void ReadLocalValue_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => obj.ReadLocalValue(property)); + }); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyValueType_GetValueReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + var obj = new DependencyObject(); + + // Set true. + obj.SetValue(property, true); + Assert.True((bool)obj.GetValue(property)); + + // Set same. + obj.SetValue(property, true); + Assert.True((bool)obj.GetValue(property)); + + // Set false. + obj.SetValue(property, false); + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyNullable_GetValueReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool?), typeof(DependencyObject)); + var obj = new DependencyObject(); + + // Set custom. + obj.SetValue(property, true); + Assert.True((bool)obj.GetValue(property)); + + // Set same. + obj.SetValue(property, true); + Assert.True((bool)obj.GetValue(property)); + + // Set null. + obj.SetValue(property, null); + Assert.Null(obj.GetValue(property)); + + // Set false. + obj.SetValue(property, false); + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyReferenceType_GetValueReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + + // Set value. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Set same. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Set different. + obj.SetValue(property, "other"); + Assert.Equal("other", obj.GetValue(property)); + + // Set null. + obj.SetValue(property, null); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyObject_GetValueReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(DependencyObject)); + var obj = new DependencyObject(); + + // Set value. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Set same. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Set different. + obj.SetValue(property, 1); + Assert.Equal(1, obj.GetValue(property)); + + // Set null. + obj.SetValue(property, null); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyWithPropertyChangeCallback_Success() + { + var obj = new DependencyObject(); + + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + + // Set same. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + + // Set different. + expectedNewValue = "other"; + expectedOldValue = "value"; + obj.SetValue(property, "other"); + Assert.Equal("other", obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + + // Set null. + expectedNewValue = null; + expectedOldValue = "other"; + obj.SetValue(property, null); + Assert.Null(obj.GetValue(property)); + Assert.Equal(3, changedCallCount); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyWithCoerceCallback_Success() + { + var obj = new DependencyObject(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Set custom. + expectedNewValue = "value"; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Set same. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(2, coerceValueCallCount); + + // Set different. + expectedNewValue = "other"; + obj.SetValue(property, "other"); + Assert.Equal("other", obj.GetValue(property)); + Assert.Equal(3, coerceValueCallCount); + + // Set null. + expectedNewValue = null; + obj.SetValue(property, null); + Assert.Null(obj.GetValue(property)); + Assert.Equal(4, coerceValueCallCount); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyWithCoerceCallbackCustom_Success() + { + var obj = new DependencyObject(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return "coercedValue"; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Set custom. + expectedNewValue = "value"; + obj.SetValue(property, "value"); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Set same. + obj.SetValue(property, "value"); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(2, coerceValueCallCount); + + // Set different. + expectedNewValue = "other"; + obj.SetValue(property, "other"); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(3, coerceValueCallCount); + + // Set null. + expectedNewValue = null; + obj.SetValue(property, null); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(4, coerceValueCallCount); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyWithPropertyChangeAndCoerceCallback_Success() + { + var obj = new DependencyObject(); + + int changedCallCount = 0; + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + + // Set same. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(2, coerceValueCallCount); + + // Set different. + expectedNewValue = "other"; + expectedOldValue = "value"; + obj.SetValue(property, "other"); + Assert.Equal("other", obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(3, coerceValueCallCount); + + // Set null. + expectedNewValue = null; + expectedOldValue = "other"; + obj.SetValue(property, null); + Assert.Null(obj.GetValue(property)); + Assert.Equal(3, changedCallCount); + Assert.Equal(4, coerceValueCallCount); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyCustomOnPropertyChanged_CallsOnPropertyChanged() + { + var obj = new CustomDependencyObject(); + + int onPropertyChangedCallCount = 0; + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + obj.OnPropertyChangedAction = (e) => + { + Assert.True(changedCallCount > onPropertyChangedCallCount); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + onPropertyChangedCallCount++; + }; + var typeMetadata = new PropertyMetadata( + null, + (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + + // Set same. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + + // Set different. + expectedNewValue = "other"; + expectedOldValue = "value"; + obj.SetValue(property, "other"); + Assert.Equal("other", obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + + // Set null. + expectedNewValue = null; + expectedOldValue = "other"; + obj.SetValue(property, null); + Assert.Null(obj.GetValue(property)); + Assert.Equal(3, changedCallCount); + } + + [Fact] + public void SetValue_DependencyPropertyNullDp_ThrowsArgumentNullException() + { + var obj = new DependencyObject(); + Assert.Throws("dp", () => obj.SetValue((DependencyProperty)null!, true)); + } + + [Fact] + public void SetValue_DependencyPropertyReadOnly_ThrowsInvalidOperationException() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + Assert.Throws(() => obj.SetValue(property, "value")); + } + + [Theory] + [InlineData(null)] + [InlineData("value")] + [InlineData(1)] + public void SetValue_DependencyPropertyInvalidValueValueType_ThrowsArgumentException(object? value) + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name + value?.GetType().Name, typeof(bool), typeof(DependencyObject)); + var obj = new DependencyObject(); + // TODO: add paramName + Assert.Throws(() => obj.SetValue(property, value)); + } + + [Theory] + [InlineData("value")] + [InlineData(1)] + public void SetValue_DependencyPropertyInvalidValueNullable_ThrowsArgumentException(object value) + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name + value?.GetType().Name, typeof(bool?), typeof(DependencyObject)); + var obj = new DependencyObject(); + // TODO: add paramName + Assert.Throws(() => obj.SetValue(property, value)); + } + + public static IEnumerable SetValue_DependencyPropertyInvalidValueReferenceType_TestData() + { + yield return new object[] { new object() }; + yield return new object[] { 1 }; + } + + [Theory] + [MemberData(nameof(SetValue_DependencyPropertyInvalidValueReferenceType_TestData))] + public void SetValue_DependencyPropertyInvalidValueReferenceType_ThrowsArgumentException(object value) + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name + value?.GetType().Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + // TODO: add paramName + Assert.Throws(() => obj.SetValue(property, value)); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyOnDifferentThread_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => obj.SetValue(property, "value")); + }); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyKeyValueType_GetValueReturnsExpected() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + // Set true. + obj.SetValue(key, true); + Assert.True((bool)obj.GetValue(property)); + + // Set same. + obj.SetValue(key, true); + Assert.True((bool)obj.GetValue(property)); + + // Set false. + obj.SetValue(key, false); + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyKeyNullable_GetValueReturnsExpected() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool?), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + // Set true. + obj.SetValue(key, true); + Assert.True((bool)obj.GetValue(property)); + + // Set same. + obj.SetValue(key, true); + Assert.True((bool)obj.GetValue(property)); + + // Set null. + obj.SetValue(key, null); + Assert.Null(obj.GetValue(property)); + + // Set false. + obj.SetValue(key, false); + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyKeyReferenceType_GetValueReturnsExpected() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + // Set value. + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Set same. + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Set different. + obj.SetValue(key, "other"); + Assert.Equal("other", obj.GetValue(property)); + + // Set null. + obj.SetValue(key, null); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyKeyObject_GetValueReturnsExpected() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + // Set value. + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Set same. + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Set different. + obj.SetValue(key, 1); + Assert.Equal(1, obj.GetValue(property)); + + // Set null. + obj.SetValue(key, null); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyKeyWithPropertyChangeCallback_Success() + { + var obj = new DependencyObject(); + + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + DependencyProperty property = key.DependencyProperty; + expectedProperty = property; + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + + // Set same. + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + + // Set different. + expectedNewValue = "other"; + expectedOldValue = "value"; + obj.SetValue(key, "other"); + Assert.Equal("other", obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + + // Set null. + expectedNewValue = null; + expectedOldValue = "other"; + obj.SetValue(key, null); + Assert.Null(obj.GetValue(property)); + Assert.Equal(3, changedCallCount); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyKeyWithCoerceCallback_Success() + { + var obj = new DependencyObject(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + } + ); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + DependencyProperty property = key.DependencyProperty; + expectedProperty = property; + + // Set custom. + expectedNewValue = "value"; + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Set same. + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(2, coerceValueCallCount); + + // Set different. + expectedNewValue = "other"; + obj.SetValue(key, "other"); + Assert.Equal("other", obj.GetValue(property)); + Assert.Equal(3, coerceValueCallCount); + + // Set null. + expectedNewValue = null; + obj.SetValue(key, null); + Assert.Null(obj.GetValue(property)); + Assert.Equal(4, coerceValueCallCount); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyKeyWithCoerceCallbackCustom_Success() + { + var obj = new DependencyObject(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return "coercedValue"; + } + ); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + DependencyProperty property = key.DependencyProperty; + expectedProperty = property; + + // Set custom. + expectedNewValue = "value"; + obj.SetValue(key, "value"); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Set same. + obj.SetValue(key, "value"); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(2, coerceValueCallCount); + + // Set different. + expectedNewValue = "other"; + obj.SetValue(key, "other"); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(3, coerceValueCallCount); + + // Set null. + expectedNewValue = null; + obj.SetValue(key, null); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(4, coerceValueCallCount); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyKeyWithPropertyChangeAndCoerceCallback_Success() + { + var obj = new DependencyObject(); + + int changedCallCount = 0; + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + } + ); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + DependencyProperty property = key.DependencyProperty; + expectedProperty = property; + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + + // Set same. + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(2, coerceValueCallCount); + + // Set different. + expectedNewValue = "other"; + expectedOldValue = "value"; + obj.SetValue(key, "other"); + Assert.Equal("other", obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(3, coerceValueCallCount); + + // Set null. + expectedNewValue = null; + expectedOldValue = "other"; + obj.SetValue(key, null); + Assert.Null(obj.GetValue(property)); + Assert.Equal(3, changedCallCount); + Assert.Equal(4, coerceValueCallCount); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyKeyCustomOnPropertyChanged_CallsOnPropertyChanged() + { + var obj = new CustomDependencyObject(); + + int onPropertyChangedCallCount = 0; + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + obj.OnPropertyChangedAction = (e) => + { + Assert.True(changedCallCount > onPropertyChangedCallCount); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + onPropertyChangedCallCount++; + }; + var typeMetadata = new PropertyMetadata( + null, + (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + DependencyProperty property = key.DependencyProperty; + expectedProperty = property; + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(1, onPropertyChangedCallCount); + + // Set same. + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(1, onPropertyChangedCallCount); + + // Set different. + expectedNewValue = "other"; + expectedOldValue = "value"; + obj.SetValue(key, "other"); + Assert.Equal("other", obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(2, onPropertyChangedCallCount); + + // Set null. + expectedNewValue = null; + expectedOldValue = "other"; + obj.SetValue(key, null); + Assert.Null(obj.GetValue(property)); + Assert.Equal(3, changedCallCount); + Assert.Equal(3, onPropertyChangedCallCount); + } + + [Fact] + public void SetValue_NullPropertyKey_ThrowsArgumentNullException() + { + var obj = new DependencyObject(); + Assert.Throws("key", () => obj.SetValue((DependencyPropertyKey)null!, true)); + } + + [Theory] + [InlineData(null)] + [InlineData("value")] + [InlineData(1)] + public void SetValue_DependencyPropertyKeyInvalidValueValueType_ThrowsArgumentException(object? value) + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name + value?.GetType().Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + // TODO: add paramName + Assert.Throws(() => obj.SetValue(key, value)); + } + + [Theory] + [InlineData("value")] + [InlineData(1)] + public void SetValue_DependencyPropertyKeyInvalidValueNullable_ThrowsArgumentException(object value) + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name + value?.GetType().Name, typeof(bool?), typeof(DependencyObject), new PropertyMetadata()); + // TODO: add paramName + Assert.Throws(() => obj.SetValue(key, value)); + } + + public static IEnumerable SetValue_DependencyPropertyKeyInvalidValueReferenceType_TestData() + { + yield return new object[] { new object() }; + yield return new object[] { 1 }; + } + + [Theory] + [MemberData(nameof(SetValue_DependencyPropertyKeyInvalidValueReferenceType_TestData))] + public void SetValue_DependencyPropertyKeyInvalidValueReferenceType_ThrowsArgumentException(object value) + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name + value?.GetType().Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + // TODO: add paramName + Assert.Throws(() => obj.SetValue(key, value)); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyKeyOnDifferentThread_ThrowsInvalidOperationException() + { + var obj = new DependencyObject(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => obj.SetValue(key, "value")); + }); + } + + [Fact] + public void OnPropertyChanged_InvokeNoMetadata_Nop() + { + var obj = new SubDependencyObject(); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + obj.OnPropertyChanged(new DependencyPropertyChangedEventArgs(property, new object(), new object())); + } + + [Fact] + public void OnPropertyChanged_InvokeEmptyMetadata_Nop() + { + var obj = new SubDependencyObject(); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + obj.OnPropertyChanged(new DependencyPropertyChangedEventArgs(property, new object(), new object())); + } + + [Fact] + public void OnPropertyChanged_InvokeMetadataHasPropertyChangedCallback_Nop() + { + var obj = new SubDependencyObject(); + int callCount = 0; + var metadata = new PropertyMetadata(null, (d, actualE) => callCount++); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), metadata); + var e = new DependencyPropertyChangedEventArgs(property, new object(), new object()); + obj.OnPropertyChanged(e); + Assert.Equal(0, callCount); + } + + [Fact] + public void OnPropertyChanged_InvokeOnDifferentThread_Nop() + { + var obj = new SubDependencyObject(); + int callCount = 0; + var metadata = new PropertyMetadata(null, (d, actualE) => callCount++); + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), metadata); + var e = new DependencyPropertyChangedEventArgs(property, new object(), new object()); + + Helpers.ExecuteOnDifferentThread(() => + { + obj.OnPropertyChanged(e); + Assert.Equal(0, callCount); + }); + } + + [Fact] + public void OnPropertyChanged_NullEProperty_ThrowsArgumentException() + { + var obj = new SubDependencyObject(); + DependencyPropertyChangedEventArgs e = default; + Assert.Throws("e", () => obj.OnPropertyChanged(e)); + } + + [Fact] + public void ShouldSerializeProperty_InvokeSetProperty_ReturnsTrue() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new SubDependencyObject(); + + obj.SetValue(property, "value"); + Assert.True(obj.ShouldSerializeProperty(property)); + + // Call again to test caching. + Assert.True(obj.ShouldSerializeProperty(property)); + } + + [Fact] + public void ShouldSerializeProperty_InvokeSetPropertyReadOnly_ReturnsTrue() + { + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + var obj = new SubDependencyObject(); + + obj.SetValue(key, "value"); + Assert.True(obj.ShouldSerializeProperty(property)); + + // Call again to test caching. + Assert.True(obj.ShouldSerializeProperty(property)); + } + + [Fact] + public void ShouldSerializeProperty_InvokeNoSuchProperty_ReturnsFalse() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new SubDependencyObject(); + Assert.False(obj.ShouldSerializeProperty(property)); + + // Call again to test caching. + Assert.False(obj.ShouldSerializeProperty(property)); + } + + [Fact] + public void ShouldSerializeProperty_InvokeNoSuchPropertyReadOnly_ReturnsFalse() + { + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + var obj = new SubDependencyObject(); + Assert.False(obj.ShouldSerializeProperty(property)); + + // Call again to test caching. + Assert.False(obj.ShouldSerializeProperty(property)); + } + + [Fact] + public void ShouldSerializeProperty_InvokeClearedProperty_ReturnsFalse() + { + DependencyProperty property = DependencyProperty.Register(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new SubDependencyObject(); + obj.SetValue(property, "value"); + obj.ClearValue(property); + Assert.False(obj.ShouldSerializeProperty(property)); + + // Call again to test caching. + Assert.False(obj.ShouldSerializeProperty(property)); + } + + [Fact] + public void ShouldSerializeProperty_InvokeClearedPropertyReadOnly_ReturnsFalse() + { + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(DependencyObjectTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + var obj = new SubDependencyObject(); + obj.SetValue(key, "value"); + obj.ClearValue(key); + Assert.False(obj.ShouldSerializeProperty(property)); + + // Call again to test caching. + Assert.False(obj.ShouldSerializeProperty(property)); + } + + private class CustomDependencyObject : DependencyObject + { + public Action? OnPropertyChangedAction { get; set; } + + protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + base.OnPropertyChanged(e); + OnPropertyChangedAction?.Invoke(e); + } + } + + private class SubDependencyObject : DependencyObject + { + public new void OnPropertyChanged(DependencyPropertyChangedEventArgs e) => base.OnPropertyChanged(e); + + public new bool ShouldSerializeProperty(DependencyProperty dp) => base.ShouldSerializeProperty(dp); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DependencyObjectTypeTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DependencyObjectTypeTests.cs new file mode 100644 index 00000000000..64236353253 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DependencyObjectTypeTests.cs @@ -0,0 +1,122 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Tests; + +public class DependencyObjectTypeTests +{ + [Fact] + public void FromSystemType_DependencyObject_Success() + { + DependencyObjectType type = DependencyObjectType.FromSystemType(typeof(DependencyObject)); + Assert.NotNull(type); + Assert.Same(type, DependencyObjectType.FromSystemType(typeof(DependencyObject))); + Assert.Null(type.BaseType); + Assert.Equal(nameof(DependencyObject), type.Name); + Assert.True(type.Id >= 0); + Assert.Equal(typeof(DependencyObject), type.SystemType); + } + + [Fact] + public void FromSystemType_SubDependencyObject_Success() + { + DependencyObjectType type = DependencyObjectType.FromSystemType(typeof(SubDependencyObject)); + Assert.NotNull(type); + Assert.Same(type, DependencyObjectType.FromSystemType(typeof(SubDependencyObject))); + Assert.NotNull(type.BaseType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(DependencyObject)), type.BaseType); + Assert.Equal(nameof(SubDependencyObject), type.Name); + Assert.True(type.Id >= 0); + Assert.Equal(typeof(SubDependencyObject), type.SystemType); + } + + [Fact] + public void FromSystemType_MultipleTypes_IdDifferent() + { + DependencyObjectType type1 = DependencyObjectType.FromSystemType(typeof(DependencyObject)); + DependencyObjectType type2 = DependencyObjectType.FromSystemType(typeof(SubDependencyObject)); + Assert.NotEqual(type1.Id, type2.Id); + } + + [Fact] + public void FromSystemType_NullSystemType_ThrowsArgumentNullException() + { + Assert.Throws("systemType", () => DependencyObjectType.FromSystemType(null!)); + } + + [Theory] + [InlineData(typeof(int))] + public void FromSystemType_InvalidSystemType_ThrowsArgumentException(Type systemType) + { + // TODO: add paramName to code. + Assert.Throws(() => DependencyObjectType.FromSystemType(systemType)); + } + + [Fact] + public void GetHashCode_Invoke_ReturnsExpected() + { + DependencyObjectType type = DependencyObjectType.FromSystemType(typeof(DependencyObject)); + Assert.Equal(type.Id, type.GetHashCode()); + } + + public static IEnumerable IsInstanceOfType_TestData() + { + yield return new object?[] { typeof(DependencyObject), new DependencyObject(), true }; + yield return new object?[] { typeof(DependencyObject), new SubDependencyObject(), true }; + yield return new object?[] { typeof(DependencyObject), new OtherDependencyObject(), true }; + yield return new object?[] { typeof(DependencyObject), null, false }; + yield return new object?[] { typeof(SubDependencyObject), new DependencyObject(), false }; + yield return new object?[] { typeof(SubDependencyObject), new SubDependencyObject(), true }; + yield return new object?[] { typeof(SubDependencyObject), new OtherDependencyObject(), false }; + yield return new object?[] { typeof(SubDependencyObject), new SubSubDependencyObject(), true }; + yield return new object?[] { typeof(SubDependencyObject), null, false }; + yield return new object?[] { typeof(SubSubDependencyObject), new DependencyObject(), false }; + yield return new object?[] { typeof(SubSubDependencyObject), new OtherDependencyObject(), false }; + yield return new object?[] { typeof(SubSubDependencyObject), new SubDependencyObject(), false }; + yield return new object?[] { typeof(SubSubDependencyObject), new SubSubDependencyObject(), true }; + yield return new object?[] { typeof(SubSubDependencyObject), null, false }; + } + + [Theory] + [MemberData(nameof(IsInstanceOfType_TestData))] + public void IsInstanceOfType_Invoke_ReturnsExpected(Type systemType, DependencyObject dependencyObject, bool expected) + { + DependencyObjectType type = DependencyObjectType.FromSystemType(systemType); + Assert.Equal(expected, type.IsInstanceOfType(dependencyObject)); + } + + [Theory] + [InlineData(typeof(DependencyObject), typeof(DependencyObject), false)] + [InlineData(typeof(DependencyObject), typeof(SubDependencyObject), false)] + [InlineData(typeof(DependencyObject), typeof(OtherDependencyObject), false)] + [InlineData(typeof(DependencyObject), null, false)] + [InlineData(typeof(SubDependencyObject), typeof(DependencyObject), true)] + [InlineData(typeof(SubDependencyObject), typeof(SubDependencyObject), false)] + [InlineData(typeof(SubDependencyObject), typeof(OtherDependencyObject), false)] + [InlineData(typeof(SubDependencyObject), typeof(SubSubDependencyObject), false)] + [InlineData(typeof(SubDependencyObject), null, false)] + [InlineData(typeof(SubSubDependencyObject), typeof(DependencyObject), true)] + [InlineData(typeof(SubSubDependencyObject), typeof(SubDependencyObject), true)] + [InlineData(typeof(SubSubDependencyObject), typeof(OtherDependencyObject), false)] + [InlineData(typeof(SubSubDependencyObject), typeof(SubSubDependencyObject), false)] + [InlineData(typeof(SubSubDependencyObject), null, false)] + public void IsSubclassOf_Invoke_ReturnsExpected(Type systemType, Type? dependencyObjectType, bool expected) + { + DependencyObjectType type = DependencyObjectType.FromSystemType(systemType); + DependencyObjectType? dependencyType = dependencyObjectType != null ? DependencyObjectType.FromSystemType(dependencyObjectType) : null; + Assert.Equal(expected, type.IsSubclassOf(dependencyType)); + } + + private class SubDependencyObject : DependencyObject + { + } + + private class OtherDependencyObject : DependencyObject + { + } + + private class SubSubDependencyObject : SubDependencyObject + { + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DependencyPropertyChangedEventArgsTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DependencyPropertyChangedEventArgsTests.cs new file mode 100644 index 00000000000..926d98451f8 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DependencyPropertyChangedEventArgsTests.cs @@ -0,0 +1,99 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Reflection; + +namespace System.Windows.Tests; + +public class DependencyPropertyChangedEventArgsTests +{ + public static IEnumerable Ctor_DependencyProperty_Object_Object_TestData() + { + yield return new object?[] { null, null }; + yield return new object?[] { "", "" }; + yield return new object?[] { "oldValue", "newValue" }; + } + + [Theory] + [MemberData(nameof(Ctor_DependencyProperty_Object_Object_TestData))] + public void Ctor_DependencyProperty_Object_Object(object oldValue, object newValue) + { + DependencyProperty property = DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name + (oldValue?.ToString() ?? "null") + (newValue?.ToString() ?? "null"), typeof(string), typeof(DependencyObject)); + var args = new DependencyPropertyChangedEventArgs(property, oldValue, newValue); + Assert.Equal(newValue, args.NewValue); + Assert.Equal(oldValue, args.OldValue); + Assert.Same(property, args.Property); + } + + [Fact] + public void Ctor_NullProperty_ThrowsNullReferenceException() + { + // TODO: this should throw ANE. + Assert.Throws(() => new DependencyPropertyChangedEventArgs(null, "oldValue", "newValue")); + } + + private static DependencyProperty s_property = DependencyProperty.Register(nameof(DependencyPropertyChangedEventArgsTests), typeof(string), typeof(DependencyObject)); + + public static IEnumerable Equals_Object_TestData() + { + yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, "oldValue", "newValue"), new DependencyPropertyChangedEventArgs(s_property, "oldValue", "newValue"), true }; + yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, "oldValue", "newValue"), new DependencyPropertyChangedEventArgs(ContentElement.IsMouseOverProperty, "oldValue", "newValue"), false }; + yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, "oldValue", "newValue"), new DependencyPropertyChangedEventArgs(s_property, "oldValue2", "newValue"), false }; + yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, "oldValue", "newValue"), new DependencyPropertyChangedEventArgs(s_property, null, "newValue"), false }; + yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, "oldValue", "newValue"), new DependencyPropertyChangedEventArgs(s_property, "oldValue", "newValue2"), false }; + yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, "oldValue", "newValue"), new DependencyPropertyChangedEventArgs(s_property, "oldValue", null), false }; + + yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, null, "newValue"), new DependencyPropertyChangedEventArgs(s_property, null, "newValue"), true }; + yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, null, "newValue"), new DependencyPropertyChangedEventArgs(s_property, "other", "newValue"), false }; + yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, "oldValue", null), new DependencyPropertyChangedEventArgs(s_property, "oldValue", null), true }; + yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, "oldValue", null), new DependencyPropertyChangedEventArgs(s_property, "oldValue", "other"), false }; + yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, null, null), new DependencyPropertyChangedEventArgs(s_property, null, null), true }; + yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, null, null), new DependencyPropertyChangedEventArgs(s_property, "other", null), false }; + yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, null, null), new DependencyPropertyChangedEventArgs(s_property, null, "other"), false }; + yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, null, null), new DependencyPropertyChangedEventArgs(s_property, "other", "other"), false }; + + // TODO: these should not throw. + //yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, "oldValue", "newValue"), new object(), false }; + //yield return new object?[] { new DependencyPropertyChangedEventArgs(s_property, "oldValue", "newValue"), null, false }; + } + + [Theory] + [MemberData(nameof(Equals_Object_TestData))] + public void Equals_Object_ReturnsExpected(DependencyPropertyChangedEventArgs args, object other, bool expected) + { + Assert.Equal(expected, args.Equals(other)); + if (other is DependencyPropertyChangedEventArgs otherArgs) + { + Assert.Equal(expected, args.Equals(otherArgs)); + Assert.Equal(expected, args == otherArgs); + Assert.Equal(!expected, args != otherArgs); + } + } + + [Fact] + public void Equals_NullObjectOther_ThrowsNullReferenceException() + { + DependencyProperty property = DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var args = new DependencyPropertyChangedEventArgs(property, "oldValue", "newValue"); + Assert.Throws(() => args.Equals(null)); + } + + [Fact] + public void Equals_OtherNotDependencyPropertyChangedEventArgs_ThrowsInvalidCastException() + { + DependencyProperty property = DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var args = new DependencyPropertyChangedEventArgs(property, "oldValue", "newValue"); + Assert.Throws(() => args.Equals(new object())); + } + + [Fact] + public void GetHashCode_Invoke_ReturnsEqual() + { + DependencyProperty property = DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var args = new DependencyPropertyChangedEventArgs(property, "oldValue", "newValue"); + Assert.NotEqual(0, args.GetHashCode()); + Assert.Equal(args.GetHashCode(), args.GetHashCode()); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DependencyPropertyKeyTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DependencyPropertyKeyTests.cs new file mode 100644 index 00000000000..4f6f8a1629f --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DependencyPropertyKeyTests.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using System.Windows.Threading; + +namespace System.Windows.Tests; + +public class DependencyPropertyKeyTests +{ + [Fact] + public void OverrideMetadata_InvokeSuper_Success() + { + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + var typeMetadata = new PropertyMetadata(); + + key.OverrideMetadata(typeof(DependencyObject), typeMetadata); + } + + [Fact] + public void OverrideMetadata_InvokeSub_Success() + { + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + var typeMetadata = new PropertyMetadata(); + + key.OverrideMetadata(typeof(SubSubDependencyObject), typeMetadata); + } + + [Fact] + public void OverrideMetadata_NullForType_ThrowsArgumentNullException() + { + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(DependencyObject), new PropertyMetadata()); + Assert.Throws("forType", () => key.OverrideMetadata(null!, new PropertyMetadata())); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(object))] + public void OverrideMetadata_ForTypeNotDependencyObject_ThrowsArgumentException(Type forType) + { + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name + forType.Name, typeof(object), typeof(DependencyObject), new PropertyMetadata()); + Assert.Throws(() => key.OverrideMetadata(forType, new PropertyMetadata())); + } + + [Fact] + public void OverrideMetadata_NullTypeMetadata_Th1rowsArgumentNullException() + { + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(DependencyObject), new PropertyMetadata()); + Assert.Throws("typeMetadata", () => key.OverrideMetadata(typeof(int), null)); + } + + [Fact] + public void OverrideMetadata_SealedTypeMetadata_Th1rowsArgumentNullException() + { + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + Assert.Throws(() => key.OverrideMetadata(typeof(int), property.DefaultMetadata)); + } + + [Fact] + public void OverrideMetadata_AlreadyRegistered_ThrowsArgumentException() + { + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(DependencyObject), new PropertyMetadata()); + Assert.Throws(() => key.OverrideMetadata(typeof(DependencyObject), new PropertyMetadata())); + } + + private class SubDependencyObject : DependencyObject + { + } + + private class SubSubDependencyObject : SubDependencyObject + { + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DependencyPropertyTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DependencyPropertyTests.cs new file mode 100644 index 00000000000..815576f19a3 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DependencyPropertyTests.cs @@ -0,0 +1,1224 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using System.Windows.Threading; + +namespace System.Windows.Tests; + +public class DependencyPropertyTests +{ + public static IEnumerable Register_String_Type_Type_TestData() + { + yield return new object?[] { "Register_String_Type_Type_TestData1", typeof(string), typeof(DependencyObjectTests), null }; + yield return new object?[] { "Register_String_Type_Type_TestData2", typeof(int), typeof(DependencyObjectTests), 0 }; + yield return new object?[] { "Register_String_Type_Type_TestData3", typeof(int?), typeof(DependencyObjectTests), null }; + yield return new object?[] { "Register_String_Type_Type_TestData4", typeof(List), typeof(DependencyObjectTests), null }; + yield return new object?[] { "Register_String_Type_Type_TestData5", typeof(Dictionary), typeof(DependencyObjectTests), null }; + yield return new object?[] { "Register_String_Type_Type_TestData6", typeof(object), typeof(DependencyObjectTests), null }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObjectTests), 0 }; + yield return new object?[] { " Register_String_Type_Type_TestData7 ", typeof(int), typeof(DependencyObjectTests), 0 }; + } + + [Theory] + [MemberData(nameof(Register_String_Type_Type_TestData))] + public void Register_InvokeStringTypeType_Success(string name, Type propertyType, Type ownerType, object? expectedDefaultValue) + { + DependencyProperty property = DependencyProperty.Register(name, propertyType, ownerType); + Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Equal(expectedDefaultValue, property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal(name, property.Name); + Assert.Equal(propertyType, property.PropertyType); + Assert.Equal(ownerType, property.OwnerType); + Assert.False(property.ReadOnly); + Assert.Null(property.ValidateValueCallback); + } + + public static IEnumerable Register_String_Type_Type_PropertyMetadata_TestData() + { + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData1", typeof(string), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData3", typeof(string), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData4", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData5", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData6", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData7", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData8", typeof(int), typeof(SubDependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData9", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData10", typeof(int?), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData11", typeof(int?), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData12", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData13", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData14", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData15", typeof(List), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData16", typeof(List), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData17", typeof(List), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData18", typeof(List), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new List() }, new List() }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData19", typeof(Dictionary), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData20", typeof(Dictionary), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData21", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData22", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new Dictionary() }, new Dictionary() }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData23", typeof(object), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData24", typeof(object), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData25", typeof(object), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData26", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData27", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_TestData28", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { " Register_String_Type_Type_PropertyMetadata_TestData29 ", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + } + + [Theory] + [MemberData(nameof(Register_String_Type_Type_PropertyMetadata_TestData))] + public void Register_InvokeStringTypeTypePropertyMetadata_Success(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue) + { + DependencyProperty property = DependencyProperty.Register(name, propertyType, ownerType, typeMetadata); + Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Equal(expectedDefaultValue, property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal(name, property.Name); + Assert.Equal(propertyType, property.PropertyType); + Assert.Equal(ownerType, property.OwnerType); + Assert.False(property.ReadOnly); + Assert.Null(property.ValidateValueCallback); + } + + public static IEnumerable Register_String_Type_Type_Validate_TestData() + { + yield return new object?[] { "Register_String_Type_Type_Validate_TestData1", typeof(string), typeof(DependencyObjectTests), null, null, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData3", typeof(string), typeof(SubDependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData4", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value", 2 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData5", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null, 2 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData6", typeof(int), typeof(DependencyObjectTests), null, 0, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData7", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData8", typeof(int), typeof(SubDependencyObject), new PropertyMetadata(), 0, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData9", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1, 2 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData10", typeof(int?), typeof(DependencyObjectTests), null, null, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData11", typeof(int?), typeof(DependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData12", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData13", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1, 2 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData14", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null, 2 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData15", typeof(List), typeof(DependencyObjectTests), null, null, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData16", typeof(List), typeof(DependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData17", typeof(List), typeof(SubDependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData18", typeof(List), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new List() }, new List(), 2 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData19", typeof(Dictionary), typeof(DependencyObjectTests), null, null, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData20", typeof(Dictionary), typeof(DependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData21", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData22", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new Dictionary() }, new Dictionary(), 2 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData23", typeof(object), typeof(DependencyObjectTests), null, null, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData24", typeof(object), typeof(DependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData25", typeof(object), typeof(SubDependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData26", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value", 2 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData27", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1, 2 }; + yield return new object?[] { "Register_String_Type_Type_Validate_TestData28", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null, 2 }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObjectTests), null, 0, 1 }; + yield return new object?[] { " Register_String_Type_Type_Validate_TestData29 ", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1, 2 }; + } + + [Theory] + [MemberData(nameof(Register_String_Type_Type_Validate_TestData))] + public void Register_InvokeStringTypeTypePropertyMetadataValidate_Success(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue, int expectedValidateValueCallbackCallCount) + { + int callCount = 0; + ValidateValueCallback validateValueCallback = value => + { + Assert.Equal(expectedDefaultValue, value); + callCount++; + return true; + }; + DependencyProperty property = DependencyProperty.Register(name, propertyType, ownerType, typeMetadata, validateValueCallback); + Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Equal(expectedDefaultValue, property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal(name, property.Name); + Assert.Equal(propertyType, property.PropertyType); + Assert.Equal(ownerType, property.OwnerType); + Assert.False(property.ReadOnly); + Assert.Same(validateValueCallback, property.ValidateValueCallback); + Assert.Equal(expectedValidateValueCallbackCallCount, callCount); + } + + public static IEnumerable Register_String_Type_Type_PropertyMetadata_NullValidate_TestData() + { + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData1", typeof(string), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData3", typeof(string), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData4", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData5", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData6", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData7", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData8", typeof(int), typeof(SubDependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData9", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData10", typeof(int?), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData11", typeof(int?), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData12", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData13", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData14", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData15", typeof(List), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData16", typeof(List), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData17", typeof(List), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData18", typeof(List), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new List() }, new List() }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData19", typeof(Dictionary), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData20", typeof(Dictionary), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData21", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData22", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new Dictionary() }, new Dictionary() }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData23", typeof(object), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData24", typeof(object), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData25", typeof(object), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData26", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData27", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "Register_String_Type_Type_PropertyMetadata_NullValidate_TestData28", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { " Register_String_Type_Type_PropertyMetadata_NullValidate_TestData8 ", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + } + + [Theory] + [MemberData(nameof(Register_String_Type_Type_PropertyMetadata_NullValidate_TestData))] + public void Register_InvokeStringTypeTypePropertyMetadataNullValidate_Success(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue) + { + DependencyProperty property = DependencyProperty.Register(name, propertyType, ownerType, typeMetadata, null); + Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Equal(expectedDefaultValue, property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal(name, property.Name); + Assert.Equal(propertyType, property.PropertyType); + Assert.Equal(ownerType, property.OwnerType); + Assert.False(property.ReadOnly); + Assert.Null(property.ValidateValueCallback); + } + + [Fact] + public void Register_Multiple_GlobalIndexDifferent() + { + DependencyProperty property1 = DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name + "1", typeof(int), typeof(DependencyObjectTests)); + DependencyProperty property2 = DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name + "2", typeof(int), typeof(DependencyObjectTests)); + Assert.NotEqual(property1.GlobalIndex, property2.GlobalIndex); + } + + [Fact] + public void Register_NullName_ThrowsArgumentNullException() + { + Assert.Throws("name", () => DependencyProperty.Register(null, typeof(int), typeof(DependencyPropertyTests))); + } + + [Fact] + public void Register_EmptyName_ThrowsArgumentException() + { + Assert.Throws("name", () => DependencyProperty.Register(string.Empty, typeof(int), typeof(DependencyPropertyTests))); + } + + [Fact] + public void Register_NullPropertyType_ThrowsArgumentNullException() + { + Assert.Throws("propertyType", () => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, null!, typeof(DependencyPropertyTests))); + Assert.Throws("propertyType", () => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, null!, typeof(DependencyPropertyTests), new PropertyMetadata())); + Assert.Throws("propertyType", () => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, null!, typeof(DependencyPropertyTests), new PropertyMetadata(), value => true)); + } + + [Fact] + public void Register_InvalidPropertyType_ThrowsNotSupportedException() + { + Assert.Throws(() => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(void), typeof(DependencyPropertyTests))); + Assert.Throws(() => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(void), typeof(DependencyPropertyTests), new PropertyMetadata())); + Assert.Throws(() => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(void), typeof(DependencyPropertyTests), new PropertyMetadata(), value => true)); + } + + [Theory] + [InlineData(typeof(void), "DefaultValue")] + [InlineData(typeof(int), "DefaultValue")] + [InlineData(typeof(string), 1)] + [InlineData(typeof(int), null)] + public void Register_PropertyTypeDoesntMatchMetadata_ThrowsArgumentException(Type propertyType, object? defaultValue) + { + var metadata = new PropertyMetadata(defaultValue); + Assert.Throws(() => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, propertyType, typeof(DependencyPropertyTests), metadata)); + Assert.Throws(() => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, propertyType, typeof(DependencyPropertyTests), metadata, value => true)); + } + + [Fact] + public void Register_NullOwnerType_ThrowsArgumentNullException() + { + Assert.Throws("ownerType", () => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(int), null!)); + Assert.Throws("ownerType", () => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(int), null!, new PropertyMetadata())); + Assert.Throws("ownerType", () => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(int), null!, new PropertyMetadata(), value => true)); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(DependencyPropertyTests))] + public void Register_OwnerTypeNotDependencyObjectWithMetadata_ThrowsArgumentException(Type ownerType) + { + Assert.Throws(() => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(int), ownerType, new PropertyMetadata())); + Assert.Throws(() => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(int), ownerType, new PropertyMetadata { DefaultValue = 1 })); + Assert.Throws(() => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(int), ownerType, new PropertyMetadata(), value => true)); + Assert.Throws(() => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(int), ownerType, new PropertyMetadata { DefaultValue = 1 }, value => true)); + } + + [Fact] + public void Register_ExpressionDefaultValue_ThrowsArgumentException() + { + var metadata = new PropertyMetadata(Activator.CreateInstance(typeof(Expression), nonPublic: true)); + Assert.Throws(() => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata)); + Assert.Throws(() => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata, value => true)); + } + + [Fact] + public void Register_DispatcherObjectDefaultValue_ThrowsArgumentException() + { + var metadata = new PropertyMetadata(new SubDispatcherObject()); + Assert.Throws(() => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata)); + Assert.Throws(() => DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata, value => true)); + } + + [Fact] + public void Register_AlreadyRegistered_ThrowsArgumentException() + { + DependencyProperty property = DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(int), typeof(DependencyPropertyTests)); + Assert.Throws(() => DependencyProperty.Register(property.Name, typeof(int), typeof(DependencyPropertyTests))); + Assert.Throws(() => DependencyProperty.Register(property.Name, typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata())); + Assert.Throws(() => DependencyProperty.Register(property.Name, typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata(), value => true)); + } + + public static IEnumerable Register_String_Type_Type_ValidateFail_TestData() + { + yield return new object?[] { " ", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "Register_String_Type_Type_ValidateFail_TestData1", typeof(string), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "Register_String_Type_Type_ValidateFail_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { " Register_String_Type_Type_ValidateFail_TestData3 ", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { " Register_String_Type_Type_ValidateFail_TestData4 ", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + } + + [Theory] + [MemberData(nameof(Register_String_Type_Type_ValidateFail_TestData))] + public void Register_InvokeStringTypeTypeValidateFail_Throws(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue) + { + int callCount = 0; + ValidateValueCallback validateValueCallback = value => + { + Assert.Equal(expectedDefaultValue, value); + callCount++; + return false; + }; + Assert.Throws(() => DependencyProperty.Register(name, propertyType, ownerType, typeMetadata, validateValueCallback)); + Assert.Equal(1, callCount); + } + + public static IEnumerable RegisterAttached_String_Type_Type_TestData() + { + yield return new object?[] { "RegisterAttached_String_Type_Type_TestData1", typeof(string), typeof(DependencyObjectTests), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_TestData2", typeof(int), typeof(DependencyObjectTests), 0 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_TestData3", typeof(int?), typeof(DependencyObjectTests), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_TestData4", typeof(List), typeof(DependencyObjectTests), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_TestData5", typeof(Dictionary), typeof(DependencyObjectTests), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_TestData6", typeof(object), typeof(DependencyObjectTests), null }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObjectTests), 0 }; + yield return new object?[] { " RegisterAttached_String_Type_Type_TestData7 ", typeof(int), typeof(DependencyObjectTests), 0 }; + } + + [Theory] + [MemberData(nameof(RegisterAttached_String_Type_Type_TestData))] + public void RegisterAttached_InvokeStringTypeType_Success(string name, Type propertyType, Type ownerType, object? expectedDefaultValue) + { + DependencyProperty property = DependencyProperty.RegisterAttached(name, propertyType, ownerType); + Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Equal(expectedDefaultValue, property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal(name, property.Name); + Assert.Equal(propertyType, property.PropertyType); + Assert.Equal(ownerType, property.OwnerType); + Assert.False(property.ReadOnly); + Assert.Null(property.ValidateValueCallback); + } + + public static IEnumerable RegisterAttached_String_Type_Type_PropertyMetadata_TestData() + { + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData1", typeof(string), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData3", typeof(string), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData4", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData5", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData6", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData7", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData8", typeof(int), typeof(SubDependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData9", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData10", typeof(int?), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData11", typeof(int?), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData12", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData13", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData14", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData15", typeof(List), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData16", typeof(List), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData17", typeof(List), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData18", typeof(List), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new List() }, new List() }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData19", typeof(Dictionary), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData20", typeof(Dictionary), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData21", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData22", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new Dictionary() }, new Dictionary() }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData23", typeof(object), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData24", typeof(object), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData25", typeof(object), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData26", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData27", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData28", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { " RegisterAttached_String_Type_Type_PropertyMetadata_TestData29 ", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData30", typeof(int), typeof(int), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_TestData31", typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata { DefaultValue = 1 }, 1 }; + } + + [Theory] + [MemberData(nameof(RegisterAttached_String_Type_Type_PropertyMetadata_TestData))] + public void RegisterAttached_InvokeStringTypeTypePropertyMetadata_Success(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue) + { + DependencyProperty property = DependencyProperty.RegisterAttached(name, propertyType, ownerType, typeMetadata); + Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Equal(expectedDefaultValue, property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal(name, property.Name); + Assert.Equal(propertyType, property.PropertyType); + Assert.Equal(ownerType, property.OwnerType); + Assert.False(property.ReadOnly); + Assert.Null(property.ValidateValueCallback); + } + + public static IEnumerable RegisterAttached_String_Type_Type_Validate_TestData() + { + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData1", typeof(string), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData3", typeof(string), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData4", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData5", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData6", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData7", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData8", typeof(int), typeof(SubDependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData9", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData10", typeof(int?), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData11", typeof(int?), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData12", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData13", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData14", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData15", typeof(List), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData16", typeof(List), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData17", typeof(List), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData18", typeof(List), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new List() }, new List() }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData19", typeof(Dictionary), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData20", typeof(Dictionary), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData21", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData22", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new Dictionary() }, new Dictionary() }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData23", typeof(object), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData24", typeof(object), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData25", typeof(object), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData26", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData27", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_Validate_TestData28", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { " RegisterAttached_String_Type_Type_Validate_TestData29 ", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + } + + [Theory] + [MemberData(nameof(RegisterAttached_String_Type_Type_Validate_TestData))] + public void RegisterAttached_InvokeStringTypeTypePropertyMetadataValidate_Success(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue) + { + int callCount = 0; + ValidateValueCallback validateValueCallback = value => + { + Assert.Equal(expectedDefaultValue, value); + callCount++; + return true; + }; + DependencyProperty property = DependencyProperty.RegisterAttached(name, propertyType, ownerType, typeMetadata, validateValueCallback); + Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Equal(expectedDefaultValue, property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal(name, property.Name); + Assert.Equal(propertyType, property.PropertyType); + Assert.Equal(ownerType, property.OwnerType); + Assert.False(property.ReadOnly); + Assert.Same(validateValueCallback, property.ValidateValueCallback); + Assert.Equal(1, callCount); + } + + public static IEnumerable RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData() + { + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData1", typeof(string), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData3", typeof(string), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData4", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData5", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData6", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData7", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData8", typeof(int), typeof(SubDependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData9", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData10", typeof(int?), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData11", typeof(int?), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData12", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData13", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData14", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData15", typeof(List), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData16", typeof(List), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData17", typeof(List), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData18", typeof(List), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new List() }, new List() }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData19", typeof(Dictionary), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData20", typeof(Dictionary), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData21", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData22", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new Dictionary() }, new Dictionary() }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData23", typeof(object), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData24", typeof(object), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData25", typeof(object), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData26", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData27", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData28", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { " RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData8 ", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + } + + [Theory] + [MemberData(nameof(RegisterAttached_String_Type_Type_PropertyMetadata_NullValidate_TestData))] + public void RegisterAttached_InvokeStringTypeTypePropertyMetadataNullValidate_Success(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue) + { + DependencyProperty property = DependencyProperty.RegisterAttached(name, propertyType, ownerType, typeMetadata, null); + Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Equal(expectedDefaultValue, property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal(name, property.Name); + Assert.Equal(propertyType, property.PropertyType); + Assert.Equal(ownerType, property.OwnerType); + Assert.False(property.ReadOnly); + Assert.Null(property.ValidateValueCallback); + } + + [Fact] + public void RegisterAttached_Multiple_GlobalIndexDifferent() + { + DependencyProperty property1 = DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name + "1", typeof(int), typeof(DependencyObjectTests)); + DependencyProperty property2 = DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name + "2", typeof(int), typeof(DependencyObjectTests)); + Assert.NotEqual(property1.GlobalIndex, property2.GlobalIndex); + } + + [Fact] + public void RegisterAttached_NullName_ThrowsArgumentNullException() + { + Assert.Throws("name", () => DependencyProperty.RegisterAttached(null, typeof(int), typeof(DependencyPropertyTests))); + Assert.Throws("name", () => DependencyProperty.RegisterAttached(null, typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata())); + Assert.Throws("name", () => DependencyProperty.RegisterAttached(null, typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata(), value => true)); + } + + [Fact] + public void RegisterAttached_EmptyName_ThrowsArgumentException() + { + Assert.Throws("name", () => DependencyProperty.RegisterAttached(string.Empty, typeof(int), typeof(DependencyPropertyTests))); + Assert.Throws("name", () => DependencyProperty.RegisterAttached(string.Empty, typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata())); + Assert.Throws("name", () => DependencyProperty.RegisterAttached(string.Empty, typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata(), value => true)); + } + + [Fact] + public void RegisterAttached_NullPropertyType_ThrowsArgumentNullException() + { + Assert.Throws("propertyType", () => DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, null!, typeof(DependencyPropertyTests))); + Assert.Throws("propertyType", () => DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, null!, typeof(DependencyPropertyTests), new PropertyMetadata())); + Assert.Throws("propertyType", () => DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, null!, typeof(DependencyPropertyTests), new PropertyMetadata(), value => true)); + } + + [Fact] + public void RegisterAttached_InvalidPropertyType_ThrowsNotSupportedException() + { + Assert.Throws(() => DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, typeof(void), typeof(DependencyPropertyTests))); + Assert.Throws(() => DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, typeof(void), typeof(DependencyPropertyTests), new PropertyMetadata())); + Assert.Throws(() => DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, typeof(void), typeof(DependencyPropertyTests), new PropertyMetadata(), value => true)); + } + + [Theory] + [InlineData(typeof(void), "DefaultValue")] + [InlineData(typeof(int), "DefaultValue")] + [InlineData(typeof(string), 1)] + [InlineData(typeof(int), null)] + public void RegisterAttached_PropertyTypeDoesntMatchMetadata_ThrowsArgumentException(Type propertyType, object? defaultValue) + { + var metadata = new PropertyMetadata(defaultValue); + Assert.Throws(() => DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, propertyType, typeof(DependencyPropertyTests), metadata)); + Assert.Throws(() => DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, propertyType, typeof(DependencyPropertyTests), metadata, value => true)); + } + + [Fact] + public void RegisterAttached_NullOwnerType_ThrowsArgumentNullException() + { + Assert.Throws("ownerType", () => DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, typeof(int), null!)); + Assert.Throws("ownerType", () => DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, typeof(int), null!, new PropertyMetadata())); + Assert.Throws("ownerType", () => DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, typeof(int), null!, new PropertyMetadata(), value => true)); + } + + [Fact] + public void RegisterAttached_ExpressionDefaultValue_ThrowsArgumentException() + { + var metadata = new PropertyMetadata(Activator.CreateInstance(typeof(Expression), nonPublic: true)); + Assert.Throws(() => DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata)); + Assert.Throws(() => DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata, value => true)); + } + + [Fact] + public void RegisterAttached_DispatcherObjectDefaultValue_ThrowsArgumentException() + { + var metadata = new PropertyMetadata(new SubDispatcherObject()); + Assert.Throws(() => DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata)); + Assert.Throws(() => DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata, value => true)); + } + + [Fact] + public void RegisterAttached_AlreadyRegisterAttacheded_ThrowsArgumentException() + { + DependencyProperty property = DependencyProperty.RegisterAttached(MethodBase.GetCurrentMethod()!.Name, typeof(int), typeof(DependencyPropertyTests)); + Assert.Throws(() => DependencyProperty.RegisterAttached(property.Name, typeof(int), typeof(DependencyPropertyTests))); + Assert.Throws(() => DependencyProperty.RegisterAttached(property.Name, typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata())); + Assert.Throws(() => DependencyProperty.RegisterAttached(property.Name, typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata(), value => true)); + } + + public static IEnumerable RegisterAttached_String_Type_Type_ValidateFail_TestData() + { + yield return new object?[] { " ", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterAttached_String_Type_Type_ValidateFail_TestData1", typeof(string), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttached_String_Type_Type_ValidateFail_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { " RegisterAttached_String_Type_Type_ValidateFail_TestData3 ", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { " RegisterAttached_String_Type_Type_ValidateFail_TestData4 ", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + } + + [Theory] + [MemberData(nameof(RegisterAttached_String_Type_Type_ValidateFail_TestData))] + public void RegisterAttached_InvokeStringTypeTypeValidateFail_Throws(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue) + { + int callCount = 0; + ValidateValueCallback validateValueCallback = value => + { + Assert.Equal(expectedDefaultValue, value); + callCount++; + return false; + }; + Assert.Throws(() => DependencyProperty.RegisterAttached(name, propertyType, ownerType, typeMetadata, validateValueCallback)); + Assert.Equal(1, callCount); + } + + public static IEnumerable RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData() + { + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData1", typeof(string), typeof(DependencyObject), null, null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData3", typeof(string), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData4", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData5", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData6", typeof(int), typeof(DependencyObject), null, 0 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData7", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData8", typeof(int), typeof(SubDependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData9", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData10", typeof(int?), typeof(DependencyObject), null, null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData11", typeof(int?), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData12", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData13", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData14", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData15", typeof(List), typeof(DependencyObject), null, null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData16", typeof(List), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData17", typeof(List), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData18", typeof(List), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new List() }, new List() }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData19", typeof(Dictionary), typeof(DependencyObject), null, null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData20", typeof(Dictionary), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData21", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData22", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new Dictionary() }, new Dictionary() }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData23", typeof(object), typeof(DependencyObject), null, null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData24", typeof(object), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData25", typeof(object), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData26", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData27", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData28", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObject), null, 0 }; + yield return new object?[] { " RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData29 ", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + } + + [Theory] + [MemberData(nameof(RegisterReadOnly_String_Type_Type_PropertyMetadata_TestData))] + public void RegisterReadOnly_InvokeStringTypeTypePropertyMetadata_Success(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue) + { + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(name, propertyType, ownerType, typeMetadata); + Assert.NotNull(key.DependencyProperty); + Assert.Same(key.DependencyProperty, key.DependencyProperty); + + DependencyProperty property = key.DependencyProperty; + Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Equal(expectedDefaultValue, property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal(name, property.Name); + Assert.Equal(propertyType, property.PropertyType); + Assert.Equal(ownerType, property.OwnerType); + Assert.True(property.ReadOnly); + Assert.Null(property.ValidateValueCallback); + } + + public static IEnumerable RegisterReadOnly_String_Type_Type_Validate_TestData() + { + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData1", typeof(string), typeof(DependencyObject), null, null, 4 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData3", typeof(string), typeof(SubDependencyObject), new PropertyMetadata(), null, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData4", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value", 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData5", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData6", typeof(int), typeof(DependencyObject), null, 0, 4 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData7", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData8", typeof(int), typeof(SubDependencyObject), new PropertyMetadata(), 0, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData9", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData10", typeof(int?), typeof(DependencyObject), null, null, 4 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData11", typeof(int?), typeof(DependencyObject), new PropertyMetadata(), null, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData12", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata(), null, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData13", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData14", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData15", typeof(List), typeof(DependencyObject), null, null, 4 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData16", typeof(List), typeof(DependencyObject), new PropertyMetadata(), null, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData17", typeof(List), typeof(SubDependencyObject), new PropertyMetadata(), null, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData18", typeof(List), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new List() }, new List(), 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData19", typeof(Dictionary), typeof(DependencyObject), null, null, 4 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData20", typeof(Dictionary), typeof(DependencyObject), new PropertyMetadata(), null, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData21", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata(), null, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData22", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new Dictionary() }, new Dictionary(), 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData23", typeof(object), typeof(DependencyObject), null, null, 4 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData24", typeof(object), typeof(DependencyObject), new PropertyMetadata(), null, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData25", typeof(object), typeof(SubDependencyObject), new PropertyMetadata(), null, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData26", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value", 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData27", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1, 2 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_Validate_TestData28", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null, 2 }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObject), null, 0, 4 }; + yield return new object?[] { " RegisterReadOnly_String_Type_Type_Validate_TestData29 ", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1, 2 }; + } + + [Theory] + [MemberData(nameof(RegisterReadOnly_String_Type_Type_Validate_TestData))] + public void RegisterReadOnly_InvokeStringTypeTypePropertyMetadataValidate_Success(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue, int expectedValidateValueCallbackCallCount) + { + int callCount = 0; + ValidateValueCallback validateValueCallback = value => + { + Assert.Equal(expectedDefaultValue, value); + callCount++; + return true; + }; + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(name, propertyType, ownerType, typeMetadata, validateValueCallback); + Assert.NotNull(key.DependencyProperty); + Assert.Same(key.DependencyProperty, key.DependencyProperty); + + DependencyProperty property = key.DependencyProperty; + Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Equal(expectedDefaultValue, property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal(name, property.Name); + Assert.Equal(propertyType, property.PropertyType); + Assert.Equal(ownerType, property.OwnerType); + Assert.True(property.ReadOnly); + Assert.Same(validateValueCallback, property.ValidateValueCallback); + Assert.Equal(expectedValidateValueCallbackCallCount, callCount); + } + + public static IEnumerable RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData() + { + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData1", typeof(string), typeof(DependencyObject), null, null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData3", typeof(string), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData4", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData5", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData6", typeof(int), typeof(DependencyObject), null, 0 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData7", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData8", typeof(int), typeof(SubDependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData9", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData10", typeof(int?), typeof(DependencyObject), null, null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData11", typeof(int?), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData12", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData13", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData14", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData15", typeof(List), typeof(DependencyObject), null, null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData16", typeof(List), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData17", typeof(List), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData18", typeof(List), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new List() }, new List() }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData19", typeof(Dictionary), typeof(DependencyObject), null, null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData20", typeof(Dictionary), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData21", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData22", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new Dictionary() }, new Dictionary() }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData23", typeof(object), typeof(DependencyObject), null, null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData24", typeof(object), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData25", typeof(object), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData26", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData27", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData28", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObject), null, 0 }; + yield return new object?[] { " RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData8 ", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + } + + [Theory] + [MemberData(nameof(RegisterReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData))] + public void RegisterReadOnly_InvokeStringTypeTypePropertyMetadataNullValidate_Success(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue) + { + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(name, propertyType, ownerType, typeMetadata, null); + Assert.NotNull(key.DependencyProperty); + Assert.Same(key.DependencyProperty, key.DependencyProperty); + + DependencyProperty property = key.DependencyProperty; + Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Equal(expectedDefaultValue, property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal(name, property.Name); + Assert.Equal(propertyType, property.PropertyType); + Assert.Equal(ownerType, property.OwnerType); + Assert.True(property.ReadOnly); + Assert.Null(property.ValidateValueCallback); + } + + [Fact] + public void RegisterReadOnly_Multiple_GlobalIndexDifferent() + { + DependencyProperty property1 = DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name + "1", typeof(int), typeof(DependencyObject), new PropertyMetadata()).DependencyProperty; + DependencyProperty property2 = DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name + "2", typeof(int), typeof(DependencyObject), new PropertyMetadata()).DependencyProperty; + Assert.NotEqual(property1.GlobalIndex, property2.GlobalIndex); + } + + [Fact] + public void RegisterReadOnly_NullName_ThrowsArgumentNullException() + { + Assert.Throws("name", () => DependencyProperty.RegisterReadOnly(null, typeof(int), typeof(DependencyObject), new PropertyMetadata())); + Assert.Throws("name", () => DependencyProperty.RegisterReadOnly(null, typeof(int), typeof(DependencyObject), new PropertyMetadata(), value => true)); + } + + [Fact] + public void RegisterReadOnly_EmptyName_ThrowsArgumentException() + { + Assert.Throws("name", () => DependencyProperty.RegisterReadOnly(string.Empty, typeof(int), typeof(DependencyObject), new PropertyMetadata())); + Assert.Throws("name", () => DependencyProperty.RegisterReadOnly(string.Empty, typeof(int), typeof(DependencyObject), new PropertyMetadata(), value => true)); + } + + [Fact] + public void RegisterReadOnly_NullPropertyType_ThrowsArgumentNullException() + { + Assert.Throws("propertyType", () => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, null!, typeof(DependencyProperty), new PropertyMetadata())); + Assert.Throws("propertyType", () => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, null!, typeof(DependencyProperty), new PropertyMetadata(), value => true)); + } + + [Fact] + public void RegisterReadOnly_InvalidPropertyType_ThrowsNotSupportedException() + { + Assert.Throws(() => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(void), typeof(DependencyObject), new PropertyMetadata())); + Assert.Throws(() => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(void), typeof(DependencyObject), new PropertyMetadata(), value => true)); + } + + [Theory] + [InlineData(typeof(void), "DefaultValue")] + [InlineData(typeof(int), "DefaultValue")] + [InlineData(typeof(string), 1)] + [InlineData(typeof(int), null)] + public void RegisterReadOnly_PropertyTypeDoesntMatchMetadata_ThrowsArgumentException(Type propertyType, object? defaultValue) + { + var metadata = new PropertyMetadata(defaultValue); + Assert.Throws(() => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, propertyType, typeof(DependencyProperty), metadata)); + Assert.Throws(() => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, propertyType, typeof(DependencyProperty), metadata, value => true)); + } + + [Fact] + public void RegisterReadOnly_NullOwnerType_ThrowsArgumentNullException() + { + Assert.Throws("ownerType", () => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(int), null!, new PropertyMetadata())); + Assert.Throws("ownerType", () => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(int), null!, new PropertyMetadata(), value => true)); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(DependencyPropertyTests))] + public void RegisterReadOnly_OwnerTypeNotDependencyObjectWithMetadata_ThrowsArgumentException(Type ownerType) + { + Assert.Throws(() => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(int), ownerType, null)); + Assert.Throws(() => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(int), ownerType, new PropertyMetadata())); + Assert.Throws(() => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(int), ownerType, new PropertyMetadata { DefaultValue = 1 })); + Assert.Throws(() => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(int), ownerType, null, value => true)); + Assert.Throws(() => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(int), ownerType, new PropertyMetadata(), value => true)); + Assert.Throws(() => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(int), ownerType, new PropertyMetadata { DefaultValue = 1 }, value => true)); + } + + [Fact] + public void RegisterReadOnly_ExpressionDefaultValue_ThrowsArgumentException() + { + var metadata = new PropertyMetadata(Activator.CreateInstance(typeof(Expression), nonPublic: true)); + Assert.Throws(() => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata)); + Assert.Throws(() => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata, value => true)); + } + + [Fact] + public void RegisterReadOnly_DispatcherObjectDefaultValue_ThrowsArgumentException() + { + var metadata = new PropertyMetadata(new SubDispatcherObject()); + Assert.Throws(() => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata)); + Assert.Throws(() => DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata, value => true)); + } + + [Fact] + public void RegisterReadOnly_AlreadyRegistered_ThrowsArgumentException() + { + DependencyProperty property = DependencyProperty.RegisterReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(int), typeof(DependencyObject), new PropertyMetadata()).DependencyProperty; + Assert.Throws(() => DependencyProperty.RegisterReadOnly(property.Name, typeof(int), typeof(DependencyObject), new PropertyMetadata())); + Assert.Throws(() => DependencyProperty.RegisterReadOnly(property.Name, typeof(int), typeof(DependencyObject), new PropertyMetadata(), value => true)); + } + + public static IEnumerable RegisterReadOnly_String_Type_Type_ValidateFail_TestData() + { + yield return new object?[] { " ", typeof(int), typeof(DependencyObject), null, 0 }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_ValidateFail_TestData1", typeof(string), typeof(DependencyObject), null, null }; + yield return new object?[] { "RegisterReadOnly_String_Type_Type_ValidateFail_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { " RegisterReadOnly_String_Type_Type_ValidateFail_TestData3 ", typeof(int), typeof(DependencyObject), null, 0 }; + yield return new object?[] { " RegisterReadOnly_String_Type_Type_ValidateFail_TestData4 ", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + } + + [Theory] + [MemberData(nameof(RegisterReadOnly_String_Type_Type_ValidateFail_TestData))] + public void RegisterReadOnly_InvokeStringTypeTypeValidateFail_Throws(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue) + { + int callCount = 0; + ValidateValueCallback validateValueCallback = value => + { + Assert.Equal(expectedDefaultValue, value); + callCount++; + return false; + }; + Assert.Throws(() => DependencyProperty.RegisterReadOnly(name, propertyType, ownerType, typeMetadata, validateValueCallback)); + Assert.Equal(1, callCount); + } + + public static IEnumerable RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData() + { + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData1", typeof(string), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData3", typeof(string), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData4", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData5", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData6", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData7", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData8", typeof(int), typeof(SubDependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData9", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData10", typeof(int?), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData11", typeof(int?), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData12", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData13", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData14", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData15", typeof(List), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData16", typeof(List), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData17", typeof(List), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData18", typeof(List), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new List() }, new List() }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData19", typeof(Dictionary), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData20", typeof(Dictionary), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData21", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData22", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new Dictionary() }, new Dictionary() }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData23", typeof(object), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData24", typeof(object), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData25", typeof(object), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData26", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData27", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData28", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { " RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData29 ", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData30", typeof(int), typeof(int), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData31", typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata { DefaultValue = 1 }, 1 }; + } + + [Theory] + [MemberData(nameof(RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_TestData))] + public void RegisterAttachedReadOnly_InvokeStringTypeTypePropertyMetadata_Success(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue) + { + DependencyPropertyKey key = DependencyProperty.RegisterAttachedReadOnly(name, propertyType, ownerType, typeMetadata); + Assert.NotNull(key.DependencyProperty); + Assert.Same(key.DependencyProperty, key.DependencyProperty); + + DependencyProperty property = key.DependencyProperty; + Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Equal(expectedDefaultValue, property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal(name, property.Name); + Assert.Equal(propertyType, property.PropertyType); + Assert.Equal(ownerType, property.OwnerType); + Assert.True(property.ReadOnly); + Assert.Null(property.ValidateValueCallback); + } + + public static IEnumerable RegisterAttachedReadOnly_String_Type_Type_Validate_TestData() + { + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData1", typeof(string), typeof(DependencyObjectTests), null, null, 2 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData3", typeof(string), typeof(SubDependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData4", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value", 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData5", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData6", typeof(int), typeof(DependencyObjectTests), null, 0, 2 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData7", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData8", typeof(int), typeof(SubDependencyObject), new PropertyMetadata(), 0, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData9", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData10", typeof(int?), typeof(DependencyObjectTests), null, null, 2 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData11", typeof(int?), typeof(DependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData12", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData13", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData14", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData15", typeof(List), typeof(DependencyObjectTests), null, null, 2 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData16", typeof(List), typeof(DependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData17", typeof(List), typeof(SubDependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData18", typeof(List), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new List() }, new List(), 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData19", typeof(Dictionary), typeof(DependencyObjectTests), null, null, 2 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData20", typeof(Dictionary), typeof(DependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData21", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData22", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new Dictionary() }, new Dictionary(), 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData23", typeof(object), typeof(DependencyObjectTests), null, null, 2 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData24", typeof(object), typeof(DependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData25", typeof(object), typeof(SubDependencyObject), new PropertyMetadata(), null, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData26", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value", 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData27", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_Validate_TestData28", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null, 1 }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObjectTests), null, 0, 2 }; + yield return new object?[] { " RegisterAttachedReadOnly_String_Type_Type_Validate_TestData29 ", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1, 1 }; + } + + [Theory] + [MemberData(nameof(RegisterAttachedReadOnly_String_Type_Type_Validate_TestData))] + public void RegisterAttachedReadOnly_InvokeStringTypeTypePropertyMetadataValidate_Success(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue, int expectedValidateValueCallbackCallCount) + { + int callCount = 0; + ValidateValueCallback validateValueCallback = value => + { + Assert.Equal(expectedDefaultValue, value); + callCount++; + return true; + }; + DependencyPropertyKey key = DependencyProperty.RegisterAttachedReadOnly(name, propertyType, ownerType, typeMetadata, validateValueCallback); + Assert.NotNull(key.DependencyProperty); + Assert.Same(key.DependencyProperty, key.DependencyProperty); + + DependencyProperty property = key.DependencyProperty; + Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Equal(expectedDefaultValue, property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal(name, property.Name); + Assert.Equal(propertyType, property.PropertyType); + Assert.Equal(ownerType, property.OwnerType); + Assert.True(property.ReadOnly); + Assert.Same(validateValueCallback, property.ValidateValueCallback); + Assert.Equal(expectedValidateValueCallbackCallCount, callCount); + } + + public static IEnumerable RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData() + { + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData1", typeof(string), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData3", typeof(string), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData4", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData5", typeof(string), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData6", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData7", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData8", typeof(int), typeof(SubDependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData9", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData10", typeof(int?), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData11", typeof(int?), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData12", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData13", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData14", typeof(int?), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData15", typeof(List), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData16", typeof(List), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData17", typeof(List), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData18", typeof(List), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new List() }, new List() }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData19", typeof(Dictionary), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData20", typeof(Dictionary), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData21", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData22", typeof(Dictionary), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = new Dictionary() }, new Dictionary() }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData23", typeof(object), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData24", typeof(object), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData25", typeof(object), typeof(SubDependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData26", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = "value" }, "value" }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData27", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData28", typeof(object), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = null }, null }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { " RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData8 ", typeof(int), typeof(SubDependencyObject), new PropertyMetadata { DefaultValue = 1 }, 1 }; + } + + [Theory] + [MemberData(nameof(RegisterAttachedReadOnly_String_Type_Type_PropertyMetadata_NullValidate_TestData))] + public void RegisterAttachedReadOnly_InvokeStringTypeTypePropertyMetadataNullValidate_Success(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue) + { + DependencyPropertyKey key = DependencyProperty.RegisterAttachedReadOnly(name, propertyType, ownerType, typeMetadata, null); + Assert.NotNull(key.DependencyProperty); + Assert.Same(key.DependencyProperty, key.DependencyProperty); + + DependencyProperty property = key.DependencyProperty; Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Equal(expectedDefaultValue, property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal(name, property.Name); + Assert.Equal(propertyType, property.PropertyType); + Assert.Equal(ownerType, property.OwnerType); + Assert.True(property.ReadOnly); + Assert.Null(property.ValidateValueCallback); + } + + [Fact] + public void RegisterAttachedReadOnly_Multiple_GlobalIndexDifferent() + { + DependencyProperty property1 = DependencyProperty.RegisterAttachedReadOnly(MethodBase.GetCurrentMethod()!.Name + "1", typeof(int), typeof(DependencyObjectTests), new PropertyMetadata()).DependencyProperty; + DependencyProperty property2 = DependencyProperty.RegisterAttachedReadOnly(MethodBase.GetCurrentMethod()!.Name + "2", typeof(int), typeof(DependencyObjectTests), new PropertyMetadata()).DependencyProperty; + Assert.NotEqual(property1.GlobalIndex, property2.GlobalIndex); + } + + [Fact] + public void RegisterAttachedReadOnly_NullName_ThrowsArgumentNullException() + { + Assert.Throws("name", () => DependencyProperty.RegisterAttachedReadOnly(null, typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata())); + Assert.Throws("name", () => DependencyProperty.RegisterAttachedReadOnly(null, typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata(), value => true)); + } + + [Fact] + public void RegisterAttachedReadOnly_EmptyName_ThrowsArgumentException() + { + Assert.Throws("name", () => DependencyProperty.RegisterAttachedReadOnly(string.Empty, typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata())); + Assert.Throws("name", () => DependencyProperty.RegisterAttachedReadOnly(string.Empty, typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata(), value => true)); + } + + [Fact] + public void RegisterAttachedReadOnly_NullPropertyType_ThrowsArgumentNullException() + { + Assert.Throws("propertyType", () => DependencyProperty.RegisterAttachedReadOnly(MethodBase.GetCurrentMethod()!.Name, null!, typeof(DependencyPropertyTests), new PropertyMetadata())); + Assert.Throws("propertyType", () => DependencyProperty.RegisterAttachedReadOnly(MethodBase.GetCurrentMethod()!.Name, null!, typeof(DependencyPropertyTests), new PropertyMetadata(), value => true)); + } + + [Fact] + public void RegisterAttachedReadOnly_InvalidPropertyType_ThrowsNotSupportedException() + { + Assert.Throws(() => DependencyProperty.RegisterAttachedReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(void), typeof(DependencyPropertyTests), new PropertyMetadata())); + Assert.Throws(() => DependencyProperty.RegisterAttachedReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(void), typeof(DependencyPropertyTests), new PropertyMetadata(), value => true)); + } + + [Theory] + [InlineData(typeof(void), "DefaultValue")] + [InlineData(typeof(int), "DefaultValue")] + [InlineData(typeof(string), 1)] + [InlineData(typeof(int), null)] + public void RegisterAttachedReadOnly_PropertyTypeDoesntMatchMetadata_ThrowsArgumentException(Type propertyType, object? defaultValue) + { + var metadata = new PropertyMetadata(defaultValue); + Assert.Throws(() => DependencyProperty.RegisterAttachedReadOnly(MethodBase.GetCurrentMethod()!.Name, propertyType, typeof(DependencyPropertyTests), metadata)); + Assert.Throws(() => DependencyProperty.RegisterAttachedReadOnly(MethodBase.GetCurrentMethod()!.Name, propertyType, typeof(DependencyPropertyTests), metadata, value => true)); + } + + [Fact] + public void RegisterAttachedReadOnly_NullOwnerType_ThrowsArgumentNullException() + { + Assert.Throws("ownerType", () => DependencyProperty.RegisterAttachedReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(int), null!, new PropertyMetadata())); + Assert.Throws("ownerType", () => DependencyProperty.RegisterAttachedReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(int), null!, new PropertyMetadata(), value => true)); + } + + [Fact] + public void RegisterAttachedReadOnly_ExpressionDefaultValue_ThrowsArgumentException() + { + var metadata = new PropertyMetadata(Activator.CreateInstance(typeof(Expression), nonPublic: true)); + Assert.Throws(() => DependencyProperty.RegisterAttachedReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata)); + Assert.Throws(() => DependencyProperty.RegisterAttachedReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata, value => true)); + } + + [Fact] + public void RegisterAttachedReadOnly_DispatcherObjectDefaultValue_ThrowsArgumentException() + { + var metadata = new PropertyMetadata(new SubDispatcherObject()); + Assert.Throws(() => DependencyProperty.RegisterAttachedReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata)); + Assert.Throws(() => DependencyProperty.RegisterAttachedReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(SubDependencyObject), metadata, value => true)); + } + + [Fact] + public void RegisterAttachedReadOnly_AlreadyRegisterAttacheded_ThrowsArgumentException() + { + DependencyProperty property = DependencyProperty.RegisterAttachedReadOnly(MethodBase.GetCurrentMethod()!.Name, typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata()).DependencyProperty; + Assert.Throws(() => DependencyProperty.RegisterAttachedReadOnly(property.Name, typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata())); + Assert.Throws(() => DependencyProperty.RegisterAttachedReadOnly(property.Name, typeof(int), typeof(DependencyPropertyTests), new PropertyMetadata(), value => true)); + } + + public static IEnumerable RegisterAttachedReadOnly_String_Type_Type_ValidateFail_TestData() + { + yield return new object?[] { " ", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { " ", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_ValidateFail_TestData1", typeof(string), typeof(DependencyObjectTests), null, null }; + yield return new object?[] { "RegisterAttachedReadOnly_String_Type_Type_ValidateFail_TestData2", typeof(string), typeof(DependencyObject), new PropertyMetadata(), null }; + yield return new object?[] { " RegisterAttachedReadOnly_String_Type_Type_ValidateFail_TestData3 ", typeof(int), typeof(DependencyObjectTests), null, 0 }; + yield return new object?[] { " RegisterAttachedReadOnly_String_Type_Type_ValidateFail_TestData4 ", typeof(int), typeof(DependencyObject), new PropertyMetadata(), 0 }; + } + + [Theory] + [MemberData(nameof(RegisterAttachedReadOnly_String_Type_Type_ValidateFail_TestData))] + public void RegisterAttachedReadOnly_InvokeStringTypeTypeValidateFail_Throws(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, object? expectedDefaultValue) + { + int callCount = 0; + ValidateValueCallback validateValueCallback = value => + { + Assert.Equal(expectedDefaultValue, value); + callCount++; + return false; + }; + Assert.Throws(() => DependencyProperty.RegisterAttachedReadOnly(name, propertyType, ownerType, typeMetadata, validateValueCallback)); + Assert.Equal(1, callCount); + } + + [Fact] + public void GetHashCode_Invoke_ReturnsGlobalIndex() + { + DependencyProperty property = DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(int), typeof(DependencyPropertyTests)); + Assert.Equal(property.GlobalIndex, property.GetHashCode()); + } + + [Fact] + public void ToString_Invoke_ReturnsName() + { + DependencyProperty property = DependencyProperty.Register(MethodBase.GetCurrentMethod()!.Name, typeof(int), typeof(DependencyPropertyTests)); + Assert.Equal(property.Name, property.ToString()); + } + + private class SubDependencyObject : DependencyObject + { + } + + private class SubDispatcherObject : DispatcherObject + { + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DesignerSerializationOptionsAttributeTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DesignerSerializationOptionsAttributeTests.cs new file mode 100644 index 00000000000..2dbf3f65fa5 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/DesignerSerializationOptionsAttributeTests.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Windows.Markup; + +namespace System.Windows.Tests; + +public class DesignerSerializationOptionsAttributeTests +{ + [Theory] + [InlineData(DesignerSerializationOptions.SerializeAsAttribute)] + public void Ctor_DesignerSerializationOptions(DesignerSerializationOptions options) + { + var attribute = new DesignerSerializationOptionsAttribute(options); + Assert.Equal(options, attribute.DesignerSerializationOptions); + } + + [Theory] + [InlineData((DesignerSerializationOptions)0)] + [InlineData(DesignerSerializationOptions.SerializeAsAttribute + 1)] + public void Ctor_InvalidOptions_ThrowsInvalidEnumArgumentException(DesignerSerializationOptions options) + { + // TODO: add paramName. + Assert.Throws(() => new DesignerSerializationOptionsAttribute(options)); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/ExpressionConverterTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/ExpressionConverterTests.cs new file mode 100644 index 00000000000..d30fffad396 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/ExpressionConverterTests.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.ComponentModel.Design.Serialization; +using System.Globalization; + +namespace System.Windows.Tests; + +public class ExpressionConverterTests +{ + [Theory] + [InlineData(null)] + [InlineData(typeof(object))] + [InlineData(typeof(string))] + [InlineData(typeof(InstanceDescriptor))] + [InlineData(typeof(Expression))] + public void CanConvertTo_Invoke_ReturnsFalse(Type? destinationType) + { + var converter = new ExpressionConverter(); + Assert.False(converter.CanConvertTo(null, destinationType)); + Assert.False(converter.CanConvertTo(new CustomTypeDescriptorContext(), destinationType)); + } + + public static IEnumerable ConvertTo_TestData() + { + // TODO: this should not throw NullReferenceException + //yield return new object?[] { null, null }; + //yield return new object?[] { string.Empty, null }; + //yield return new object?[] { "value", null }; + //yield return new object?[] { new object(), null }; + + yield return new object?[] { null, typeof(object) }; + yield return new object?[] { string.Empty, typeof(object) }; + yield return new object?[] { "value", typeof(object) }; + yield return new object?[] { new object(), typeof(object) }; + + yield return new object?[] { null, typeof(string) }; + yield return new object?[] { string.Empty, typeof(string) }; + yield return new object?[] { "value", typeof(string) }; + yield return new object?[] { new object(), typeof(string) }; + + yield return new object?[] { null, typeof(Expression) }; + yield return new object?[] { string.Empty, typeof(Expression) }; + yield return new object?[] { "value", typeof(Expression) }; + yield return new object?[] { new object(), typeof(Expression) }; + } + + [Theory] + [MemberData(nameof(ConvertTo_TestData))] + public void ConvertTo_Invoke_ThrowsNotSupportedException(object value, Type destinationType) + { + var converter = new ExpressionConverter(); + Assert.Throws(() => converter.ConvertTo(value, destinationType)); + Assert.Throws(() => converter.ConvertTo(null, null, value, destinationType)); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, destinationType)); + } + + public static IEnumerable ConvertTo_NullDestinationType_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + yield return new object?[] { new object() }; + } + + [Theory] + [MemberData(nameof(ConvertTo_NullDestinationType_TestData))] + public void ConvertTo_NullDestinationType_ThrowsNullReferenceException(object value) + { + // TODO: this should not throw NullReferenceException + var converter = new ExpressionConverter(); + Assert.Throws(() => converter.ConvertTo(value, null!)); + Assert.Throws(() => converter.ConvertTo(null, null, value, null!)); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, null!)); + } + + [Theory] + [InlineData(null)] + [InlineData(typeof(object))] + [InlineData(typeof(string))] + [InlineData(typeof(InstanceDescriptor))] + [InlineData(typeof(Expression))] + public void CanConvertFrom_Invoke_ReturnsFalse(Type? sourceType) + { + var converter = new ExpressionConverter(); + Assert.False(converter.CanConvertFrom(sourceType!)); + Assert.False(converter.CanConvertFrom(null, sourceType)); + Assert.False(converter.CanConvertFrom(new CustomTypeDescriptorContext(), sourceType)); + } + + public static IEnumerable ConvertFrom_CantConvert_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new object() }; + } + + [Theory] + [MemberData(nameof(ConvertFrom_CantConvert_TestData))] + public void ConvertFrom_CantConvert_ThrowsNotSupportedException(object value) + { + var converter = new ExpressionConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + private class CustomTypeDescriptorContext : ITypeDescriptorContext + { + public IContainer Container => throw new NotImplementedException(); + + public object Instance => throw new NotImplementedException(); + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/ExpressionTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/ExpressionTests.cs new file mode 100644 index 00000000000..9b137f04c86 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/ExpressionTests.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; + +namespace System.Windows.Tests; + +public class ExpressionTests +{ + [Fact] + public void TypeConverter_Get_ReturnsExpected() + { + Assert.IsType(TypeDescriptor.GetConverter(typeof(Expression))); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/FreezableTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/FreezableTests.cs new file mode 100644 index 00000000000..b4cae5ca7ce --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/FreezableTests.cs @@ -0,0 +1,7140 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using System.Windows.Threading; + +namespace System.Windows.Tests; + +public class FreezableTests +{ + [Fact] + public void Ctor_Default() + { + var freezable = new SubFreezable(); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void Changed_AddRemove_Success() + { + var freezable = new SubFreezable(); + + int callCount = 0; + EventHandler handler = (s, e) => callCount++; + freezable.Changed += handler; + Assert.Equal(0, callCount); + + freezable.Changed -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + Assert.Throws("handler", () => freezable.Changed -= handler); + Assert.Equal(0, callCount); + + // Add null. + freezable.Changed += null; + Assert.Equal(0, callCount); + + // Remove null. + freezable.Changed -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void Changed_AddRemoveFrozen_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + int callCount = 0; + EventHandler handler = (s, e) => callCount++; + Assert.Throws(() => freezable.Changed += handler); + Assert.Throws(() => freezable.Changed += null); + Assert.Throws(() => freezable.Changed -= handler); + Assert.Throws(() => freezable.Changed -= null); + Assert.Equal(0, callCount); + } + + [Fact] + public void Changed_AddRemoveOnDifferentThread_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + int callCount = 0; + EventHandler handler = (s, e) => callCount++; + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.Changed += handler); + Assert.Throws(() => freezable.Changed += null); + Assert.Throws(() => freezable.Changed -= handler); + Assert.Throws(() => freezable.Changed -= null); + Assert.Equal(0, callCount); + }); + } + + [Fact] + public void Changed_AddRemoveFrozenOnDifferentThread_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + int callCount = 0; + EventHandler handler = (s, e) => callCount++; + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.Changed += handler); + Assert.Throws(() => freezable.Changed += null); + Assert.Throws(() => freezable.Changed -= handler); + Assert.Throws(() => freezable.Changed -= null); + Assert.Equal(0, callCount); + }); + } + + [Fact] + public void CanFreeze_Get_ReturnsTrue() + { + var freezable = new SubFreezable(); + Assert.True(freezable.CanFreeze); + } + + [Fact] + public void CanFreeze_GetWithProperties_ReturnsTrue() + { + var freezable = new SubFreezable(); + freezable.SetValue(SubFreezable.Property1, 10); + freezable.SetValue(SubFreezable.Property2, 20); + freezable.SetCurrentValue(SubFreezable.Property3, 30); + freezable.SetValue(SubFreezable.Property4, 40); + freezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + freezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + freezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + freezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + Assert.True(freezable.CanFreeze); + } + + [Fact] + public void CanFreeze_GetWithUnfreezableProperties_ReturnsFalse() + { + var freezable = new SubFreezable(); + Assert.True(freezable.CanFreeze); + + freezable.SetValue(SubFreezable.Property1, 10); + freezable.SetValue(SubFreezable.Property2, 20); + freezable.SetCurrentValue(SubFreezable.Property3, 30); + freezable.SetValue(SubFreezable.Property4, 40); + freezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + freezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + freezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + freezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + var anotherFreezable4 = new CustomFreezable(); + anotherFreezable4.SetValue(SubFreezable.Property1, 90); + anotherFreezable4.FreezeCoreAction = (checking) => false; + freezable.SetValue(SubFreezable.Property9, anotherFreezable4); + + Assert.False(freezable.CanFreeze); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CanFreeze_GetCustomFreezeCore_ReturnsExpected(bool result) + { + var freezable = new CustomFreezable(); + int callCount = 0; + freezable.FreezeCoreAction = (checking) => + { + Assert.True(checking); + callCount++; + return result; + }; + + // Get once. + Assert.Equal(result, freezable.CanFreeze); + Assert.Equal(1, callCount); + + // get again. + Assert.Equal(result, freezable.CanFreeze); + Assert.Equal(2, callCount); + } + + [Fact] + public void CanFreeze_GetFrozen_ReturnsTrue() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Assert.True(freezable.CanFreeze); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CanFreeze_GetFrozenCustomFreezeCore_ReturnsTrue(bool result) + { + var freezable = new CustomFreezable(); + freezable.Freeze(); + + int callCount = 0; + freezable.FreezeCoreAction = (checking) => + { + callCount++; + return result; + }; + + // Get once. + Assert.True(freezable.CanFreeze); + Assert.Equal(0, callCount); + + // get again. + Assert.True(freezable.CanFreeze); + Assert.Equal(0, callCount); + } + + [Fact] + public void CanFreeze_GetOnDifferentThread_ReturnsTrue() + { + var freezable = new SubFreezable(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.True(freezable.CanFreeze); + }); + } + + [Fact] + public void CanFreeze_GetFrozenOnDifferentThread_ReturnsTrue() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.True(freezable.CanFreeze); + }); + } + + [Fact] + public void DependencyObjectType_Get_ReturnsExpected() + { + var freezable = new SubFreezable(); + DependencyObjectType dependencyObjectType = freezable.DependencyObjectType; + Assert.NotNull(dependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), dependencyObjectType); + } + + [Fact] + public void DependencyObjectType_GetFrozen_ReturnsExpected() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + DependencyObjectType dependencyObjectType = freezable.DependencyObjectType; + Assert.NotNull(dependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), dependencyObjectType); + } + + [Fact] + public void DependencyObjectType_GetOnDifferentThread_ReturnsExpected() + { + var freezable = new SubFreezable(); + Helpers.ExecuteOnDifferentThread(() => + { + DependencyObjectType dependencyObjectType = freezable.DependencyObjectType; + Assert.NotNull(dependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), dependencyObjectType); + }); + } + + [Fact] + public void DependencyObjectType_GetFrozenOnDifferentThread_ReturnsExpected() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + DependencyObjectType dependencyObjectType = freezable.DependencyObjectType; + Assert.NotNull(dependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), dependencyObjectType); + }); + } + + [Fact] + public void Dispatcher_GetFrozen_ReturnsNull() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Assert.Null(freezable.Dispatcher); + } + + [Fact] + public void Dispatcher_Get_ReturnsExpected() + { + var freezable = new SubFreezable(); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + } + + [Fact] + public void Dispatcher_GetOnDifferentThread_ReturnsExpected() + { + var freezable = new SubFreezable(); + Dispatcher expected = Dispatcher.CurrentDispatcher; + Helpers.ExecuteOnDifferentThread(() => + { + Dispatcher? dispatcher = freezable.Dispatcher; + Assert.NotNull(dispatcher); + Assert.Same(expected, dispatcher); + }); + } + + [Fact] + public void Dispatcher_GetFrozenOnDifferentThread_ReturnsNull() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Null(freezable.Dispatcher); + }); + } + + [Fact] + public void IsFrozen_Get_ReturnsFalse() + { + var freezable = new SubFreezable(); + Assert.False(freezable.IsFrozen); + } + + [Fact] + public void IsFrozen_GetFrozen_ReturnsTrue() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Assert.True(freezable.IsFrozen); + } + + [Fact] + public void IsFrozen_GetOnDifferentThread_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.IsFrozen); + }); + } + + [Fact] + public void IsFrozen_GetFrozenOnDifferentThread_ReturnsTrue() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.True(freezable.IsFrozen); + }); + } + + [Fact] + public void IsSealed_Get_ReturnsFalse() + { + var freezable = new SubFreezable(); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void IsSealed_GetFrozen_ReturnsTrue() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Assert.True(freezable.IsSealed); + } + + [Fact] + public void IsSealed_GetOnDifferentThread_ReturnsFalse() + { + var freezable = new SubFreezable(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.False(freezable.IsSealed); + }); + } + + [Fact] + public void IsSealed_GetFrozenOnDifferentThread_ReturnsTrue() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.True(freezable.IsSealed); + }); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyValueType_GetValueReturnsExpected() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + + // Clear default. + obj.ClearValue(property); + Assert.False((bool)obj.GetValue(property)); + + // Set custom. + obj.SetValue(property, true); + Assert.True((bool)obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(property); + Assert.False((bool)obj.GetValue(property)); + + // Clear again. + obj.ClearValue(property); + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyNullable_GetValueReturnsExpected() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool?), typeof(DependencyObject)); + + // Clear default. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(property, true); + Assert.True((bool)obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + + // Clear again. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyReferenceType_GetValueReturnsExpected() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + + // Clear default. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + + // Clear again. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyCustomDefaultValue_GetValueReturnsExpected() + { + var obj = new SubFreezable(); + var typeMetadata = new PropertyMetadata("default"); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + + // Clear default. + obj.ClearValue(property); + Assert.Equal("default", obj.GetValue(property)); + + // Set custom. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(property); + Assert.Equal("default", obj.GetValue(property)); + + // Clear again. + obj.ClearValue(property); + Assert.Equal("default", obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyCustomPropertyChangedCallback_Success() + { + var obj = new SubFreezable(); + + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata(null, (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Clear default. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + + // Clear custom. + expectedNewValue = null; + expectedOldValue = "value"; + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + + // Clear again. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyCustomCoerceValueCallback_Success() + { + var obj = new SubFreezable(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + }); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Clear default. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, coerceValueCallCount); + + // Set custom. + expectedNewValue = "value"; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Clear custom. + expectedNewValue = null; + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Clear again. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyCustomPropertyChangedAndCoerceValueCallback_Success() + { + var obj = new SubFreezable(); + + int coerceValueCallCount = 0; + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + }); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Clear default. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + Assert.Equal(0, coerceValueCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + + // Clear custom. + expectedNewValue = null; + expectedOldValue = "value"; + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + + // Clear again. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyCustomOnPropertyChanged_CallsOnPropertyChanged() + { + var obj = new CustomFreezable(); + + int onPropertyChangedCallCount = 0; + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + obj.OnPropertyChangedAction = (e) => + { + Assert.True(changedCallCount > onPropertyChangedCallCount); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + onPropertyChangedCallCount++; + }; + var typeMetadata = new PropertyMetadata(null, (d, e) => + { + Assert.Equal(changedCallCount, onPropertyChangedCallCount); + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Clear default. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + Assert.Equal(0, onPropertyChangedCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(1, onPropertyChangedCallCount); + + // Clear custom. + expectedNewValue = null; + expectedOldValue = "value"; + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(2, onPropertyChangedCallCount); + + // Clear again. + obj.ClearValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(2, onPropertyChangedCallCount); + } + + [Fact] + public void ClearValue_InvokeWithHandler_CallsChanged() + { + var freezable = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + + int callCount = 0; + EventHandler handler = (sender, e) => + { + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + callCount++; + }; + freezable.Changed += handler; + + // Clear. + freezable.ClearValue(property); + Assert.False((bool)freezable.GetValue(property)); + Assert.Equal(0, callCount); + + // Set. + freezable.SetValue(property, true); + Assert.True((bool)freezable.GetValue(property)); + Assert.Equal(1, callCount); + + // Clear. + freezable.ClearValue(property); + Assert.False((bool)freezable.GetValue(property)); + Assert.Equal(2, callCount); + + // Remove handler. + freezable.Changed -= handler; + freezable.SetValue(property, true); + Assert.True((bool)freezable.GetValue(property)); + Assert.Equal(2, callCount); + + freezable.ClearValue(property); + Assert.False((bool)freezable.GetValue(property)); + Assert.Equal(2, callCount); + } + + [Fact] + public void ClearValue_InvokeWithCustomOnChanged_CallsChanged() + { + var freezable = new CustomFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + + int onChangedCallCount = 0; + int changedCallCount = 0; + freezable.OnChangedAction = () => + { + Assert.Equal(changedCallCount, onChangedCallCount); + onChangedCallCount++; + }; + freezable.Changed += (sender, e) => + { + Assert.True(onChangedCallCount > changedCallCount); + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + changedCallCount++; + }; + + // Clear. + freezable.ClearValue(property); + Assert.False((bool)freezable.GetValue(property)); + Assert.Equal(0, onChangedCallCount); + Assert.Equal(0, changedCallCount); + + // Set. + freezable.SetValue(property, true); + Assert.True((bool)freezable.GetValue(property)); + Assert.Equal(1, onChangedCallCount); + Assert.Equal(1, changedCallCount); + + // Clear. + freezable.ClearValue(property); + Assert.False((bool)freezable.GetValue(property)); + Assert.Equal(2, onChangedCallCount); + Assert.Equal(2, changedCallCount); + + // Clear again. + freezable.ClearValue(property); + Assert.False((bool)freezable.GetValue(property)); + Assert.Equal(2, onChangedCallCount); + Assert.Equal(2, changedCallCount); + } + + [Fact] + public void ClearValue_DependencyPropertyNullDp_ThrowsArgumentNullException() + { + var obj = new SubFreezable(); + Assert.Throws("dp", () => obj.ClearValue((DependencyProperty)null!)); + } + + [Fact] + public void ClearValue_DependencyPropertyReadOnly_ThrowsInvalidOperationException() + { + var obj = new SubFreezable(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + Assert.Throws(() => obj.ClearValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyFrozen_ThrowsInvalidOperationException() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + obj.Freeze(); + + Assert.Throws(() => obj.ClearValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyOnDifferentThread_ThrowsInvalidOperationException() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => obj.ClearValue(property)); + }); + } + + [Fact] + public void ClearValue_InvokePropertyFrozenOnDifferentThread_ThrowsInvalidOperationException() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + obj.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => obj.ClearValue(property)); + }); + } + + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyValueType_GetValueReturnsExpected() + { + var obj = new SubFreezable(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + // Clear default. + obj.ClearValue(key); + Assert.False((bool)obj.GetValue(property)); + + // Set custom. + obj.SetValue(key, true); + Assert.True((bool)obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(key); + Assert.False((bool)obj.GetValue(property)); + + // Clear again. + obj.ClearValue(key); + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyNullable_GetValueReturnsExpected() + { + var obj = new SubFreezable(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool?), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + // Clear default. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(key, true); + Assert.True((bool)obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + + // Clear again. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyReferenceType_GetValueReturnsExpected() + { + var obj = new SubFreezable(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + // Clear default. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + + // Clear again. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyObject_GetValueReturnsExpected() + { + var obj = new SubFreezable(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + // Clear default. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + + // Clear again. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyCustomDefaultValue_GetValueReturnsExpected() + { + var obj = new SubFreezable(); + var typeMetadata = new PropertyMetadata("default"); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + DependencyProperty property = key.DependencyProperty; + + // Clear default. + obj.ClearValue(key); + Assert.Equal("default", obj.GetValue(property)); + + // Set custom. + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Clear custom. + obj.ClearValue(key); + Assert.Equal("default", obj.GetValue(property)); + + // Clear again. + obj.ClearValue(key); + Assert.Equal("default", obj.GetValue(property)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyCustomPropertyChangedCallback_Success() + { + var obj = new SubFreezable(); + + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata(null, (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + DependencyProperty property = key.DependencyProperty; + expectedProperty = property; + + // Clear default. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + + // Clear custom. + expectedNewValue = null; + expectedOldValue = "value"; + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + + // Clear again. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyCustomCoerceValueCallback_Success() + { + var obj = new SubFreezable(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + }); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + DependencyProperty property = key.DependencyProperty; + expectedProperty = property; + + // Clear default. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, coerceValueCallCount); + + // Set custom. + expectedNewValue = "value"; + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Clear custom. + expectedNewValue = null; + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Clear again. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyCustomPropertyChangedAndCoerceValueCallback_Success() + { + var obj = new SubFreezable(); + + int coerceValueCallCount = 0; + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + }); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + DependencyProperty property = key.DependencyProperty; + expectedProperty = property; + + // Clear default. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + Assert.Equal(0, coerceValueCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + + // Clear custom. + expectedNewValue = null; + expectedOldValue = "value"; + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + + // Clear again. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyCustomOnPropertyChanged_CallsOnPropertyChanged() + { + var obj = new CustomFreezable(); + + int onPropertyChangedCallCount = 0; + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + obj.OnPropertyChangedAction = (e) => + { + Assert.True(changedCallCount > onPropertyChangedCallCount); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + onPropertyChangedCallCount++; + }; + var typeMetadata = new PropertyMetadata(null, (d, e) => + { + Assert.Equal(changedCallCount, onPropertyChangedCallCount); + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + DependencyProperty property = key.DependencyProperty; + expectedProperty = property; + + // Clear default. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + Assert.Equal(0, onPropertyChangedCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(key, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(1, onPropertyChangedCallCount); + + // Clear custom. + expectedNewValue = null; + expectedOldValue = "value"; + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(2, onPropertyChangedCallCount); + + // Clear again. + obj.ClearValue(key); + Assert.Null(obj.GetValue(property)); + Assert.Equal(2, changedCallCount); + Assert.Equal(2, onPropertyChangedCallCount); + } + + [Fact] + public void ClearValue_InvokePropertyKeyWithHandler_CallsChanged() + { + var freezable = new SubFreezable(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + + int callCount = 0; + EventHandler handler = (sender, e) => + { + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + callCount++; + }; + freezable.Changed += handler; + + // Clear. + freezable.ClearValue(key); + Assert.False((bool)freezable.GetValue(key.DependencyProperty)); + Assert.Equal(0, callCount); + + // Set. + freezable.SetValue(key, true); + Assert.True((bool)freezable.GetValue(key.DependencyProperty)); + Assert.Equal(1, callCount); + + // Clear. + freezable.ClearValue(key); + Assert.False((bool)freezable.GetValue(key.DependencyProperty)); + Assert.Equal(2, callCount); + + // Remove handler. + freezable.Changed -= handler; + freezable.SetValue(key, true); + Assert.True((bool)freezable.GetValue(key.DependencyProperty)); + Assert.Equal(2, callCount); + + freezable.ClearValue(key); + Assert.False((bool)freezable.GetValue(key.DependencyProperty)); + Assert.Equal(2, callCount); + } + + [Fact] + public void ClearValue_InvokePropertyKeyWithCustomOnChanged_CallsChanged() + { + var freezable = new CustomFreezable(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + + int onChangedCallCount = 0; + int changedCallCount = 0; + freezable.OnChangedAction = () => + { + Assert.Equal(changedCallCount, onChangedCallCount); + onChangedCallCount++; + }; + freezable.Changed += (sender, e) => + { + Assert.True(onChangedCallCount > changedCallCount); + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + changedCallCount++; + }; + + // Clear. + freezable.ClearValue(key); + Assert.False((bool)freezable.GetValue(key.DependencyProperty)); + Assert.Equal(0, onChangedCallCount); + Assert.Equal(0, changedCallCount); + + // Set. + freezable.SetValue(key, true); + Assert.True((bool)freezable.GetValue(key.DependencyProperty)); + Assert.Equal(1, onChangedCallCount); + Assert.Equal(1, changedCallCount); + + // Clear. + freezable.ClearValue(key); + Assert.False((bool)freezable.GetValue(key.DependencyProperty)); + Assert.Equal(2, onChangedCallCount); + Assert.Equal(2, changedCallCount); + + // Clear again. + freezable.ClearValue(key); + Assert.False((bool)freezable.GetValue(key.DependencyProperty)); + Assert.Equal(2, onChangedCallCount); + Assert.Equal(2, changedCallCount); + } + + [Fact] + public void ClearValue_DependencyPropertyNullKey_ThrowsArgumentNullException() + { + var obj = new SubFreezable(); + Assert.Throws("key", () => obj.ClearValue((DependencyPropertyKey)null!)); + } + + [Fact] + public void ClearValue_InvokeFrozen_ThrowsInvalidOperationException() + { + var obj = new SubFreezable(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata(null)); + obj.Freeze(); + + Assert.Throws(() => obj.ClearValue(key)); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyOnDifferentThread_ThrowsInvalidOperationException() + { + var obj = new SubFreezable(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => obj.ClearValue(key)); + }); + } + + [Fact] + public void ClearValue_InvokeDependencyPropertyKeyFrozenOnDifferentThread_ThrowsInvalidOperationException() + { + var obj = new SubFreezable(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata(null)); + obj.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => obj.ClearValue(key)); + }); + } + + [Fact] + public void Clone_InvokeDefault_Success() + { + var freezable = new SubFreezable(); + int createInstanceCallCount = 0; + freezable.CreateInstanceCoreAction = () => + { + createInstanceCallCount++; + return new SubFreezable(); + }; + + // Clone. + SubFreezable clone = Assert.IsType(freezable.Clone()); + Assert.NotSame(freezable, clone); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.NotNull(clone.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone.Dispatcher); + Assert.False(clone.IsFrozen); + Assert.False(clone.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.Equal(1, createInstanceCallCount); + + // Clone again. + SubFreezable clone2 = Assert.IsType(freezable.Clone()); + Assert.NotSame(freezable, clone); + Assert.NotSame(clone, clone2); + Assert.True(clone2.CanFreeze); + Assert.NotNull(clone2.DependencyObjectType); + Assert.Same(clone2.DependencyObjectType, clone2.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone2.DependencyObjectType); + Assert.NotNull(clone2.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone2.Dispatcher); + Assert.False(clone2.IsFrozen); + Assert.False(clone2.IsSealed); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.NotNull(clone.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone.Dispatcher); + Assert.False(clone.IsFrozen); + Assert.False(clone.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.Equal(2, createInstanceCallCount); + } + + [Fact] + public void Clone_InvokeWithProperties_Success() + { + var freezable = new SubFreezable(); + int createInstanceCallCount = 0; + freezable.CreateInstanceCoreAction = () => + { + createInstanceCallCount++; + return new SubFreezable(); + }; + + freezable.SetValue(SubFreezable.Property1, 10); + freezable.SetValue(SubFreezable.Property2, 20); + freezable.SetCurrentValue(SubFreezable.Property3, 30); + freezable.SetValue(SubFreezable.Property4, 40); + freezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + freezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + freezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + freezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + // Clone. + SubFreezable clone = Assert.IsType(freezable.Clone()); + Assert.NotSame(freezable, clone); + Assert.Equal(10, clone.GetValue(SubFreezable.Property1)); + Assert.Equal(20, clone.GetValue(SubFreezable.Property2)); + Assert.Equal(30, clone.GetValue(SubFreezable.Property3)); + Assert.Equal(40, clone.GetValue(SubFreezable.Property4)); + Assert.Equal(50, clone.GetValue(SubFreezable.Property5)); + Assert.NotSame(anotherFreezable1, ((SubFreezable)clone.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)clone.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)clone.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.NotSame(anotherFreezable2, ((SubFreezable)clone.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)clone.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)clone.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.NotSame(anotherFreezable3, ((SubFreezable)clone.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)clone.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)clone.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.NotNull(clone.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone.Dispatcher); + Assert.False(clone.IsFrozen); + Assert.False(clone.IsSealed); + } + + [Fact] + public void Clone_InvokeWithUnfreezableProperties_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => new SubFreezable(); + freezable.SetValue(SubFreezable.Property1, 10); + freezable.SetValue(SubFreezable.Property2, 20); + freezable.SetCurrentValue(SubFreezable.Property3, 30); + freezable.SetValue(SubFreezable.Property4, 40); + freezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + freezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + freezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + freezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + var anotherFreezable4 = new CustomFreezable(); + anotherFreezable4.CreateInstanceCoreAction = () => new CustomFreezable(); + anotherFreezable4.SetValue(SubFreezable.Property1, 90); + anotherFreezable4.FreezeCoreAction = (isChecking) => false; + freezable.SetValue(SubFreezable.Property9, anotherFreezable4); + + SubFreezable clone = Assert.IsType(freezable.Clone()); + Assert.NotSame(freezable, clone); + Assert.Equal(10, clone.GetValue(SubFreezable.Property1)); + Assert.Equal(20, clone.GetValue(SubFreezable.Property2)); + Assert.Equal(30, clone.GetValue(SubFreezable.Property3)); + Assert.Equal(40, clone.GetValue(SubFreezable.Property4)); + Assert.Equal(50, clone.GetValue(SubFreezable.Property5)); + Assert.NotSame(anotherFreezable1, ((SubFreezable)clone.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)clone.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)clone.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.NotSame(anotherFreezable2, ((SubFreezable)clone.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)clone.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)clone.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.NotSame(anotherFreezable3, ((SubFreezable)clone.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)clone.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)clone.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.NotNull(clone.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone.Dispatcher); + Assert.False(clone.IsFrozen); + Assert.False(clone.IsSealed); + } + + [Fact] + public void Clone_InvokeWithReadOnlyProperty_DoesNotCopy() + { + var freezable = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => new SubFreezable(); + DependencyPropertyKey propertyKey = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + freezable.SetValue(propertyKey, "value"); + + // Clone. + SubFreezable clone = Assert.IsType(freezable.Clone()); + Assert.Null(clone.GetValue(propertyKey.DependencyProperty)); + Assert.Equal("value", freezable.GetValue(propertyKey.DependencyProperty)); + } + + [Fact] + public void Clone_InvokeFrozen_ReturnsExpected() + { + var freezable = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => new SubFreezable(); + freezable.Freeze(); + + SubFreezable clone = Assert.IsType(freezable.Clone()); + Assert.NotSame(freezable, clone); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.NotNull(clone.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone.Dispatcher); + Assert.False(clone.IsFrozen); + Assert.False(clone.IsSealed); + } + + [Fact] + public void Clone_InvokeNullCreateInstanceCore_ThrowsNullReferenceException() + { + var freezable = new CustomFreezable(); + freezable.CreateInstanceCoreAction = () => null!; + Assert.Throws(() => freezable.Clone()); + } + + [Fact] + public void Clone_InvokeDifferentCreateInstanceCore_Success() + { + var freezable = new CustomFreezable(); + var clone = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => clone; + Assert.Same(clone, freezable.Clone()); + Assert.False(freezable.IsFrozen); + Assert.False(clone.IsFrozen); + } + + [Fact] + public void Clone_InvokeSameCreateInstanceCore_Success() + { + var freezable = new CustomFreezable(); + freezable.CreateInstanceCoreAction = () => freezable; + Assert.Same(freezable, freezable.Clone()); + Assert.False(freezable.IsFrozen); + } + + [Fact] + public void Clone_InvokeFrozenCreateInstanceCore_Success() + { + var freezable = new CustomFreezable(); + var clone = new CustomFreezable(); + clone.Freeze(); + freezable.CreateInstanceCoreAction = () => clone; + Assert.Same(clone, freezable.Clone()); + Assert.False(freezable.IsFrozen); + Assert.True(clone.IsFrozen); + } + + [Fact] + public void Clone_InvokeCantFreeze_ReturnsExpected() + { + var freezable = new CustomFreezable(); + var clone = new CustomFreezable(); + freezable.CreateInstanceCoreAction = () => clone; + int callCount = 0; + clone.FreezeCoreAction = (checking) => + { + Assert.True(checking); + callCount++; + return false; + }; + + Assert.Same(clone, freezable.Clone()); + Assert.NotSame(freezable, clone); + Assert.False(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(CustomFreezable)), clone.DependencyObjectType); + Assert.NotNull(clone.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone.Dispatcher); + Assert.False(clone.IsFrozen); + Assert.False(clone.IsSealed); + } + + [Fact] + public void Clone_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.Clone()); + }); + } + + [Fact] + public void Clone_InvokeFrozenOnDifferentThread_ReturnsExpected() + { + var freezable = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + SubFreezable clone = Assert.IsType(freezable.Clone()); + Assert.NotSame(freezable, clone); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.NotNull(clone.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone.Dispatcher); + Assert.False(clone.IsFrozen); + Assert.False(clone.IsSealed); + }); + } + + [Fact] + public void CloneCore_InvokeDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + // Clone. + freezable.CloneCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.CloneCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCore_InvokeWithProperties_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + // Clone. + freezable.CloneCore(sourceFreezable); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.NotSame(anotherFreezable1, ((SubFreezable)freezable.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.NotSame(anotherFreezable2, ((SubFreezable)freezable.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.NotSame(anotherFreezable3, ((SubFreezable)freezable.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)freezable.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCore_InvokeWithReadOnlyProperty_DoesNotCopy() + { + var freezable = new SubFreezable(); + var sourceFreezable = new SubFreezable(); + DependencyPropertyKey propertyKey = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + sourceFreezable.SetValue(propertyKey, "value"); + + // Clone. + freezable.CloneCore(sourceFreezable); + Assert.Null(freezable.GetValue(propertyKey.DependencyProperty)); + Assert.Equal("value", sourceFreezable.GetValue(propertyKey.DependencyProperty)); + } + + [Fact] + public void CloneCore_InvokeSourceFrozenDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + sourceFreezable.Freeze(); + + // Clone. + freezable.CloneCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.CloneCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCore_InvokeSourceFrozenWithProperties_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + sourceFreezable.Freeze(); + + // Clone. + freezable.CloneCore(sourceFreezable); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.NotSame(anotherFreezable1, ((SubFreezable)freezable.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.NotSame(anotherFreezable2, ((SubFreezable)freezable.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.NotSame(anotherFreezable3, ((SubFreezable)freezable.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)freezable.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCore_InvokeDestinationFrozenDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + freezable.Freeze(); + + // Clone. + freezable.CloneCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + + // Clone again. + freezable.CloneCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void CloneCore_InvokeDestinationFrozenWithProperties_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + freezable.Freeze(); + + // Invoke. + Assert.Throws(() => freezable.CloneCore(sourceFreezable)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.Null(freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void CloneCore_InvokeOnDifferentThreadDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + Helpers.ExecuteOnDifferentThread(() => + { + freezable.CloneCore(sourceFreezable); + }); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.CloneCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCore_InvokeOnDifferentThreadWithProperties_ThrowsInvalidOperationException() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + // Invoke. + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.CloneCore(sourceFreezable)); + }); + + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCore_InvokeSourceFrozenOnDifferentThreadDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + sourceFreezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + freezable.CloneCore(sourceFreezable); + }); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.CloneCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCore_InvokeSourceFrozenOnDifferentThreadWithProperties_ThrowsInvalidOperationException() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + sourceFreezable.Freeze(); + + // Invoke. + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.CloneCore(sourceFreezable)); + }); + + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCore_InvokeDestinationFrozenOnDifferentThreadDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + freezable.CloneCore(sourceFreezable); + }); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + + // Clone again. + freezable.CloneCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void CloneCore_InvokeDestinationFrozenOnDifferentThreadWithProperties_ThrowsInvalidOperationException() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + freezable.Freeze(); + + // Invoke. + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.CloneCore(sourceFreezable)); + }); + + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.Null(freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void CloneCore_NullSourceFreezable_ThrowsArgumentNullException() + { + var freezable = new SubFreezable(); + // TODO: this should not throw NullReferenceException. + //Assert.Throws("sourceFreezable", () => freezable.CloneCore(null!)); + Assert.Throws(() => freezable.CloneCore(null!)); + } + + [Fact] + public void CloneCurrentValue_InvokeDefault_Success() + { + var freezable = new SubFreezable(); + int createInstanceCallCount = 0; + freezable.CreateInstanceCoreAction = () => + { + createInstanceCallCount++; + return new SubFreezable(); + }; + + // Clone. + SubFreezable clone = Assert.IsType(freezable.CloneCurrentValue()); + Assert.NotSame(freezable, clone); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.NotNull(clone.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone.Dispatcher); + Assert.False(clone.IsFrozen); + Assert.False(clone.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.Equal(1, createInstanceCallCount); + + // Clone again. + SubFreezable clone2 = Assert.IsType(freezable.CloneCurrentValue()); + Assert.NotSame(freezable, clone); + Assert.NotSame(clone, clone2); + Assert.True(clone2.CanFreeze); + Assert.NotNull(clone2.DependencyObjectType); + Assert.Same(clone2.DependencyObjectType, clone2.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone2.DependencyObjectType); + Assert.NotNull(clone2.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone2.Dispatcher); + Assert.False(clone2.IsFrozen); + Assert.False(clone2.IsSealed); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.NotNull(clone.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone.Dispatcher); + Assert.False(clone.IsFrozen); + Assert.False(clone.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.Equal(2, createInstanceCallCount); + } + + [Fact] + public void CloneCurrentValue_InvokeWithProperties_Success() + { + var freezable = new SubFreezable(); + int createInstanceCallCount = 0; + freezable.CreateInstanceCoreAction = () => + { + createInstanceCallCount++; + return new SubFreezable(); + }; + + freezable.SetValue(SubFreezable.Property1, 10); + freezable.SetValue(SubFreezable.Property2, 20); + freezable.SetCurrentValue(SubFreezable.Property3, 30); + freezable.SetValue(SubFreezable.Property4, 40); + freezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + freezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + freezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + freezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + // Clone. + SubFreezable clone = Assert.IsType(freezable.CloneCurrentValue()); + Assert.NotSame(freezable, clone); + Assert.Equal(10, clone.GetValue(SubFreezable.Property1)); + Assert.Equal(20, clone.GetValue(SubFreezable.Property2)); + Assert.Equal(30, clone.GetValue(SubFreezable.Property3)); + Assert.Equal(40, clone.GetValue(SubFreezable.Property4)); + Assert.Equal(50, clone.GetValue(SubFreezable.Property5)); + Assert.NotSame(anotherFreezable1, ((SubFreezable)clone.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)clone.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)clone.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.NotSame(anotherFreezable2, ((SubFreezable)clone.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)clone.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)clone.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.NotSame(anotherFreezable3, ((SubFreezable)clone.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)clone.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)clone.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.NotNull(clone.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone.Dispatcher); + Assert.False(clone.IsFrozen); + Assert.False(clone.IsSealed); + } + + [Fact] + public void CloneCurrentValue_InvokeWithUnfreezableProperties_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => new SubFreezable(); + freezable.SetValue(SubFreezable.Property1, 10); + freezable.SetValue(SubFreezable.Property2, 20); + freezable.SetCurrentValue(SubFreezable.Property3, 30); + freezable.SetValue(SubFreezable.Property4, 40); + freezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + freezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + freezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + freezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + var anotherFreezable4 = new CustomFreezable(); + anotherFreezable4.CreateInstanceCoreAction = () => new CustomFreezable(); + anotherFreezable4.SetValue(SubFreezable.Property1, 90); + anotherFreezable4.FreezeCoreAction = (isChecking) => false; + freezable.SetValue(SubFreezable.Property9, anotherFreezable4); + + SubFreezable clone = Assert.IsType(freezable.CloneCurrentValue()); + Assert.NotSame(freezable, clone); + Assert.Equal(10, clone.GetValue(SubFreezable.Property1)); + Assert.Equal(20, clone.GetValue(SubFreezable.Property2)); + Assert.Equal(30, clone.GetValue(SubFreezable.Property3)); + Assert.Equal(40, clone.GetValue(SubFreezable.Property4)); + Assert.Equal(50, clone.GetValue(SubFreezable.Property5)); + Assert.NotSame(anotherFreezable1, ((SubFreezable)clone.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)clone.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)clone.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.NotSame(anotherFreezable2, ((SubFreezable)clone.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)clone.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)clone.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.NotSame(anotherFreezable3, ((SubFreezable)clone.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)clone.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)clone.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.NotNull(clone.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone.Dispatcher); + Assert.False(clone.IsFrozen); + Assert.False(clone.IsSealed); + } + + [Fact] + public void CloneCurrentValue_InvokeWithReadOnlyProperty_DoesNotCopy() + { + var freezable = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => new SubFreezable(); + DependencyPropertyKey propertyKey = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + freezable.SetValue(propertyKey, "value"); + + // CloneCurrentValue. + SubFreezable clone = Assert.IsType(freezable.CloneCurrentValue()); + Assert.Null(clone.GetValue(propertyKey.DependencyProperty)); + Assert.Equal("value", freezable.GetValue(propertyKey.DependencyProperty)); + } + + [Fact] + public void CloneCurrentValue_InvokeFrozen_ReturnsExpected() + { + var freezable = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => new SubFreezable(); + freezable.Freeze(); + + SubFreezable clone = Assert.IsType(freezable.CloneCurrentValue()); + Assert.NotSame(freezable, clone); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.NotNull(clone.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone.Dispatcher); + Assert.False(clone.IsFrozen); + Assert.False(clone.IsSealed); + } + + [Fact] + public void CloneCurrentValue_InvokeNullCreateInstanceCore_ThrowsNullReferenceException() + { + var freezable = new CustomFreezable(); + freezable.CreateInstanceCoreAction = () => null!; + Assert.Throws(() => freezable.CloneCurrentValue()); + } + + [Fact] + public void CloneCurrentValue_InvokeCreateInstanceCore_Success() + { + var freezable = new CustomFreezable(); + var clone = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => freezable; + Assert.Same(freezable, freezable.CloneCurrentValue()); + Assert.False(clone.IsFrozen); + } + + [Fact] + public void CloneCurrentValue_InvokeSameCreateInstanceCore_Success() + { + var freezable = new CustomFreezable(); + freezable.CreateInstanceCoreAction = () => freezable; + Assert.Same(freezable, freezable.CloneCurrentValue()); + } + + [Fact] + public void CloneCurrentValue_InvokeFrozenCreateInstanceCore_Success() + { + var freezable = new CustomFreezable(); + var clone = new CustomFreezable(); + clone.Freeze(); + freezable.CreateInstanceCoreAction = () => clone; + Assert.Same(clone, freezable.CloneCurrentValue()); + Assert.True(clone.IsFrozen); + } + + [Fact] + public void CloneCurrentValue_InvokeCantFreeze_ReturnsExpected() + { + var freezable = new CustomFreezable(); + var clone = new CustomFreezable(); + freezable.CreateInstanceCoreAction = () => clone; + int callCount = 0; + clone.FreezeCoreAction = (checking) => + { + Assert.True(checking); + callCount++; + return false; + }; + + Assert.Same(clone, freezable.CloneCurrentValue()); + Assert.NotSame(freezable, clone); + Assert.False(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(CustomFreezable)), clone.DependencyObjectType); + Assert.NotNull(clone.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone.Dispatcher); + Assert.False(clone.IsFrozen); + Assert.False(clone.IsSealed); + } + + [Fact] + public void CloneCurrentValue_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.CloneCurrentValue()); + }); + } + + [Fact] + public void CloneCurrentValue_InvokeFrozenOnDifferentThread_ReturnsExpected() + { + var freezable = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + SubFreezable clone = Assert.IsType(freezable.CloneCurrentValue()); + Assert.NotSame(freezable, clone); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.NotNull(clone.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, clone.Dispatcher); + Assert.False(clone.IsFrozen); + Assert.False(clone.IsSealed); + }); + } + + [Fact] + public void CloneCurrentValueCore_InvokeDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + // Clone. + freezable.CloneCurrentValueCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.CloneCurrentValueCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCurrentValueCore_InvokeWithProperties_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + // Clone. + freezable.CloneCurrentValueCore(sourceFreezable); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.NotSame(anotherFreezable1, ((SubFreezable)freezable.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.NotSame(anotherFreezable2, ((SubFreezable)freezable.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.NotSame(anotherFreezable3, ((SubFreezable)freezable.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)freezable.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCurrentValueCore_InvokeWithReadOnlyProperty_DoesNotCopy() + { + var freezable = new SubFreezable(); + var sourceFreezable = new SubFreezable(); + DependencyPropertyKey propertyKey = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + sourceFreezable.SetValue(propertyKey, "value"); + + // Clone. + freezable.CloneCurrentValueCore(sourceFreezable); + Assert.Null(freezable.GetValue(propertyKey.DependencyProperty)); + Assert.Equal("value", sourceFreezable.GetValue(propertyKey.DependencyProperty)); + } + + [Fact] + public void CloneCurrentValueCore_InvokeSourceFrozenDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + sourceFreezable.Freeze(); + + // Clone. + freezable.CloneCurrentValueCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.CloneCurrentValueCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCurrentValueCore_InvokeSourceFrozenWithProperties_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + sourceFreezable.Freeze(); + + // Clone. + freezable.CloneCurrentValueCore(sourceFreezable); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.NotSame(anotherFreezable1, ((SubFreezable)freezable.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.NotSame(anotherFreezable2, ((SubFreezable)freezable.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.NotSame(anotherFreezable3, ((SubFreezable)freezable.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)freezable.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCurrentValueCore_InvokeDestinationFrozenDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + freezable.Freeze(); + + // Clone. + freezable.CloneCurrentValueCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + + // Clone again. + freezable.CloneCurrentValueCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void CloneCurrentValueCore_InvokeDestinationFrozenWithProperties_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + freezable.Freeze(); + + // Clone. + Assert.Throws(() => freezable.CloneCurrentValueCore(sourceFreezable)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.Null(freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void CloneCurrentValueCore_InvokeOnDifferentThreadDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + freezable.CloneCurrentValueCore(sourceFreezable); + }); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.CloneCurrentValueCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCurrentValueCore_InvokeOnDifferentThreadWithProperties_ThrowsInvalidOperationException() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.CloneCurrentValueCore(sourceFreezable)); + }); + + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCurrentValueCore_InvokeSourceFrozenOnDifferentThreadDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + sourceFreezable.Freeze(); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + freezable.CloneCurrentValueCore(sourceFreezable); + }); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.CloneCurrentValueCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCurrentValueCore_InvokeSourceFrozenOnDifferentThreadWithProperties_ThrowsInvalidOperationException() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + sourceFreezable.Freeze(); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.CloneCurrentValueCore(sourceFreezable)); + }); + + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void CloneCurrentValueCore_InvokeDestinationFrozenOnDifferentThreadDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + freezable.Freeze(); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + freezable.CloneCurrentValueCore(sourceFreezable); + }); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + + // Clone again. + freezable.CloneCurrentValueCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void CloneCurrentValueCore_InvokeDestinationFrozenOnDifferentThreadWithProperties_ThrowsInvalidOperationException() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + freezable.Freeze(); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.CloneCurrentValueCore(sourceFreezable)); + }); + + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.Null(freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void CloneCurrentValueCore_NullSourceFreezable_ThrowsArgumentNullException() + { + var freezable = new SubFreezable(); + // TODO: this should not throw NullReferenceException. + //Assert.Throws("sourceFreezable", () => freezable.CloneCurrentValueCore(null!)); + Assert.Throws(() => freezable.CloneCurrentValueCore(null!)); + } + + public static IEnumerable CreateInstance_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new SubFreezable() }; + yield return new object?[] { new SubFreezable2() }; + + var frozen = new SubFreezable2(); + frozen.Freeze(); + yield return new object?[] { frozen }; + } + + [Theory] + [MemberData(nameof(CreateInstance_TestData))] + public void CreateInstance_Invoke_ReturnsExpected(Freezable result) + { + var freezable = new SubFreezable(); + int callCount = 0; + freezable.CreateInstanceCoreAction = () => + { + callCount++; + return result; + }; + + Assert.Same(result, freezable.CreateInstance()); + Assert.Equal(1, callCount); + + // Call again. + Assert.Same(result, freezable.CreateInstance()); + Assert.Equal(2, callCount); + } + + [Theory] + [MemberData(nameof(CreateInstance_TestData))] + public void CreateInstance_InvokeFrozen_ReturnsExpected(Freezable result) + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + int callCount = 0; + freezable.CreateInstanceCoreAction = () => + { + callCount++; + return result; + }; + + Assert.Same(result, freezable.CreateInstance()); + Assert.Equal(1, callCount); + + // Call again. + Assert.Same(result, freezable.CreateInstance()); + Assert.Equal(2, callCount); + } + + [Theory] + [MemberData(nameof(CreateInstance_TestData))] + public void CreateInstance_InvokeOnDifferentThread_ReturnsExpected(Freezable result) + { + var freezable = new SubFreezable(); + int callCount = 0; + freezable.CreateInstanceCoreAction = () => + { + callCount++; + return result; + }; + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Same(result, freezable.CreateInstance()); + Assert.Equal(1, callCount); + }); + } + + [Theory] + [MemberData(nameof(CreateInstance_TestData))] + public void CreateInstance_InvokeFrozenOnDifferentThread_ReturnsExpected(Freezable result) + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + int callCount = 0; + freezable.CreateInstanceCoreAction = () => + { + callCount++; + return result; + }; + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Same(result, freezable.CreateInstance()); + Assert.Equal(1, callCount); + }); + } + + + + [Fact] + public void CoerceValue_InvokeValueType_GetValueReturnsExpected() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + + // Coerce default. + obj.CoerceValue(property); + Assert.False((bool)obj.GetValue(property)); + + // Set true. + obj.SetValue(property, true); + Assert.True((bool)obj.GetValue(property)); + + // Coerce true. + obj.CoerceValue(property); + Assert.True((bool)obj.GetValue(property)); + + // Coerce again. + obj.CoerceValue(property); + Assert.True((bool)obj.GetValue(property)); + } + + [Fact] + public void CoerceValue_InvokeNullable_GetValueReturnsExpected() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool?), typeof(DependencyObject)); + + // Coerce default. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(property, true); + Assert.True((bool)obj.GetValue(property)); + + // Coerce custom. + obj.CoerceValue(property); + Assert.True((bool)obj.GetValue(property)); + + // Coerce again. + obj.CoerceValue(property); + Assert.True((bool)obj.GetValue(property)); + } + + [Fact] + public void CoerceValue_InvokeReferenceType_GetValueReturnsExpected() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + + // Coerce default. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Coerce custom. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + + // Coerce again. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + } + + [Fact] + public void CoerceValue_InvokeObject_GetValueReturnsExpected() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(object), typeof(DependencyObject)); + + // Coerce default. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + + // Set custom. + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + + // Coerce custom. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + + // Coerce again. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + + // Set different. + obj.SetValue(property, 1); + Assert.Equal(1, obj.GetValue(property)); + + // Coerce custom. + obj.CoerceValue(property); + Assert.Equal(1, obj.GetValue(property)); + + // Coerce again. + obj.CoerceValue(property); + Assert.Equal(1, obj.GetValue(property)); + + // Set null. + obj.SetValue(property, null); + Assert.Null(obj.GetValue(property)); + + // Coerce custom. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + + // Coerce again. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void CoerceValue_InvokeCustomPropertyChangeCallback_Success() + { + var obj = new SubFreezable(); + + int changedCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Coerce default. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + + // Coerce custom. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + + // Coerce again. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + } + + [Fact] + public void CoerceValue_InvokeCustomCoerceValueCallback_Success() + { + var obj = new SubFreezable(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Coerce default. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Set custom. + expectedNewValue = "value"; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(2, coerceValueCallCount); + + // Coerce custom. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(3, coerceValueCallCount); + + // Coerce again. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(4, coerceValueCallCount); + } + + [Theory] + [InlineData(null)] + [InlineData("customCoercedValue")] + public void CoerceValue_InvokeCustomCoerceValueCallbackReturnsCustom_Success(object? customCoercedValue) + { + var obj = new SubFreezable(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + object? newBaseValue = "coercedValue"; + var typeMetadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return newBaseValue; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name + customCoercedValue?.GetType().Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Coerce default. + obj.CoerceValue(property); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Set custom. + expectedNewValue = "value"; + obj.SetValue(property, "value"); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(2, coerceValueCallCount); + + // Coerce custom. + newBaseValue = customCoercedValue; + obj.CoerceValue(property); + Assert.Equal(customCoercedValue, obj.GetValue(property)); + Assert.Equal(3, coerceValueCallCount); + + // Coerce again. + obj.CoerceValue(property); + Assert.Equal(customCoercedValue, obj.GetValue(property)); + Assert.Equal(4, coerceValueCallCount); + } + + [Fact] + public void CoerceValue_InvokeCustomCoerceValueCallbackReturnsUnset_Success() + { + var obj = new SubFreezable(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedBaseValue = null; + object? newBaseValue = null; + var typeMetadata = new PropertyMetadata( + null, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedBaseValue, baseValue); + coerceValueCallCount++; + return newBaseValue; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Set custom. + expectedBaseValue = "value"; + newBaseValue = "coercedValue"; + obj.SetValue(property, "value"); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Coerce. + expectedBaseValue = "value"; + newBaseValue = DependencyProperty.UnsetValue; + obj.CoerceValue(property); + Assert.Equal("coercedValue", obj.GetValue(property)); + Assert.Equal(2, coerceValueCallCount); + } + + [Fact] + public void CoerceValue_InvokeChangeAndCoerceValueCallback_Success() + { + var obj = new SubFreezable(); + + int changedCallCount = 0; + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedOldValue = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + null, + (d, e) => + { + Assert.Same(obj, d); + Assert.Same(expectedProperty, e.Property); + Assert.Same(expectedOldValue, e.OldValue); + Assert.Same(expectedNewValue, e.NewValue); + Assert.Equal(expectedNewValue, obj.GetValue(e.Property)); + changedCallCount++; + }, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Same(expectedNewValue, baseValue); + coerceValueCallCount++; + return baseValue; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Coerce default. + obj.CoerceValue(property); + Assert.Null(obj.GetValue(property)); + Assert.Equal(0, changedCallCount); + Assert.Equal(1, coerceValueCallCount); + + // Set custom. + expectedNewValue = "value"; + expectedOldValue = null; + obj.SetValue(property, "value"); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(2, coerceValueCallCount); + + // Coerce custom. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(3, coerceValueCallCount); + + // Coerce again. + obj.CoerceValue(property); + Assert.Equal("value", obj.GetValue(property)); + Assert.Equal(1, changedCallCount); + Assert.Equal(4, coerceValueCallCount); + } + + [Fact] + public void CoerceValue_InvokeReadOnly_GetValueReturnsExpected() + { + var obj = new SubFreezable(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + // Coerce default. + obj.CoerceValue(property); + Assert.False((bool)obj.GetValue(property)); + + // Coerce again. + obj.CoerceValue(property); + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void CoerceValue_DependencyPropertyNullDp_ThrowsArgumentNullException() + { + var obj = new SubFreezable(); + // TODO: this should throw ArgumentNullException + //Assert.Throws("dp", () => obj.CoerceValue(null!)); + Assert.Throws(() => obj.CoerceValue(null!)); + } + + [Theory] + [InlineData(null)] + [InlineData("value")] + public void CoerceValue_InvalidResultDefault_ThrowsArgumentException(object? invalidValue) + { + var obj = new SubFreezable(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + var typeMetadata = new PropertyMetadata( + 0, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Equal(expectedNewValue, baseValue); + coerceValueCallCount++; + return invalidValue; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name + invalidValue?.GetType().Name, typeof(int), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Coerce default. + expectedNewValue = 0; + Assert.Throws(() => obj.CoerceValue(property)); + Assert.Equal(0, obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + } + + [Theory] + [InlineData(null)] + [InlineData("value")] + public void CoerceValue_InvalidResultCustom_ThrowsArgumentException(object? invalidValue) + { + var obj = new SubFreezable(); + + int coerceValueCallCount = 0; + DependencyProperty? expectedProperty = null; + object? expectedNewValue = null; + object? newBaseValue = 1; + var typeMetadata = new PropertyMetadata( + 0, + null, + (d, baseValue) => + { + Assert.Same(obj, d); + Assert.Equal(expectedNewValue, baseValue); + coerceValueCallCount++; + return newBaseValue; + } + ); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name + invalidValue?.GetType().Name, typeof(int), typeof(DependencyObject), typeMetadata); + expectedProperty = property; + + // Set custom. + expectedNewValue = 2; + newBaseValue = 2; + obj.SetValue(property, 2); + Assert.Equal(2, obj.GetValue(property)); + Assert.Equal(1, coerceValueCallCount); + + // Coerce custom. + newBaseValue = invalidValue; + Assert.Throws(() => obj.CoerceValue(property)); + Assert.Equal(2, coerceValueCallCount); + } + + [Fact] + public void CoerceValue_InvokeFrozen_ThrowsInvalidOperationException() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + obj.Freeze(); + + // Coerce default. + obj.CoerceValue(property); + Assert.False((bool)obj.GetValue(property)); + + // Coerce again. + obj.CoerceValue(property); + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void CoerceValue_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => obj.CoerceValue(property)); + }); + } + + [Fact] + public void CoerceValue_InvokeFrozenOnDifferentThread_Nop() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + obj.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + obj.CoerceValue(property); + }); + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void CoerceValue_InvokeFrozenCustomOnDifferentThread_Nop() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + obj.SetValue(property, true); + obj.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + obj.CoerceValue(property); + }); + Assert.True((bool)obj.GetValue(property)); + } + + [Fact] + public void Freeze_Invoke_Success() + { + var freezable = new SubFreezable(); + + // Freeze once. + freezable.Freeze(); + Assert.True(freezable.CanFreeze); + Assert.Null(freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + + // Freeze again. + freezable.Freeze(); + Assert.True(freezable.CanFreeze); + Assert.Null(freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void Freeze_InvokeWithProperties_Success() + { + var freezable = new SubFreezable(); + freezable.SetValue(SubFreezable.Property1, 10); + freezable.SetValue(SubFreezable.Property2, 20); + freezable.SetCurrentValue(SubFreezable.Property3, 30); + freezable.SetValue(SubFreezable.Property4, 40); + freezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + freezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + freezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + freezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + // Freeze once. + freezable.Freeze(); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.Same(anotherFreezable1, ((SubFreezable)freezable.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.Same(anotherFreezable2, ((SubFreezable)freezable.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.Same(anotherFreezable3, ((SubFreezable)freezable.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)freezable.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(freezable.CanFreeze); + Assert.Null(freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + + // Freeze again. + freezable.Freeze(); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.Same(anotherFreezable1, ((SubFreezable)freezable.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.Same(anotherFreezable2, ((SubFreezable)freezable.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.Same(anotherFreezable3, ((SubFreezable)freezable.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)freezable.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(freezable.CanFreeze); + Assert.Null(freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void Freeze_InvokeWithUnfreezableProperties_Success() + { + var freezable = new SubFreezable(); + freezable.SetValue(SubFreezable.Property1, 10); + freezable.SetValue(SubFreezable.Property2, 20); + freezable.SetCurrentValue(SubFreezable.Property3, 30); + freezable.SetValue(SubFreezable.Property4, 40); + freezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + freezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + freezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + freezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + var anotherFreezable4 = new CustomFreezable(); + anotherFreezable4.SetValue(SubFreezable.Property1, 90); + anotherFreezable4.FreezeCoreAction = (isChecking) => false; + freezable.SetValue(SubFreezable.Property9, anotherFreezable4); + + // Freeze once. + Assert.Throws(() => freezable.Freeze()); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.Same(anotherFreezable1, ((SubFreezable)freezable.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.Same(anotherFreezable2, ((SubFreezable)freezable.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.Same(anotherFreezable3, ((SubFreezable)freezable.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)freezable.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.False(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Freeze again. + Assert.Throws(() => freezable.Freeze()); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.False(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void Freeze_InvokeCustomFreezeCore_Success() + { + var freezable = new CustomFreezable(); + int callCount = 0; + freezable.FreezeCoreAction = (checking) => + { + Assert.Equal(callCount == 0, checking); + callCount++; + return true; + }; + + // Freeze once. + freezable.Freeze(); + Assert.Equal(2, callCount); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(CustomFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + + // Freeze again. + freezable.Freeze(); + Assert.Equal(2, callCount); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(CustomFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void Freeze_InvokeWithHandler_CallsChanged() + { + var freezable = new SubFreezable(); + + int callCount = 0; + EventHandler handler = (sender, e) => + { + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + callCount++; + }; + freezable.Changed += handler; + + // Freeze. + freezable.Freeze(); + Assert.Equal(1, callCount); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + + // Freeze again. + freezable.Freeze(); + Assert.Equal(1, callCount); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void Freeze_InvokeCustomOnChanged_CallsChanged() + { + var freezable = new CustomFreezable(); + int onChangedCallCount = 0; + int changedCallCount = 0; + freezable.OnChangedAction = () => + { + Assert.Equal(changedCallCount, onChangedCallCount); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + onChangedCallCount++; + }; + freezable.Changed += (sender, e) => + { + Assert.True(onChangedCallCount > changedCallCount); + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + changedCallCount++; + }; + + // Freeze. + freezable.Freeze(); + Assert.Equal(1, changedCallCount); + Assert.Equal(2, onChangedCallCount); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(CustomFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + + // Freeze again. + freezable.Freeze(); + Assert.Equal(1, changedCallCount); + Assert.Equal(2, onChangedCallCount); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(CustomFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void Freeze_InvokeWithRemovedHandler_CallsChanged() + { + var freezable = new SubFreezable(); + + int callCount = 0; + EventHandler handler = (sender, e) => callCount++; + freezable.Changed += handler; + freezable.Changed -= handler; + + // Freeze. + freezable.Freeze(); + Assert.Equal(0, callCount); + Assert.True(freezable.CanFreeze); + + // Freeze again. + freezable.Freeze(); + Assert.Equal(0, callCount); + Assert.True(freezable.CanFreeze); + } + + [Fact] + public void Freeze_InvokeCantFreeze_ThrowsInvalidOperationException() + { + var freezable = new CustomFreezable(); + int callCount = 0; + freezable.FreezeCoreAction = (checking) => + { + Assert.True(checking); + callCount++; + return false; + }; + + // Freeze. + Assert.Throws(() => freezable.Freeze()); + Assert.Equal(1, callCount); + Assert.False(freezable.IsFrozen); + } + + [Fact] + public void Freeze_InvokeFrozenCantFreeze_Nop() + { + var freezable = new CustomFreezable(); + + // Freeze once. + freezable.Freeze(); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(CustomFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + + int callCount = 0; + freezable.FreezeCoreAction = (checking) => + { + Assert.True(checking); + callCount++; + return false; + }; + + // Freeze again. + freezable.Freeze(); + Assert.Equal(0, callCount); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(CustomFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void Freeze_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.Freeze()); + }); + Assert.False(freezable.IsFrozen); + } + + [Fact] + public void Freeze_InvokeFrozenOnDifferentThread_Nop() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + freezable.Freeze(); + }); + Assert.True(freezable.IsFrozen); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void FreezeCore_InvokeDefault_Success(bool isChecking) + { + var freezable = new SubFreezable(); + + // Freeze once. + Assert.True(freezable.FreezeCore(isChecking)); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Freeze again. + Assert.True(freezable.FreezeCore(isChecking)); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void FreezeCore_InvokeWithProperties_Success(bool isChecking) + { + var freezable = new SubFreezable(); + freezable.SetValue(SubFreezable.Property1, 10); + freezable.SetValue(SubFreezable.Property2, 20); + freezable.SetCurrentValue(SubFreezable.Property3, 30); + freezable.SetValue(SubFreezable.Property4, 40); + freezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + freezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + freezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + freezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + // Freeze once. + Assert.True(freezable.FreezeCore(isChecking)); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.Same(anotherFreezable1, ((SubFreezable)freezable.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.Equal(!isChecking, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.Same(anotherFreezable2, ((SubFreezable)freezable.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.Equal(!isChecking, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.Same(anotherFreezable3, ((SubFreezable)freezable.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)freezable.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Freeze again. + Assert.True(freezable.FreezeCore(isChecking)); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.Same(anotherFreezable1, ((SubFreezable)freezable.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.Equal(!isChecking, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.Same(anotherFreezable2, ((SubFreezable)freezable.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.Equal(!isChecking, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.Same(anotherFreezable3, ((SubFreezable)freezable.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)freezable.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void FreezeCore_InvokeWithUnfreezableProperties_Success(bool isChecking) + { + var freezable = new SubFreezable(); + freezable.SetValue(SubFreezable.Property1, 10); + freezable.SetValue(SubFreezable.Property2, 20); + freezable.SetCurrentValue(SubFreezable.Property3, 30); + freezable.SetValue(SubFreezable.Property4, 40); + freezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + freezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + freezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + freezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + var anotherFreezable4 = new CustomFreezable(); + anotherFreezable4.SetValue(SubFreezable.Property1, 90); + anotherFreezable4.FreezeCoreAction = (isChecking) => false; + freezable.SetValue(SubFreezable.Property9, anotherFreezable4); + + // Freeze once. + Assert.Equal(!isChecking, freezable.FreezeCore(isChecking)); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.Same(anotherFreezable1, ((SubFreezable)freezable.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.Equal(!isChecking, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.Same(anotherFreezable2, ((SubFreezable)freezable.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.Equal(!isChecking, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.Same(anotherFreezable3, ((SubFreezable)freezable.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)freezable.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.Same(anotherFreezable4, freezable.GetValue(SubFreezable.Property9)); + Assert.Equal(90, ((CustomFreezable)freezable.GetValue(SubFreezable.Property9)).GetValue(SubFreezable.Property1)); + Assert.Equal(!isChecking, ((CustomFreezable)freezable.GetValue(SubFreezable.Property9)).IsFrozen); + Assert.False(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Freeze again. + Assert.Equal(!isChecking, freezable.FreezeCore(isChecking)); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.Same(anotherFreezable1, ((SubFreezable)freezable.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.Equal(!isChecking, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.Same(anotherFreezable2, ((SubFreezable)freezable.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.Equal(!isChecking, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.Same(anotherFreezable3, ((SubFreezable)freezable.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)freezable.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.False(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void FreezeCore_InvokeWithHandler_DoesNotCallChanged(bool isChecking) + { + var freezable = new SubFreezable(); + + int callCount = 0; + EventHandler handler = (sender, e) => callCount++; + freezable.Changed += handler; + + // Freeze. + Assert.True(freezable.FreezeCore(isChecking)); + Assert.Equal(0, callCount); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Freeze again. + Assert.True(freezable.FreezeCore(isChecking)); + Assert.Equal(0, callCount); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void FreezeCore_InvokeWithRemovedHandler_CallsChanged(bool isChecking) + { + var freezable = new SubFreezable(); + + int callCount = 0; + EventHandler handler = (sender, e) => callCount++; + freezable.Changed += handler; + freezable.Changed -= handler; + + // Freeze. + Assert.True(freezable.FreezeCore(isChecking)); + Assert.Equal(0, callCount); + Assert.True(freezable.CanFreeze); + + // Freeze again. + Assert.True(freezable.FreezeCore(isChecking)); + Assert.Equal(0, callCount); + Assert.True(freezable.CanFreeze); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void FreezeCore_InvokeOnDifferentThread_ReturnsTrue(bool isChecking) + { + var freezable = new SubFreezable(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.True(freezable.FreezeCore(isChecking)); + }); + Assert.False(freezable.IsFrozen); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void FreezeCore_InvokeFrozenOnDifferentThread_ReturnsTrue(bool isChecking) + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.True(freezable.FreezeCore(isChecking)); + }); + Assert.True(freezable.IsFrozen); + } + + [Fact] + public void GetAsFrozen_InvokeDefault_Success() + { + var freezable = new SubFreezable(); + int createInstanceCallCount = 0; + freezable.CreateInstanceCoreAction = () => + { + createInstanceCallCount++; + return new SubFreezable(); + }; + + // Clone. + SubFreezable clone = Assert.IsType(freezable.GetAsFrozen()); + Assert.NotSame(freezable, clone); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.Null(clone.Dispatcher); + Assert.True(clone.IsFrozen); + Assert.True(clone.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.Equal(1, createInstanceCallCount); + + // Clone again. + SubFreezable clone2 = Assert.IsType(freezable.GetAsFrozen()); + Assert.NotSame(freezable, clone); + Assert.NotSame(clone, clone2); + Assert.True(clone2.CanFreeze); + Assert.NotNull(clone2.DependencyObjectType); + Assert.Same(clone2.DependencyObjectType, clone2.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone2.DependencyObjectType); + Assert.Null(clone2.Dispatcher); + Assert.True(clone2.IsFrozen); + Assert.True(clone2.IsSealed); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.Null(clone.Dispatcher); + Assert.True(clone.IsFrozen); + Assert.True(clone.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.Equal(2, createInstanceCallCount); + } + + [Fact] + public void GetAsFrozen_InvokeWithProperties_Success() + { + var freezable = new SubFreezable(); + int createInstanceCallCount = 0; + freezable.CreateInstanceCoreAction = () => + { + createInstanceCallCount++; + return new SubFreezable(); + }; + + freezable.SetValue(SubFreezable.Property1, 10); + freezable.SetValue(SubFreezable.Property2, 20); + freezable.SetCurrentValue(SubFreezable.Property3, 30); + freezable.SetValue(SubFreezable.Property4, 40); + freezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + freezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + freezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + freezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + // Clone. + SubFreezable clone = Assert.IsType(freezable.GetAsFrozen()); + Assert.NotSame(freezable, clone); + Assert.Equal(10, clone.GetValue(SubFreezable.Property1)); + Assert.Equal(20, clone.GetValue(SubFreezable.Property2)); + Assert.Equal(30, clone.GetValue(SubFreezable.Property3)); + Assert.Equal(40, clone.GetValue(SubFreezable.Property4)); + Assert.Equal(50, clone.GetValue(SubFreezable.Property5)); + Assert.NotSame(anotherFreezable1, ((SubFreezable)clone.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)clone.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)clone.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.NotSame(anotherFreezable2, ((SubFreezable)clone.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)clone.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)clone.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.Same(anotherFreezable3, ((SubFreezable)clone.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)clone.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)clone.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.Null(clone.Dispatcher); + Assert.True(clone.IsFrozen); + Assert.True(clone.IsSealed); + } + + [Fact] + public void GetAsFrozen_InvokeWithUnfreezableProperties_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => new SubFreezable(); + freezable.SetValue(SubFreezable.Property1, 10); + freezable.SetValue(SubFreezable.Property2, 20); + freezable.SetCurrentValue(SubFreezable.Property3, 30); + freezable.SetValue(SubFreezable.Property4, 40); + freezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + freezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + freezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + freezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + var anotherFreezable4 = new CustomFreezable(); + anotherFreezable4.CreateInstanceCoreAction = () => new CustomFreezable(); + anotherFreezable4.SetValue(SubFreezable.Property1, 90); + anotherFreezable4.FreezeCoreAction = (isChecking) => false; + freezable.SetValue(SubFreezable.Property9, anotherFreezable4); + + SubFreezable clone = Assert.IsType(freezable.GetAsFrozen()); + Assert.NotSame(freezable, clone); + Assert.Equal(10, clone.GetValue(SubFreezable.Property1)); + Assert.Equal(20, clone.GetValue(SubFreezable.Property2)); + Assert.Equal(30, clone.GetValue(SubFreezable.Property3)); + Assert.Equal(40, clone.GetValue(SubFreezable.Property4)); + Assert.Equal(50, clone.GetValue(SubFreezable.Property5)); + Assert.NotSame(anotherFreezable1, ((SubFreezable)clone.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)clone.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)clone.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.NotSame(anotherFreezable2, ((SubFreezable)clone.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)clone.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)clone.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.Same(anotherFreezable3, ((SubFreezable)clone.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)clone.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)clone.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.Null(clone.Dispatcher); + Assert.True(clone.IsFrozen); + Assert.True(clone.IsSealed); + } + + [Fact] + public void GetAsFrozen_InvokeWithReadOnlyProperty_DoesNotCopy() + { + var freezable = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => new SubFreezable(); + DependencyPropertyKey propertyKey = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + freezable.SetValue(propertyKey, "value"); + + // Clone. + SubFreezable clone = Assert.IsType(freezable.GetAsFrozen()); + Assert.Null(clone.GetValue(propertyKey.DependencyProperty)); + Assert.Equal("value", freezable.GetValue(propertyKey.DependencyProperty)); + } + + [Fact] + public void GetAsFrozen_InvokeFrozen_ReturnsExpected() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + Assert.Same(freezable, freezable.GetAsFrozen()); + } + + [Fact] + public void GetAsFrozen_InvokeNullCreateInstanceCore_ThrowsNullReferenceException() + { + var freezable = new CustomFreezable(); + freezable.CreateInstanceCoreAction = () => null!; + Assert.Throws(() => freezable.GetAsFrozen()); + } + + [Fact] + public void GetAsFrozen_InvokeDifferentCreateInstanceCore_Success() + { + var freezable = new CustomFreezable(); + var clone = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => clone; + Assert.Same(clone, freezable.GetAsFrozen()); + Assert.False(freezable.IsFrozen); + Assert.True(clone.IsFrozen); + } + + [Fact] + public void GetAsFrozen_InvokeSameCreateInstanceCore_Success() + { + var freezable = new CustomFreezable(); + freezable.CreateInstanceCoreAction = () => freezable; + Assert.Same(freezable, freezable.GetAsFrozen()); + Assert.True(freezable.IsFrozen); + } + + [Fact] + public void GetAsFrozen_InvokeFrozenCreateInstanceCore_Success() + { + var freezable = new CustomFreezable(); + var clone = new CustomFreezable(); + clone.Freeze(); + freezable.CreateInstanceCoreAction = () => clone; + Assert.Same(clone, freezable.GetAsFrozen()); + Assert.False(freezable.IsFrozen); + Assert.True(clone.IsFrozen); + } + + [Fact] + public void GetAsFrozen_InvokeCantFreeze_ThrowsInvalidOperationException() + { + var freezable = new CustomFreezable(); + var clone = new CustomFreezable(); + freezable.CreateInstanceCoreAction = () => clone; + int callCount = 0; + clone.FreezeCoreAction = (checking) => + { + Assert.True(checking); + callCount++; + return false; + }; + + Assert.Throws(() => freezable.GetAsFrozen()); + } + + [Fact] + public void GetAsFrozen_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.GetAsFrozen()); + }); + } + + [Fact] + public void GetAsFrozen_InvokeFrozenOnDifferentThread_ReturnsExpected() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Same(freezable, freezable.GetAsFrozen()); + }); + } + + [Fact] + public void GetAsFrozenCore_InvokeDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + // Clone. + freezable.GetAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.GetAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetAsFrozenCore_InvokeWithProperties_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + // Clone. + freezable.GetAsFrozenCore(sourceFreezable); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.NotSame(anotherFreezable1, ((SubFreezable)freezable.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.NotSame(anotherFreezable2, ((SubFreezable)freezable.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.Same(anotherFreezable3, ((SubFreezable)freezable.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)freezable.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetAsFrozenCore_InvokeWithReadOnlyProperty_DoesNotCopy() + { + var freezable = new SubFreezable(); + var sourceFreezable = new SubFreezable(); + DependencyPropertyKey propertyKey = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + sourceFreezable.SetValue(propertyKey, "value"); + + // Clone. + freezable.GetAsFrozenCore(sourceFreezable); + Assert.Null(freezable.GetValue(propertyKey.DependencyProperty)); + Assert.Equal("value", sourceFreezable.GetValue(propertyKey.DependencyProperty)); + } + + [Fact] + public void GetAsFrozenCore_InvokeSourceFrozenDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + sourceFreezable.Freeze(); + + // Clone. + freezable.GetAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.GetAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetAsFrozenCore_InvokeSourceFrozenWithProperties_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + sourceFreezable.Freeze(); + + // Clone. + freezable.GetAsFrozenCore(sourceFreezable); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.Same(anotherFreezable1, ((SubFreezable)freezable.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.Same(anotherFreezable2, ((SubFreezable)freezable.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.Same(anotherFreezable3, ((SubFreezable)freezable.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)freezable.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetAsFrozenCore_InvokeDestinationFrozenDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + freezable.Freeze(); + + // Clone. + freezable.GetAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + + // Clone again. + freezable.GetAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void GetAsFrozenCore_InvokeDestinationFrozenWithProperties_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + freezable.Freeze(); + + // Clone. + Assert.Throws(() => freezable.GetAsFrozenCore(sourceFreezable)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.Null(freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void GetAsFrozenCore_InvokeOnDifferentThreadDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + freezable.GetAsFrozenCore(sourceFreezable); + }); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.GetAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetAsFrozenCore_InvokeOnDifferentThreadWithProperties_ThrowsInvalidOperationException() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.GetAsFrozenCore(sourceFreezable)); + }); + + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetAsFrozenCore_InvokeSourceFrozenOnDifferentThreadDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + sourceFreezable.Freeze(); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + freezable.GetAsFrozenCore(sourceFreezable); + }); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.GetAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetAsFrozenCore_InvokeSourceFrozenOnDifferentThreadWithProperties_ThrowsInvalidOperationException() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + sourceFreezable.Freeze(); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.GetAsFrozenCore(sourceFreezable)); + }); + + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetAsFrozenCore_InvokeDestinationFrozenOnDifferentThreadDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + freezable.Freeze(); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + freezable.GetAsFrozenCore(sourceFreezable); + }); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + + // Clone again. + freezable.GetAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void GetAsFrozenCore_InvokeDestinationFrozenOnDifferentThreadWithProperties_ThrowsInvalidOperationException() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + freezable.Freeze(); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.GetAsFrozenCore(sourceFreezable)); + }); + + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.Null(freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void GetAsFrozenCore_NullSourceFreezable_ThrowsArgumentNullException() + { + var freezable = new SubFreezable(); + // TODO: this should not throw NullReferenceException. + //Assert.Throws("sourceFreezable", () => freezable.GetAsFrozenCore(null!)); + Assert.Throws(() => freezable.GetAsFrozenCore(null!)); + } + + [Fact] + public void GetCurrentValueAsFrozen_InvokeDefault_Success() + { + var freezable = new SubFreezable(); + int createInstanceCallCount = 0; + freezable.CreateInstanceCoreAction = () => + { + createInstanceCallCount++; + return new SubFreezable(); + }; + + // Clone. + SubFreezable clone = Assert.IsType(freezable.GetCurrentValueAsFrozen()); + Assert.NotSame(freezable, clone); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.Null(clone.Dispatcher); + Assert.True(clone.IsFrozen); + Assert.True(clone.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.Equal(1, createInstanceCallCount); + + // Clone again. + SubFreezable clone2 = Assert.IsType(freezable.GetCurrentValueAsFrozen()); + Assert.NotSame(freezable, clone); + Assert.NotSame(clone, clone2); + Assert.True(clone2.CanFreeze); + Assert.NotNull(clone2.DependencyObjectType); + Assert.Same(clone2.DependencyObjectType, clone2.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone2.DependencyObjectType); + Assert.Null(clone2.Dispatcher); + Assert.True(clone2.IsFrozen); + Assert.True(clone2.IsSealed); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.Null(clone.Dispatcher); + Assert.True(clone.IsFrozen); + Assert.True(clone.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + Assert.True(freezable.CanFreeze); + Assert.Equal(2, createInstanceCallCount); + } + + [Fact] + public void GetCurrentValueAsFrozen_InvokeWithProperties_Success() + { + var freezable = new SubFreezable(); + int createInstanceCallCount = 0; + freezable.CreateInstanceCoreAction = () => + { + createInstanceCallCount++; + return new SubFreezable(); + }; + + freezable.SetValue(SubFreezable.Property1, 10); + freezable.SetValue(SubFreezable.Property2, 20); + freezable.SetCurrentValue(SubFreezable.Property3, 30); + freezable.SetValue(SubFreezable.Property4, 40); + freezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + freezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + freezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + freezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + // Clone. + SubFreezable clone = Assert.IsType(freezable.GetCurrentValueAsFrozen()); + Assert.NotSame(freezable, clone); + Assert.Equal(10, clone.GetValue(SubFreezable.Property1)); + Assert.Equal(20, clone.GetValue(SubFreezable.Property2)); + Assert.Equal(30, clone.GetValue(SubFreezable.Property3)); + Assert.Equal(40, clone.GetValue(SubFreezable.Property4)); + Assert.Equal(50, clone.GetValue(SubFreezable.Property5)); + Assert.NotSame(anotherFreezable1, ((SubFreezable)clone.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)clone.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)clone.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.NotSame(anotherFreezable2, ((SubFreezable)clone.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)clone.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)clone.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.Same(anotherFreezable3, ((SubFreezable)clone.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)clone.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)clone.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.Null(clone.Dispatcher); + Assert.True(clone.IsFrozen); + Assert.True(clone.IsSealed); + } + + [Fact] + public void GetCurrentValueAsFrozen_InvokeWithUnfreezableProperties_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => new SubFreezable(); + freezable.SetValue(SubFreezable.Property1, 10); + freezable.SetValue(SubFreezable.Property2, 20); + freezable.SetCurrentValue(SubFreezable.Property3, 30); + freezable.SetValue(SubFreezable.Property4, 40); + freezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + freezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + freezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + freezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + var anotherFreezable4 = new CustomFreezable(); + anotherFreezable4.CreateInstanceCoreAction = () => new CustomFreezable(); + anotherFreezable4.SetValue(SubFreezable.Property1, 90); + anotherFreezable4.FreezeCoreAction = (isChecking) => false; + freezable.SetValue(SubFreezable.Property9, anotherFreezable4); + + SubFreezable clone = Assert.IsType(freezable.GetCurrentValueAsFrozen()); + Assert.NotSame(freezable, clone); + Assert.Equal(10, clone.GetValue(SubFreezable.Property1)); + Assert.Equal(20, clone.GetValue(SubFreezable.Property2)); + Assert.Equal(30, clone.GetValue(SubFreezable.Property3)); + Assert.Equal(40, clone.GetValue(SubFreezable.Property4)); + Assert.Equal(50, clone.GetValue(SubFreezable.Property5)); + Assert.NotSame(anotherFreezable1, ((SubFreezable)clone.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)clone.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)clone.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.NotSame(anotherFreezable2, ((SubFreezable)clone.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)clone.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)clone.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.Same(anotherFreezable3, ((SubFreezable)clone.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)clone.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)clone.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(clone.CanFreeze); + Assert.NotNull(clone.DependencyObjectType); + Assert.Same(clone.DependencyObjectType, clone.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), clone.DependencyObjectType); + Assert.Null(clone.Dispatcher); + Assert.True(clone.IsFrozen); + Assert.True(clone.IsSealed); + } + + [Fact] + public void GetCurrentValueAsFrozen_InvokeWithReadOnlyProperty_DoesNotCopy() + { + var freezable = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => new SubFreezable(); + DependencyPropertyKey propertyKey = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + freezable.SetValue(propertyKey, "value"); + + // Clone. + SubFreezable clone = Assert.IsType(freezable.GetCurrentValueAsFrozen()); + Assert.Null(clone.GetValue(propertyKey.DependencyProperty)); + Assert.Equal("value", freezable.GetValue(propertyKey.DependencyProperty)); + } + + [Fact] + public void GetCurrentValueAsFrozen_InvokeFrozen_ReturnsExpected() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + Assert.Same(freezable, freezable.GetCurrentValueAsFrozen()); + } + + [Fact] + public void GetCurrentValueAsFrozen_InvokeNullCreateInstanceCore_ThrowsNullReferenceException() + { + var freezable = new CustomFreezable(); + freezable.CreateInstanceCoreAction = () => null!; + Assert.Throws(() => freezable.GetCurrentValueAsFrozen()); + } + + [Fact] + public void GetCurrentValueAsFrozen_InvokeDifferentCreateInstanceCore_Success() + { + var freezable = new CustomFreezable(); + var clone = new SubFreezable(); + freezable.CreateInstanceCoreAction = () => clone; + Assert.Same(clone, freezable.GetCurrentValueAsFrozen()); + Assert.False(freezable.IsFrozen); + Assert.True(clone.IsFrozen); + } + + [Fact] + public void GetCurrentValueAsFrozen_InvokeSameCreateInstanceCore_Success() + { + var freezable = new CustomFreezable(); + freezable.CreateInstanceCoreAction = () => freezable; + Assert.Same(freezable, freezable.GetCurrentValueAsFrozen()); + Assert.True(freezable.IsFrozen); + } + + [Fact] + public void GetCurrentValueAsFrozen_InvokeFrozenCreateInstanceCore_Success() + { + var freezable = new CustomFreezable(); + var clone = new CustomFreezable(); + clone.Freeze(); + freezable.CreateInstanceCoreAction = () => clone; + Assert.Same(clone, freezable.GetCurrentValueAsFrozen()); + Assert.False(freezable.IsFrozen); + Assert.True(clone.IsFrozen); + } + + [Fact] + public void GetCurrentValueAsFrozen_InvokeCantFreeze_ThrowsInvalidOperationException() + { + var freezable = new CustomFreezable(); + var clone = new CustomFreezable(); + freezable.CreateInstanceCoreAction = () => clone; + int callCount = 0; + clone.FreezeCoreAction = (checking) => + { + Assert.True(checking); + callCount++; + return false; + }; + + Assert.Throws(() => freezable.GetCurrentValueAsFrozen()); + } + + [Fact] + public void GetCurrentValueAsFrozen_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.GetCurrentValueAsFrozen()); + }); + } + + [Fact] + public void GetCurrentValueAsFrozen_InvokeFrozenOnDifferentThread_ReturnsExpected() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Same(freezable, freezable.GetCurrentValueAsFrozen()); + }); + } + + [Fact] + public void GetCurrentValueAsFrozenCore_InvokeDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + // Invoke. + freezable.GetCurrentValueAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.GetCurrentValueAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetCurrentValueAsFrozenCore_InvokeWithProperties_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + // Invoke. + freezable.GetCurrentValueAsFrozenCore(sourceFreezable); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.NotSame(anotherFreezable1, ((SubFreezable)freezable.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.NotSame(anotherFreezable2, ((SubFreezable)freezable.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.False(((SubFreezable)freezable.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.Same(anotherFreezable3, ((SubFreezable)freezable.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)freezable.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetCurrentValueAsFrozenCore_InvokeWithReadOnlyProperty_DoesNotCopy() + { + var freezable = new SubFreezable(); + var sourceFreezable = new SubFreezable(); + DependencyPropertyKey propertyKey = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + sourceFreezable.SetValue(propertyKey, "value"); + + // Clone. + freezable.GetCurrentValueAsFrozenCore(sourceFreezable); + Assert.Null(freezable.GetValue(propertyKey.DependencyProperty)); + Assert.Equal("value", sourceFreezable.GetValue(propertyKey.DependencyProperty)); + } + + [Fact] + public void GetCurrentValueAsFrozenCore_InvokeSourceFrozenDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + sourceFreezable.Freeze(); + + // Invoke. + freezable.GetCurrentValueAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.GetCurrentValueAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetCurrentValueAsFrozenCore_InvokeSourceFrozenWithProperties_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + sourceFreezable.Freeze(); + + // Invoke. + freezable.GetCurrentValueAsFrozenCore(sourceFreezable); + Assert.Equal(10, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(20, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(30, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(40, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(50, freezable.GetValue(SubFreezable.Property5)); + Assert.Same(anotherFreezable1, ((SubFreezable)freezable.GetValue(SubFreezable.Property6))); + Assert.Equal(60, ((SubFreezable)freezable.GetValue(SubFreezable.Property6)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property6)).IsFrozen); + Assert.Same(anotherFreezable2, ((SubFreezable)freezable.GetValue(SubFreezable.Property7))); + Assert.Equal(70, ((SubFreezable)freezable.GetValue(SubFreezable.Property7)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property7)).IsFrozen); + Assert.Same(anotherFreezable3, ((SubFreezable)freezable.GetValue(SubFreezable.Property8))); + Assert.Equal(80, ((SubFreezable)freezable.GetValue(SubFreezable.Property8)).GetValue(SubFreezable.Property1)); + Assert.True(((SubFreezable)freezable.GetValue(SubFreezable.Property8)).IsFrozen); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetCurrentValueAsFrozenCore_InvokeDestinationFrozenDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + freezable.Freeze(); + + // Invoke. + freezable.GetCurrentValueAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + + // Clone again. + freezable.GetCurrentValueAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void GetCurrentValueAsFrozenCore_InvokeDestinationFrozenWithProperties_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + freezable.Freeze(); + + // Invoke. + Assert.Throws(() => freezable.GetCurrentValueAsFrozenCore(sourceFreezable)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.Null(freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void GetCurrentValueAsFrozenCore_InvokeOnDifferentThreadDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + freezable.GetCurrentValueAsFrozenCore(sourceFreezable); + }); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.GetCurrentValueAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetCurrentValueAsFrozenCore_InvokeOnDifferentThreadWithProperties_ThrowsInvalidOperationException() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.GetCurrentValueAsFrozenCore(sourceFreezable)); + }); + + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetCurrentValueAsFrozenCore_InvokeSourceFrozenOnDifferentThreadDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + sourceFreezable.Freeze(); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + freezable.GetCurrentValueAsFrozenCore(sourceFreezable); + }); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + + // Clone again. + freezable.GetCurrentValueAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetCurrentValueAsFrozenCore_InvokeSourceFrozenOnDifferentThreadWithProperties_ThrowsInvalidOperationException() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + sourceFreezable.Freeze(); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.GetCurrentValueAsFrozenCore(sourceFreezable)); + }); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.False(freezable.IsFrozen); + Assert.False(freezable.IsSealed); + } + + [Fact] + public void GetCurrentValueAsFrozenCore_InvokeDestinationFrozenOnDifferentThreadDefault_Success() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + freezable.Freeze(); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + freezable.GetCurrentValueAsFrozenCore(sourceFreezable); + }); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + + // Clone again. + freezable.GetCurrentValueAsFrozenCore(sourceFreezable); + Assert.True(freezable.CanFreeze); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.Null(freezable.Dispatcher); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void GetCurrentValueAsFrozenCore_InvokeDestinationFrozenOnDifferentThreadWithProperties_ThrowsInvalidOperationException() + { + var sourceFreezable = new SubFreezable(); + var freezable = new SubFreezable(); + + sourceFreezable.SetValue(SubFreezable.Property1, 10); + sourceFreezable.SetValue(SubFreezable.Property2, 20); + sourceFreezable.SetCurrentValue(SubFreezable.Property3, 30); + sourceFreezable.SetValue(SubFreezable.Property4, 40); + sourceFreezable.SetCurrentValue(SubFreezable.Property5, 50); + + var anotherFreezable1 = new SubFreezable(); + anotherFreezable1.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable1.SetValue(SubFreezable.Property1, 60); + sourceFreezable.SetValue(SubFreezable.Property6, anotherFreezable1); + + var anotherFreezable2 = new SubFreezable(); + anotherFreezable2.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable2.SetValue(SubFreezable.Property1, 70); + sourceFreezable.SetCurrentValue(SubFreezable.Property7, anotherFreezable2); + + var anotherFreezable3 = new SubFreezable(); + anotherFreezable3.CreateInstanceCoreAction = () => new SubFreezable(); + anotherFreezable3.SetValue(SubFreezable.Property1, 80); + anotherFreezable3.Freeze(); + sourceFreezable.SetCurrentValue(SubFreezable.Property8, anotherFreezable3); + + freezable.Freeze(); + + // Clone. + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.GetCurrentValueAsFrozenCore(sourceFreezable)); + }); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property1)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property2)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property3)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property4)); + Assert.Equal(0, freezable.GetValue(SubFreezable.Property5)); + Assert.Null(freezable.GetValue(SubFreezable.Property6)); + Assert.Null(freezable.GetValue(SubFreezable.Property7)); + Assert.Null(freezable.GetValue(SubFreezable.Property8)); + Assert.True(freezable.CanFreeze); + Assert.Null(freezable.Dispatcher); + Assert.NotNull(freezable.DependencyObjectType); + Assert.Same(freezable.DependencyObjectType, freezable.DependencyObjectType); + Assert.Same(DependencyObjectType.FromSystemType(typeof(SubFreezable)), freezable.DependencyObjectType); + Assert.True(freezable.IsFrozen); + Assert.True(freezable.IsSealed); + } + + [Fact] + public void GetCurrentValueAsFrozenCore_NullSourceFreezable_ThrowsArgumentNullException() + { + var freezable = new SubFreezable(); + // TODO: this should not throw NullReferenceException. + //Assert.Throws("sourceFreezable", () => freezable.GetCurrentValueAsFrozenCore(null!)); + Assert.Throws(() => freezable.GetCurrentValueAsFrozenCore(null!)); + } + + [Fact] + public void GetValue_Invoke_ReturnsExpected() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + + Assert.Null(obj.GetValue(property)); + + // Call again to test caching. + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeSetValue_ReturnsExpected() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + obj.SetValue(property, "value"); + + Assert.Equal("value", obj.GetValue(property)); + + // Call again to test caching. + Assert.Equal("value", obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeCustomDefaultValue_ReturnsExpected() + { + var obj = new SubFreezable(); + var typeMetadata = new PropertyMetadata("value"); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + + Assert.Equal("value", obj.GetValue(property)); + + // Call again to test caching. + Assert.Equal("value", obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeReadOnlyProperty_ReturnsExpected() + { + var obj = new SubFreezable(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + + Assert.False((bool)obj.GetValue(property)); + + // Call again to test caching. + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeFrozen_ReturnsExpected() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + obj.Freeze(); + + Assert.Null(obj.GetValue(property)); + + // Call again to test caching. + Assert.Null(obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeFrozenSetValue_ReturnsExpected() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + obj.SetValue(property, "value"); + obj.Freeze(); + + Assert.Equal("value", obj.GetValue(property)); + + // Call again to test caching. + Assert.Equal("value", obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeFrozenCustomDefaultValue_ReturnsExpected() + { + var obj = new SubFreezable(); + var typeMetadata = new PropertyMetadata("value"); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), typeMetadata); + obj.Freeze(); + + Assert.Equal("value", obj.GetValue(property)); + + // Call again to test caching. + Assert.Equal("value", obj.GetValue(property)); + } + + [Fact] + public void GetValue_InvokeFrozenReadOnlyProperty_ReturnsExpected() + { + var obj = new SubFreezable(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + obj.Freeze(); + + Assert.False((bool)obj.GetValue(property)); + + // Call again to test caching. + Assert.False((bool)obj.GetValue(property)); + } + + [Fact] + public void GetValue_NullProperty_ThrowsArgumentNullException() + { + var obj = new SubFreezable(); + Assert.Throws("dp", () => obj.GetValue(null!)); + } + + [Fact] + public void GetValue_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => obj.GetValue(property)); + }); + } + + [Fact] + public void GetValue_InvokeFrozenOnDifferentThread_ReturnsExpected() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + obj.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.False((bool)obj.GetValue(property)); + }); + } + + [Fact] + public void ReadPreamble_InvokeSuccess() + { + var freezable = new SubFreezable(); + + // Call. + freezable.ReadPreamble(); + + // Call again. + freezable.ReadPreamble(); + } + + [Fact] + public void ReadPreamble_InvokeWithHandler_DoesNotCallChanged() + { + var freezable = new CustomFreezable(); + int onChangedCallCount = 0; + freezable.OnChangedAction = () => onChangedCallCount++; + int changedCallCount = 0; + freezable.Changed += (sender, e) => changedCallCount++; + freezable.ReadPreamble(); + Assert.Equal(0, onChangedCallCount); + Assert.Equal(0, changedCallCount); + } + + [Fact] + public void ReadPreamble_InvokeFrozen_Nop() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + // Call. + freezable.ReadPreamble(); + + // Call again. + freezable.ReadPreamble(); + } + + [Fact] + public void ReadPreamble_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.ReadPreamble()); + }); + } + + [Fact] + public void ReadPreamble_InvokeFrozenOnDifferentThread_Nop() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + freezable.ReadPreamble(); + }); + } + + [Fact] + public void OnChanged_Invoke_Nop() + { + var freezable = new SubFreezable(); + freezable.OnChanged(); + } + + [Fact] + public void OnChanged_InvokeWithHandler_DoesNotCallChanged() + { + var freezable = new SubFreezable(); + int callCount = 0; + freezable.Changed += (sender, e) => callCount++; + + freezable.OnChanged(); + Assert.Equal(0, callCount); + } + + [Fact] + public void OnChanged_InvokeFrozen_Nop() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + freezable.OnChanged(); + } + + [Fact] + public void OnChanged_InvokeOnDifferentThread_Nop() + { + var freezable = new SubFreezable(); + Helpers.ExecuteOnDifferentThread(() => + { + freezable.OnChanged(); + }); + } + + [Fact] + public void OnChanged_InvokeFrozenOnDifferentThread_Nop() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + freezable.OnChanged(); + }); + } + + [Fact] + public void OnFreezablePropertyChanged_Invoke_Nop() + { + var freezable = new SubFreezable(); + var value1 = new SubDependencyObject(); + var value2 = new SubDependencyObject(); + freezable.OnFreezablePropertyChanged(value1, value2); + freezable.OnFreezablePropertyChanged(value1, null!); + freezable.OnFreezablePropertyChanged(null!, value2); + } + + [Fact] + public void OnFreezablePropertyChanged_InvokeFrozen_Nop() + { + var freezable = new SubFreezable(); + var value1 = new SubDependencyObject(); + var value2 = new SubDependencyObject(); + freezable.Freeze(); + freezable.OnFreezablePropertyChanged(value1, value2); + freezable.OnFreezablePropertyChanged(value1, null!); + freezable.OnFreezablePropertyChanged(null!, value2); + } + + [Fact] + public void OnFreezablePropertyChanged_InvokeOnDifferentThread_Nop() + { + var freezable = new SubFreezable(); + var value1 = new SubDependencyObject(); + var value2 = new SubDependencyObject(); + + Helpers.ExecuteOnDifferentThread(() => + { + freezable.OnFreezablePropertyChanged(value1, value2); + freezable.OnFreezablePropertyChanged(value1, null!); + freezable.OnFreezablePropertyChanged(null!, value2); + }); + } + + [Fact] + public void OnFreezablePropertyChanged_InvokeFrozenOnDifferentThread_Nop() + { + var freezable = new SubFreezable(); + var value1 = new SubDependencyObject(); + var value2 = new SubDependencyObject(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + freezable.OnFreezablePropertyChanged(value1, value2); + freezable.OnFreezablePropertyChanged(value1, null!); + freezable.OnFreezablePropertyChanged(null!, value2); + }); + } + + [Fact] + public void OnFreezablePropertyChanged_InvokeDifferentThread_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + var value1 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + var value2 = new SubDependencyObject(); + var value3 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + var value4 = new SubFreezable(); + value4.Freeze(); + + Assert.Throws(() => freezable.OnFreezablePropertyChanged(null!, value1)); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(null!, value3)); + freezable.OnFreezablePropertyChanged(null!, value4); + freezable.OnFreezablePropertyChanged(value1, value2); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value1, value3)); + freezable.OnFreezablePropertyChanged(value1, value4); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value2, value1)); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value2, value3)); + freezable.OnFreezablePropertyChanged(value2, value4); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value3, value1)); + freezable.OnFreezablePropertyChanged(value3, value2); + freezable.OnFreezablePropertyChanged(value3, value4); + } + + [Fact] + public void OnFreezablePropertyChanged_InvokeDifferentThreadFrozen_Nop() + { + var freezable = new SubFreezable(); + var value1 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + var value2 = new SubDependencyObject(); + var value3 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + freezable.Freeze(); + var value4 = new SubFreezable(); + value4.Freeze(); + + freezable.OnFreezablePropertyChanged(null!, value1); + freezable.OnFreezablePropertyChanged(null!, value2); + freezable.OnFreezablePropertyChanged(null!, value3); + freezable.OnFreezablePropertyChanged(null!, value4); + freezable.OnFreezablePropertyChanged(value1, value1); + freezable.OnFreezablePropertyChanged(value1, value2); + freezable.OnFreezablePropertyChanged(value1, value3); + freezable.OnFreezablePropertyChanged(value1, value4); + freezable.OnFreezablePropertyChanged(value2, value1); + freezable.OnFreezablePropertyChanged(value2, value2); + freezable.OnFreezablePropertyChanged(value2, value3); + freezable.OnFreezablePropertyChanged(value2, value4); + freezable.OnFreezablePropertyChanged(value3, value1); + freezable.OnFreezablePropertyChanged(value3, value2); + freezable.OnFreezablePropertyChanged(value3, value3); + freezable.OnFreezablePropertyChanged(value3, value4); + } + + [Fact] + public void OnFreezablePropertyChanged_InvokeDifferentThreadOnDifferentThread_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + var value1 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + var value2 = new SubDependencyObject(); + var value3 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + var value4 = new SubFreezable(); + value4.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.OnFreezablePropertyChanged(null!, value1)); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(null!, value3)); + freezable.OnFreezablePropertyChanged(null!, value4); + freezable.OnFreezablePropertyChanged(value1, value2); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value1, value3)); + freezable.OnFreezablePropertyChanged(value1, value4); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value2, value1)); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value2, value3)); + freezable.OnFreezablePropertyChanged(value2, value4); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value3, value1)); + freezable.OnFreezablePropertyChanged(value3, value2); + freezable.OnFreezablePropertyChanged(value3, value4); + }); + } + + [Fact] + public void OnFreezablePropertyChanged_InvokeDifferentThreadFrozenOnDifferentThread_Nop() + { + var freezable = new SubFreezable(); + var value1 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + var value2 = new SubDependencyObject(); + var value3 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + freezable.Freeze(); + var value4 = new SubFreezable(); + value4.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + freezable.OnFreezablePropertyChanged(null!, value1); + freezable.OnFreezablePropertyChanged(null!, value2); + freezable.OnFreezablePropertyChanged(null!, value3); + freezable.OnFreezablePropertyChanged(null!, value4); + freezable.OnFreezablePropertyChanged(value1, value1); + freezable.OnFreezablePropertyChanged(value1, value2); + freezable.OnFreezablePropertyChanged(value1, value3); + freezable.OnFreezablePropertyChanged(value1, value4); + freezable.OnFreezablePropertyChanged(value2, value1); + freezable.OnFreezablePropertyChanged(value2, value2); + freezable.OnFreezablePropertyChanged(value2, value3); + freezable.OnFreezablePropertyChanged(value2, value4); + freezable.OnFreezablePropertyChanged(value3, value1); + freezable.OnFreezablePropertyChanged(value3, value2); + freezable.OnFreezablePropertyChanged(value3, value3); + freezable.OnFreezablePropertyChanged(value3, value4); + }); + } + public static IEnumerable OnFreezablePropertyChanged_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { SubFreezable.Property1 }; + } + + [Theory] + [MemberData(nameof(OnFreezablePropertyChanged_TestData))] + public void OnFreezablePropertyChanged_InvokeDependencyProperty_Nop(DependencyProperty property) + { + var freezable = new SubFreezable(); + var value1 = new SubDependencyObject(); + var value2 = new SubDependencyObject(); + freezable.OnFreezablePropertyChanged(value1, value2, property); + freezable.OnFreezablePropertyChanged(value1, null!, property); + freezable.OnFreezablePropertyChanged(null!, value2, property); + } + + [Theory] + [MemberData(nameof(OnFreezablePropertyChanged_TestData))] + public void OnFreezablePropertyChanged_InvokeDependencyPropertyFrozen_Nop(DependencyProperty property) + { + var freezable = new SubFreezable(); + var value1 = new SubDependencyObject(); + var value2 = new SubDependencyObject(); + freezable.Freeze(); + freezable.OnFreezablePropertyChanged(value1, value2, property); + freezable.OnFreezablePropertyChanged(value1, null!, property); + freezable.OnFreezablePropertyChanged(null!, value2, property); + } + + [Theory] + [MemberData(nameof(OnFreezablePropertyChanged_TestData))] + public void OnFreezablePropertyChanged_InvokeDependencyPropertyOnDifferentThread_Nop(DependencyProperty property) + { + var freezable = new SubFreezable(); + var value1 = new SubDependencyObject(); + var value2 = new SubDependencyObject(); + + Helpers.ExecuteOnDifferentThread(() => + { + freezable.OnFreezablePropertyChanged(value1, value2, property); + freezable.OnFreezablePropertyChanged(value1, null!, property); + freezable.OnFreezablePropertyChanged(null!, value2, property); + }); + } + + [Theory] + [MemberData(nameof(OnFreezablePropertyChanged_TestData))] + public void OnFreezablePropertyChanged_InvokeDependencyPropertyFrozenOnDifferentThread_Nop(DependencyProperty property) + { + var freezable = new SubFreezable(); + var value1 = new SubDependencyObject(); + var value2 = new SubDependencyObject(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + freezable.OnFreezablePropertyChanged(value1, value2, property); + freezable.OnFreezablePropertyChanged(value1, null!, property); + freezable.OnFreezablePropertyChanged(null!, value2, property); + }); + } + + [Theory] + [MemberData(nameof(OnFreezablePropertyChanged_TestData))] + public void OnFreezablePropertyChanged_InvokeDependencyPropertyDifferentThread_ThrowsInvalidOperationException(DependencyProperty property) + { + var freezable = new SubFreezable(); + var value1 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + var value2 = new SubDependencyObject(); + var value3 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + var value4 = new SubFreezable(); + value4.Freeze(); + + Assert.Throws(() => freezable.OnFreezablePropertyChanged(null!, value1, property)); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(null!, value3, property)); + freezable.OnFreezablePropertyChanged(null!, value4, property); + freezable.OnFreezablePropertyChanged(value1, value2, property); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value1, value3, property)); + freezable.OnFreezablePropertyChanged(value1, value4, property); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value2, value1, property)); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value2, value3, property)); + freezable.OnFreezablePropertyChanged(value2, value4, property); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value3, value1, property)); + freezable.OnFreezablePropertyChanged(value3, value2, property); + freezable.OnFreezablePropertyChanged(value3, value4, property); + } + + [Theory] + [MemberData(nameof(OnFreezablePropertyChanged_TestData))] + public void OnFreezablePropertyChanged_InvokeDependencyPropertyDifferentThreadFrozen_Nop(DependencyProperty property) + { + var freezable = new SubFreezable(); + var value1 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + var value2 = new SubDependencyObject(); + var value3 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + freezable.Freeze(); + var value4 = new SubFreezable(); + value4.Freeze(); + + freezable.OnFreezablePropertyChanged(null!, value1, property); + freezable.OnFreezablePropertyChanged(null!, value2, property); + freezable.OnFreezablePropertyChanged(null!, value3, property); + freezable.OnFreezablePropertyChanged(null!, value4, property); + freezable.OnFreezablePropertyChanged(value1, value1, property); + freezable.OnFreezablePropertyChanged(value1, value2, property); + freezable.OnFreezablePropertyChanged(value1, value3, property); + freezable.OnFreezablePropertyChanged(value1, value4, property); + freezable.OnFreezablePropertyChanged(value2, value1, property); + freezable.OnFreezablePropertyChanged(value2, value2, property); + freezable.OnFreezablePropertyChanged(value2, value3, property); + freezable.OnFreezablePropertyChanged(value2, value4, property); + freezable.OnFreezablePropertyChanged(value3, value1, property); + freezable.OnFreezablePropertyChanged(value3, value2, property); + freezable.OnFreezablePropertyChanged(value3, value3, property); + freezable.OnFreezablePropertyChanged(value3, value4, property); + } + + [Theory] + [MemberData(nameof(OnFreezablePropertyChanged_TestData))] + public void OnFreezablePropertyChanged_InvokeDependencyPropertyDifferentThreadOnDifferentThread_ThrowsInvalidOperationException(DependencyProperty property) + { + var freezable = new SubFreezable(); + var value1 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + var value2 = new SubDependencyObject(); + var value3 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + var value4 = new SubFreezable(); + value4.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.OnFreezablePropertyChanged(null!, value1, property)); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(null!, value3, property)); + freezable.OnFreezablePropertyChanged(null!, value4, property); + freezable.OnFreezablePropertyChanged(value1, value2, property); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value1, value3, property)); + freezable.OnFreezablePropertyChanged(value1, value4, property); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value2, value1, property)); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value2, value3, property)); + freezable.OnFreezablePropertyChanged(value2, value4, property); + Assert.Throws(() => freezable.OnFreezablePropertyChanged(value3, value1, property)); + freezable.OnFreezablePropertyChanged(value3, value2, property); + freezable.OnFreezablePropertyChanged(value3, value4, property); + }); + } + + [Theory] + [MemberData(nameof(OnFreezablePropertyChanged_TestData))] + public void OnFreezablePropertyChanged_InvokeDependencyPropertyDifferentThreadFrozenOnDifferentThread_Nop(DependencyProperty property) + { + var freezable = new SubFreezable(); + var value1 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + var value2 = new SubDependencyObject(); + var value3 = Helpers.ExecuteOnDifferentThread(() => new SubDependencyObject()); + freezable.Freeze(); + var value4 = new SubFreezable(); + value4.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + freezable.OnFreezablePropertyChanged(null!, value1, property); + freezable.OnFreezablePropertyChanged(null!, value2, property); + freezable.OnFreezablePropertyChanged(null!, value3, property); + freezable.OnFreezablePropertyChanged(null!, value4, property); + freezable.OnFreezablePropertyChanged(value1, value1, property); + freezable.OnFreezablePropertyChanged(value1, value2, property); + freezable.OnFreezablePropertyChanged(value1, value3, property); + freezable.OnFreezablePropertyChanged(value1, value4, property); + freezable.OnFreezablePropertyChanged(value2, value1, property); + freezable.OnFreezablePropertyChanged(value2, value2, property); + freezable.OnFreezablePropertyChanged(value2, value3, property); + freezable.OnFreezablePropertyChanged(value2, value4, property); + freezable.OnFreezablePropertyChanged(value3, value1, property); + freezable.OnFreezablePropertyChanged(value3, value2, property); + freezable.OnFreezablePropertyChanged(value3, value3, property); + freezable.OnFreezablePropertyChanged(value3, value4, property); + }); + } + + [Fact] + public void OnPropertyChanged_InvokeNoMetadata_Nop() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + obj.OnPropertyChanged(new DependencyPropertyChangedEventArgs(property, new object(), new object())); + } + + [Fact] + public void OnPropertyChanged_InvokeEmptyMetadata_Nop() + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), new PropertyMetadata()); + obj.OnPropertyChanged(new DependencyPropertyChangedEventArgs(property, new object(), new object())); + } + + [Fact] + public void OnPropertyChanged_InvokeMetadataHasPropertyChangedCallback_Nop() + { + var obj = new SubFreezable(); + int callCount = 0; + var metadata = new PropertyMetadata(null, (d, actualE) => callCount++); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), metadata); + var e = new DependencyPropertyChangedEventArgs(property, new object(), new object()); + obj.OnPropertyChanged(e); + Assert.Equal(0, callCount); + } + + [Fact] + public void OnPropertyChanged_InvokeOnDifferentThread_Nop() + { + var obj = new SubFreezable(); + int callCount = 0; + var metadata = new PropertyMetadata(null, (d, actualE) => callCount++); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject), metadata); + var e = new DependencyPropertyChangedEventArgs(property, new object(), new object()); + + Helpers.ExecuteOnDifferentThread(() => + { + obj.OnPropertyChanged(e); + Assert.Equal(0, callCount); + }); + } + + [Fact] + public void OnPropertyChanged_NullEProperty_ThrowsArgumentException() + { + var obj = new SubFreezable(); + DependencyPropertyChangedEventArgs e = default; + Assert.Throws("e", () => obj.OnPropertyChanged(e)); + } + + [Fact] + public void SetValue_InvokeDependencyProperty_GetValueReturnsExpected() + { + var freezable = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + + // Set true. + freezable.SetValue(property, true); + Assert.True((bool)freezable.GetValue(property)); + + // Set same. + freezable.SetValue(property, true); + Assert.True((bool)freezable.GetValue(property)); + + // Set false. + freezable.SetValue(property, false); + Assert.False((bool)freezable.GetValue(property)); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyWithHandler_CallsChanged() + { + var freezable = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + + int callCount = 0; + EventHandler handler = (sender, e) => + { + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + callCount++; + }; + freezable.Changed += handler; + + // Set. + freezable.SetValue(property, true); + Assert.True((bool)freezable.GetValue(property)); + Assert.Equal(1, callCount); + + // Set same. + freezable.SetValue(property, true); + Assert.True((bool)freezable.GetValue(property)); + Assert.Equal(1, callCount); + + // Set different. + freezable.SetValue(property, false); + Assert.False((bool)freezable.GetValue(property)); + Assert.Equal(2, callCount); + + // Remove handler. + freezable.Changed -= handler; + freezable.SetValue(property, true); + Assert.True((bool)freezable.GetValue(property)); + Assert.Equal(2, callCount); + } + + [Fact] + public void SetValue_InvokeWithCustomOnChanged_CallsChanged() + { + var freezable = new CustomFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + + int onChangedCallCount = 0; + int changedCallCount = 0; + freezable.OnChangedAction = () => + { + Assert.Equal(changedCallCount, onChangedCallCount); + onChangedCallCount++; + }; + freezable.Changed += (sender, e) => + { + Assert.True(onChangedCallCount > changedCallCount); + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + changedCallCount++; + }; + + // Set. + freezable.SetValue(property, true); + Assert.True((bool)freezable.GetValue(property)); + Assert.Equal(1, onChangedCallCount); + Assert.Equal(1, changedCallCount); + + // Set same. + freezable.SetValue(property, true); + Assert.True((bool)freezable.GetValue(property)); + Assert.Equal(1, onChangedCallCount); + Assert.Equal(1, changedCallCount); + + // Set different. + freezable.SetValue(property, false); + Assert.False((bool)freezable.GetValue(property)); + Assert.Equal(2, onChangedCallCount); + Assert.Equal(2, changedCallCount); + } + + + [Fact] + public void SetValue_DependencyPropertyNullDp_ThrowsArgumentNullException() + { + var obj = new SubFreezable(); + Assert.Throws("dp", () => obj.SetValue((DependencyProperty)null!, true)); + } + + [Fact] + public void SetValue_DependencyPropertyReadOnly_ThrowsInvalidOperationException() + { + var obj = new SubFreezable(); + DependencyPropertyKey key = DependencyProperty.RegisterReadOnly(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new PropertyMetadata()); + DependencyProperty property = key.DependencyProperty; + Assert.Throws(() => obj.SetValue(property, "value")); + } + + [Theory] + [InlineData(null)] + [InlineData("value")] + [InlineData(1)] + public void SetValue_DependencyPropertyInvalidValueValueType_ThrowsArgumentException(object? value) + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name + value?.GetType().Name, typeof(bool), typeof(DependencyObject)); + // TODO: add paramName + Assert.Throws(() => obj.SetValue(property, value)); + } + + [Theory] + [InlineData("value")] + [InlineData(1)] + public void SetValue_DependencyPropertyInvalidValueNullable_ThrowsArgumentException(object value) + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name + value?.GetType().Name, typeof(bool?), typeof(DependencyObject)); + // TODO: add paramName + Assert.Throws(() => obj.SetValue(property, value)); + } + + public static IEnumerable SetValue_DependencyPropertyInvalidValueReferenceType_TestData() + { + yield return new object[] { new object() }; + yield return new object[] { 1 }; + } + + [Theory] + [MemberData(nameof(SetValue_DependencyPropertyInvalidValueReferenceType_TestData))] + public void SetValue_DependencyPropertyInvalidValueReferenceType_ThrowsArgumentException(object value) + { + var obj = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name + value?.GetType().Name, typeof(string), typeof(DependencyObject)); + // TODO: add paramName + Assert.Throws(() => obj.SetValue(property, value)); + } + + [Fact] + public void SetValue_InvokeDependencyPropertyFrozen_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject)); + freezable.SetValue(property, true); + freezable.Freeze(); + + // Set true. + Assert.Throws(() => freezable.SetValue(property, true)); + Assert.True((bool)freezable.GetValue(property)); + + // Set same. + Assert.Throws(() => freezable.SetValue(property, true)); + Assert.True((bool)freezable.GetValue(property)); + + // Set false. + Assert.Throws(() => freezable.SetValue(property, false)); + Assert.True((bool)freezable.GetValue(property)); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SetValue_InvokeDifferentThread_ThrowsInvalidOperationException(bool value) + { + var freezable = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name + value.ToString(), typeof(bool), typeof(DependencyObject)); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.SetValue(property, value)); + }); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void SetValue_InvokeFrozenOnDifferentThread_ThrowsInvalidOperationException(bool value) + { + var freezable = new SubFreezable(); + DependencyProperty property = DependencyProperty.Register(nameof(FreezableTests) + MethodBase.GetCurrentMethod()!.Name + value.ToString(), typeof(bool), typeof(DependencyObject)); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.SetValue(property, value)); + }); + } + + [Fact] + public void WritePostscript_Invoke_Success() + { + var freezable = new SubFreezable(); + + // Call. + freezable.WritePostscript(); + + // Call again. + freezable.WritePostscript(); + } + + [Fact] + public void WritePostscript_InvokeWithHandler_CallsChanged() + { + var freezable = new CustomFreezable(); + int onChangedCallCount = 0; + int changedCallCount = 0; + freezable.OnChangedAction = () => + { + Assert.Equal(changedCallCount, onChangedCallCount); + onChangedCallCount++; + }; + freezable.Changed += (sender, e) => + { + Assert.True(onChangedCallCount > changedCallCount); + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + changedCallCount++; + }; + + // Call. + freezable.WritePostscript(); + Assert.Equal(1, onChangedCallCount); + Assert.Equal(1, changedCallCount); + + // Call again. + freezable.WritePostscript(); + Assert.Equal(2, onChangedCallCount); + Assert.Equal(2, changedCallCount); + } + + [Fact] + public void WritePostscript_InvokeWithRemovedHandler_DoesNotCallChanged() + { + var freezable = new CustomFreezable(); + int onChangedCallCount = 0; + int changedCallCount = 0; + freezable.OnChangedAction = () => + { + onChangedCallCount++; + }; + EventHandler handler = (sender, e) => changedCallCount++; + freezable.Changed += handler; + freezable.Changed -= handler; + + // Call. + freezable.WritePostscript(); + Assert.Equal(1, onChangedCallCount); + Assert.Equal(0, changedCallCount); + + // Call again. + freezable.WritePostscript(); + Assert.Equal(2, onChangedCallCount); + Assert.Equal(0, changedCallCount); + } + + [Fact] + public void WritePostscript_InvokeWithMultipleHandlers_CallsChanged() + { + var freezable = new CustomFreezable(); + int onChangedCallCount = 0; + int changedCallCount1 = 0; + int changedCallCount2 = 0; + int changedCallCount3 = 0; + freezable.OnChangedAction = () => + { + onChangedCallCount++; + }; + freezable.Changed += (sender, e) => + { + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + changedCallCount1++; + }; + freezable.Changed += (sender, e) => + { + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + changedCallCount2++; + }; + freezable.Changed += (sender, e) => + { + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + changedCallCount3++; + }; + + // Call. + freezable.WritePostscript(); + Assert.Equal(1, onChangedCallCount); + Assert.Equal(1, changedCallCount1); + Assert.Equal(1, changedCallCount2); + Assert.Equal(1, changedCallCount3); + + // Call again. + freezable.WritePostscript(); + Assert.Equal(2, onChangedCallCount); + Assert.Equal(2, changedCallCount1); + Assert.Equal(2, changedCallCount2); + Assert.Equal(2, changedCallCount3); + } + + [Fact] + public void WritePostscript_InvokeWithMultipleHandlersRemovedOne_DoesNotCallChanged() + { + var freezable = new CustomFreezable(); + int onChangedCallCount = 0; + int changedCallCount1 = 0; + int changedCallCount2 = 0; + int changedCallCount3 = 0; + freezable.OnChangedAction = () => + { + onChangedCallCount++; + }; + EventHandler handler1 = (sender, e) => + { + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + changedCallCount1++; + }; + freezable.Changed += handler1; + EventHandler handler2 = (sender, e) => + { + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + changedCallCount2++; + }; + freezable.Changed += handler2; + EventHandler handler3 = (sender, e) => changedCallCount3++; + freezable.Changed += handler3; + freezable.Changed -= handler3; + + // Call. + freezable.WritePostscript(); + Assert.Equal(1, onChangedCallCount); + Assert.Equal(1, changedCallCount1); + Assert.Equal(1, changedCallCount2); + Assert.Equal(0, changedCallCount3); + + // Call again. + freezable.WritePostscript(); + Assert.Equal(2, onChangedCallCount); + Assert.Equal(2, changedCallCount1); + Assert.Equal(2, changedCallCount2); + Assert.Equal(0, changedCallCount3); + } + + [Fact] + public void WritePostscript_InvokeWithMultipleHandlersRemovedAllButOne_DoesNotCallChanged() + { + var freezable = new CustomFreezable(); + int onChangedCallCount = 0; + int changedCallCount1 = 0; + int changedCallCount2 = 0; + int changedCallCount3 = 0; + freezable.OnChangedAction = () => + { + onChangedCallCount++; + }; + EventHandler handler1 = (sender, e) => changedCallCount1++; + freezable.Changed += handler1; + EventHandler handler2 = (sender, e) => + { + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + changedCallCount2++; + }; + freezable.Changed += handler2; + EventHandler handler3 = (sender, e) => changedCallCount3++; + freezable.Changed += handler3; + freezable.Changed -= handler3; + freezable.Changed -= handler1; + + // Call. + freezable.WritePostscript(); + Assert.Equal(1, onChangedCallCount); + Assert.Equal(0, changedCallCount1); + Assert.Equal(1, changedCallCount2); + Assert.Equal(0, changedCallCount3); + + // Call again. + freezable.WritePostscript(); + Assert.Equal(2, onChangedCallCount); + Assert.Equal(0, changedCallCount1); + Assert.Equal(2, changedCallCount2); + Assert.Equal(0, changedCallCount3); + } + + [Fact] + public void WritePostscript_InvokeWithMultipleHandlersRemovedAll_DoesNotCallChanged() + { + var freezable = new CustomFreezable(); + int onChangedCallCount = 0; + int changedCallCount1 = 0; + int changedCallCount2 = 0; + int changedCallCount3 = 0; + freezable.OnChangedAction = () => + { + onChangedCallCount++; + }; + EventHandler handler1 = (sender, e) => changedCallCount1++; + freezable.Changed += handler1; + EventHandler handler2 = (sender, e) => changedCallCount2++; + freezable.Changed += handler2; + EventHandler handler3 = (sender, e) => changedCallCount3++; + freezable.Changed += handler3; + freezable.Changed -= handler3; + freezable.Changed -= handler1; + + // Call. + freezable.WritePostscript(); + Assert.Equal(1, onChangedCallCount); + Assert.Equal(0, changedCallCount1); + Assert.Equal(1, changedCallCount2); + Assert.Equal(0, changedCallCount3); + + // Call again. + freezable.WritePostscript(); + Assert.Equal(2, onChangedCallCount); + Assert.Equal(0, changedCallCount1); + Assert.Equal(2, changedCallCount2); + Assert.Equal(0, changedCallCount3); + } + + [Fact] + public void WritePostscript_InvokeFrozen_Success() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + // Call. + freezable.WritePostscript(); + + // Call again. + freezable.WritePostscript(); + } + + [Fact] + public void WritePostscript_InvokeFrozenWithHandler_DoesNotCallChanged() + { + var freezable = new CustomFreezable(); + bool shouldTrack = false; + int onChangedCallCount = 0; + int changedCallCount = 0; + freezable.OnChangedAction = () => + { + if (shouldTrack) + { + onChangedCallCount++; + } + }; + freezable.Changed += (sender, e) => + { + if (shouldTrack) + { + Assert.Same(freezable, sender); + Assert.Same(EventArgs.Empty, e); + changedCallCount++; + } + }; + freezable.Freeze(); + shouldTrack = true; + + // Call. + freezable.WritePostscript(); + Assert.Equal(1, onChangedCallCount); + Assert.Equal(0, changedCallCount); + + // Call again. + freezable.WritePostscript(); + Assert.Equal(2, onChangedCallCount); + Assert.Equal(0, changedCallCount); + } + + [Fact] + public void WritePostscript_InvokeOnDifferentThread_Success() + { + var freezable = new SubFreezable(); + Helpers.ExecuteOnDifferentThread(() => + { + freezable.WritePostscript(); + }); + } + + [Fact] + public void WritePostscript_InvokeFrozenOnDifferentThread_Success() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + freezable.WritePostscript(); + }); + } + + [Fact] + public void WritePreamble_Invoke_Success() + { + var freezable = new SubFreezable(); + + // Call. + freezable.WritePreamble(); + + // Call again. + freezable.WritePreamble(); + } + + [Fact] + public void WritePreamble_InvokeWithHandler_DoesNotCallChanged() + { + var freezable = new CustomFreezable(); + int onChangedCallCount = 0; + freezable.OnChangedAction = () => onChangedCallCount++; + int changedCallCount = 0; + freezable.Changed += (sender, e) => changedCallCount++; + + // Call. + freezable.WritePreamble(); + Assert.Equal(0, onChangedCallCount); + Assert.Equal(0, changedCallCount); + + // Call again. + freezable.WritePreamble(); + Assert.Equal(0, onChangedCallCount); + Assert.Equal(0, changedCallCount); + } + + [Fact] + public void WritePreamble_InvokeFrozen_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + // Call. + Assert.Throws(() => freezable.WritePreamble()); + + // Call again. + Assert.Throws(() => freezable.WritePreamble()); + } + + [Fact] + public void WritePreamble_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.WritePreamble()); + }); + } + + [Fact] + public void WritePreamble_InvokeFrozenOnDifferentThread_ThrowsInvalidOperationException() + { + var freezable = new SubFreezable(); + freezable.Freeze(); + + Helpers.ExecuteOnDifferentThread(() => + { + Assert.Throws(() => freezable.WritePreamble()); + }); + } + + private class SubDependencyObject : DependencyObject + { + } + + private class CustomFreezable : Freezable + { + public Func? CreateInstanceCoreAction { get; set; } + + protected override Freezable CreateInstanceCore() + { + if (CreateInstanceCoreAction is null) + { + throw new NotImplementedException(); + } + + return CreateInstanceCoreAction(); + } + + public Func? FreezeCoreAction { get; set; } + + protected override bool FreezeCore(bool isChecking) + { + if (FreezeCoreAction != null) + { + return FreezeCoreAction(isChecking); + } + + return base.FreezeCore(isChecking); + } + + public Action? OnChangedAction { get; set; } + + protected override void OnChanged() + { + if (OnChangedAction != null) + { + OnChangedAction(); + } + + base.OnChanged(); + } + + public Action? OnPropertyChangedAction { get; set; } + + protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + base.OnPropertyChanged(e); + OnPropertyChangedAction?.Invoke(e); + } + + public new void ReadPreamble() => base.ReadPreamble(); + + public new void WritePreamble() => base.WritePreamble(); + + public new void WritePostscript() => base.WritePostscript(); + } + + private class SubFreezable : Freezable + { + public static DependencyProperty Property1 { get; } = DependencyProperty.Register($"{nameof(SubFreezable)}_{nameof(Property1)}", typeof(int), typeof(SubFreezable)); + public static DependencyProperty Property2 { get; } = DependencyProperty.Register($"{nameof(SubFreezable)}_{nameof(Property2)}", typeof(int), typeof(SubFreezable)); + public static DependencyProperty Property3 { get; } = DependencyProperty.Register($"{nameof(SubFreezable)}_{nameof(Property3)}", typeof(int), typeof(SubFreezable)); + public static DependencyProperty Property4 { get; } = DependencyProperty.RegisterAttached($"{nameof(SubFreezable)}_{nameof(Property4)}", typeof(int), typeof(SubFreezable)); + public static DependencyProperty Property5 { get; } = DependencyProperty.RegisterAttached($"{nameof(SubFreezable)}_{nameof(Property5)}", typeof(int), typeof(SubFreezable)); + public static DependencyProperty Property6 { get; } = DependencyProperty.Register($"{nameof(SubFreezable)}_{nameof(Property6)}", typeof(DependencyObject), typeof(SubFreezable)); + public static DependencyProperty Property7 { get; } = DependencyProperty.Register($"{nameof(SubFreezable)}_{nameof(Property7)}", typeof(DependencyObject), typeof(SubFreezable)); + public static DependencyProperty Property8 { get; } = DependencyProperty.Register($"{nameof(SubFreezable)}_{nameof(Property8)}", typeof(DependencyObject), typeof(SubFreezable)); + public static DependencyProperty Property9 { get; } = DependencyProperty.Register($"{nameof(SubFreezable)}_{nameof(Property9)}", typeof(DependencyObject), typeof(SubFreezable)); + public static DependencyPropertyKey ReadOnlyProperty { get; } = DependencyProperty.RegisterReadOnly($"{nameof(SubFreezable)}_{nameof(ReadOnlyProperty)}", typeof(int), typeof(SubFreezable), new PropertyMetadata(1)); + + public new Freezable CreateInstance() => base.CreateInstance(); + + public Func? CreateInstanceCoreAction { get; set; } + + protected override Freezable CreateInstanceCore() + { + if (CreateInstanceCoreAction is null) + { + throw new NotImplementedException(); + } + + return CreateInstanceCoreAction(); + } + + public new void CloneCore(Freezable sourceFreezable) => base.CloneCore(sourceFreezable); + + public new void CloneCurrentValueCore(Freezable sourceFreezable) => base.CloneCurrentValueCore(sourceFreezable); + + public new bool FreezeCore(bool isChecking) => base.FreezeCore(isChecking); + + public new void GetAsFrozenCore(Freezable sourceFreezable) => base.GetAsFrozenCore(sourceFreezable); + + public new void GetCurrentValueAsFrozenCore(Freezable sourceFreezable) => base.GetCurrentValueAsFrozenCore(sourceFreezable); + + public new void ReadPreamble() => base.ReadPreamble(); + + public new void OnChanged() => base.OnChanged(); + + public new void OnFreezablePropertyChanged(DependencyObject oldValue, DependencyObject newValue) => base.OnFreezablePropertyChanged(oldValue, newValue, Property1); + + public new void OnFreezablePropertyChanged(DependencyObject oldValue, DependencyObject newValue, DependencyProperty property) => base.OnFreezablePropertyChanged(oldValue, newValue, property); + + public new void OnPropertyChanged(DependencyPropertyChangedEventArgs e) => base.OnPropertyChanged(e); + + public new void WritePreamble() => base.WritePreamble(); + + public new void WritePostscript() => base.WritePostscript(); + } + + private class SubFreezable2 : Freezable + { + public SubFreezable2() : base() + { + } + + protected override Freezable CreateInstanceCore() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/KeyConverterTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/KeyConverterTests.cs new file mode 100644 index 00000000000..b1465705334 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/KeyConverterTests.cs @@ -0,0 +1,444 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.ComponentModel.Design.Serialization; +using System.Globalization; + +namespace System.Windows.Input.Tests; + +public class KeyConverterTests +{ + public static IEnumerable CanConvertTo_TestData() + { + yield return new object?[] { null, null, false }; + yield return new object?[] { null, typeof(object), false }; + yield return new object?[] { null, typeof(string), false }; + yield return new object?[] { null, typeof(InstanceDescriptor), false }; + yield return new object?[] { null, typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext(), null, false }; + yield return new object?[] { new CustomTypeDescriptorContext(), typeof(object), false }; + yield return new object?[] { new CustomTypeDescriptorContext(), typeof(string), false }; + yield return new object?[] { new CustomTypeDescriptorContext(), typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext(), typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, null, false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, typeof(object), false }; + // TODO: this should not throw. + //yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, typeof(string), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None }, null, false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None }, typeof(object), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None }, typeof(string), true }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None }, typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None }, typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.Cancel }, null, false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.Cancel }, typeof(object), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.Cancel }, typeof(string), true }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.Cancel }, typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.Cancel }, typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.A }, null, false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.A }, typeof(object), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.A }, typeof(string), true }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.A }, typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.A }, typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.OemClear }, null, false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.OemClear }, typeof(object), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.OemClear }, typeof(string), true }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.OemClear }, typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.OemClear }, typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed }, null, false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed }, typeof(object), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed }, typeof(string), true }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed }, typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed }, typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None - 1 }, null, false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None - 1 }, typeof(object), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None - 1 }, typeof(string), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None - 1 }, typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.None - 1 }, typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed + 1 }, null, false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed + 1 }, typeof(object), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed + 1 }, typeof(string), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed + 1 }, typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = Key.DeadCharProcessed + 1 }, typeof(Key), false }; + } + + [Theory] + [MemberData(nameof(CanConvertTo_TestData))] + public void CanConvertTo_Invoke_ReturnsExpected(ITypeDescriptorContext context, Type destinationType, bool expected) + { + var converter = new KeyConverter(); + Assert.Equal(expected, converter.CanConvertTo(context, destinationType)); + } + + [Fact] + public void CanConvertTo_InvokeToStringInstanceNotKey_ThrowsInvalidCastException() + { + // TODO: this should return false. + var converter = new KeyConverter(); + var context = new CustomTypeDescriptorContext { Instance = new object() }; + Assert.Throws(() => converter.CanConvertTo(context, typeof(string))); + } + + public static IEnumerable ConvertTo_KeyToString_TestData() + { + yield return new object[] { Key.None, "" }; + yield return new object[] { Key.Cancel, "Cancel" }; + yield return new object[] { Key.Back, "Backspace" }; + yield return new object[] { Key.Tab, "Tab" }; + yield return new object[] { Key.LineFeed, "Clear" }; + yield return new object[] { Key.Return, "Return" }; + yield return new object[] { Key.HanjaMode, "HanjaMode" }; + yield return new object[] { Key.Escape, "Esc" }; + yield return new object[] { Key.ImeConvert, "ImeConvert" }; + yield return new object[] { Key.Help, "Help" }; + yield return new object[] { Key.D0, "0" }; + yield return new object[] { Key.D1, "1" }; + yield return new object[] { Key.D2, "2" }; + yield return new object[] { Key.D3, "3" }; + yield return new object[] { Key.D4, "4" }; + yield return new object[] { Key.D5, "5" }; + yield return new object[] { Key.D6, "6" }; + yield return new object[] { Key.D7, "7" }; + yield return new object[] { Key.D8, "8" }; + yield return new object[] { Key.D9, "9" }; + yield return new object[] { Key.A, "A" }; + yield return new object[] { Key.B, "B" }; + yield return new object[] { Key.C, "C" }; + yield return new object[] { Key.D, "D" }; + yield return new object[] { Key.E, "E" }; + yield return new object[] { Key.F, "F" }; + yield return new object[] { Key.G, "G" }; + yield return new object[] { Key.H, "H" }; + yield return new object[] { Key.I, "I" }; + yield return new object[] { Key.J, "J" }; + yield return new object[] { Key.K, "K" }; + yield return new object[] { Key.L, "L" }; + yield return new object[] { Key.M, "M" }; + yield return new object[] { Key.N, "N" }; + yield return new object[] { Key.O, "O" }; + yield return new object[] { Key.P, "P" }; + yield return new object[] { Key.Q, "Q" }; + yield return new object[] { Key.R, "R" }; + yield return new object[] { Key.S, "S" }; + yield return new object[] { Key.T, "T" }; + yield return new object[] { Key.U, "U" }; + yield return new object[] { Key.V, "V" }; + yield return new object[] { Key.W, "W" }; + yield return new object[] { Key.X, "X" }; + yield return new object[] { Key.Y, "Y" }; + yield return new object[] { Key.Z, "Z" }; + yield return new object[] { Key.LWin, "LWin" }; + yield return new object[] { Key.DeadCharProcessed, "DeadCharProcessed" }; + } + + [Theory] + [MemberData(nameof(ConvertTo_KeyToString_TestData))] + public void ConvertTo_InvokeKeyToString_ReturnsExpected(Key value, string expected) + { + var converter = new KeyConverter(); + Assert.Equal(expected, converter.ConvertTo(value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + } + + [Theory] + [InlineData((Key)int.MinValue)] + [InlineData((Key)(-1))] + [InlineData(Key.DeadCharProcessed + 1)] + [InlineData((Key)int.MaxValue)] + public void ConvertTo_InvalidKey_ThrowsNotSupportedException(Key value) + { + var converter = new KeyConverter(); + Assert.Throws(() => converter.ConvertTo(value, typeof(string))); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + } + + [Theory] + [InlineData(null)] + // TODO: this should not throw InvalidCastException. + //[InlineData("", "")] + //[InlineData("value", "value")] + public void ConvertTo_InvokeNotKeyToStringNull_ThrowsNotSupportedException(object? value) + { + var converter = new KeyConverter(); + Assert.Throws(() => converter.ConvertTo(value, typeof(string))); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + } + + [Theory] + [InlineData("")] + [InlineData("value")] + public void ConvertTo_InvokeNotKeyToStringNotNull_ThrowsInvalidCastException(object value) + { + // TODO: this should not throw InvalidCastException. + var converter = new KeyConverter(); + Assert.Throws(() => converter.ConvertTo(value, typeof(string))); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + } + + public static IEnumerable ConvertTo_CantConvert_TestData() + { + yield return new object?[] { null, typeof(object) }; + yield return new object?[] { string.Empty, typeof(object) }; + yield return new object?[] { "value", typeof(object) }; + yield return new object?[] { new object(), typeof(object) }; + yield return new object?[] { Key.None, typeof(object) }; + + yield return new object?[] { null, typeof(Key) }; + yield return new object?[] { string.Empty, typeof(Key) }; + yield return new object?[] { "value", typeof(Key) }; + yield return new object?[] { new object(), typeof(Key) }; + yield return new object?[] { Key.None, typeof(Key) }; + } + + [Theory] + [MemberData(nameof(ConvertTo_CantConvert_TestData))] + public void ConvertTo_CantConvert_ThrowsNotSupportedException(object value, Type destinationType) + { + var converter = new KeyConverter(); + Assert.Throws(() => converter.ConvertTo(value, destinationType)); + Assert.Throws(() => converter.ConvertTo(null, null, value, destinationType)); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, destinationType)); + } + + public static IEnumerable ConvertTo_NullDestinationType_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + yield return new object?[] { new object() }; + yield return new object?[] { Key.None }; + } + + [Theory] + [MemberData(nameof(ConvertTo_NullDestinationType_TestData))] + public void ConvertTo_NullDestinationType_ThrowsArgumentNullException(object value) + { + var converter = new KeyConverter(); + Assert.Throws("destinationType", () => converter.ConvertTo(value, null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(null, null, Key.None, null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, Key.None, null!)); + } + + [Theory] + [InlineData(null, false)] + [InlineData(typeof(object), false)] + [InlineData(typeof(string), true)] + [InlineData(typeof(InstanceDescriptor), false)] + [InlineData(typeof(Key), false)] + public void CanConvertFrom_Invoke_ReturnsExpected(Type? sourceType, bool expected) + { + var converter = new KeyConverter(); + Assert.Equal(expected, converter.CanConvertFrom(sourceType!)); + Assert.Equal(expected, converter.CanConvertFrom(null, sourceType)); + Assert.Equal(expected, converter.CanConvertFrom(new CustomTypeDescriptorContext(), sourceType)); + } + + public static IEnumerable ConvertFrom_TestData() + { + yield return new object[] { "", Key.None }; + yield return new object[] { " ", Key.None }; + yield return new object[] { "0", Key.D0 }; + yield return new object[] { " 0 ", Key.D0 }; + yield return new object[] { "1", Key.D1 }; + yield return new object[] { "2", Key.D2 }; + yield return new object[] { "3", Key.D3 }; + yield return new object[] { "4", Key.D4 }; + yield return new object[] { "5", Key.D5 }; + yield return new object[] { "6", Key.D6 }; + yield return new object[] { "7", Key.D7 }; + yield return new object[] { "8", Key.D8 }; + yield return new object[] { "9", Key.D9 }; + yield return new object[] { "A", Key.A }; + yield return new object[] { "B", Key.B }; + yield return new object[] { "C", Key.C }; + yield return new object[] { "D", Key.D }; + yield return new object[] { "E", Key.E }; + yield return new object[] { "F", Key.F }; + yield return new object[] { "G", Key.G }; + yield return new object[] { "H", Key.H }; + yield return new object[] { "I", Key.I }; + yield return new object[] { "J", Key.J }; + yield return new object[] { "K", Key.K }; + yield return new object[] { "L", Key.L }; + yield return new object[] { "M", Key.M }; + yield return new object[] { "N", Key.N }; + yield return new object[] { "O", Key.O }; + yield return new object[] { "P", Key.P }; + yield return new object[] { "Q", Key.Q }; + yield return new object[] { "R", Key.R }; + yield return new object[] { "S", Key.S }; + yield return new object[] { "T", Key.T }; + yield return new object[] { "U", Key.U }; + yield return new object[] { "V", Key.V }; + yield return new object[] { "W", Key.W }; + yield return new object[] { "X", Key.X }; + yield return new object[] { "Y", Key.Y }; + yield return new object[] { "Z", Key.Z }; + yield return new object[] { "a", Key.A }; + yield return new object[] { "b", Key.B }; + yield return new object[] { "c", Key.C }; + yield return new object[] { "d", Key.D }; + yield return new object[] { "e", Key.E }; + yield return new object[] { "f", Key.F }; + yield return new object[] { "g", Key.G }; + yield return new object[] { "h", Key.H }; + yield return new object[] { "i", Key.I }; + yield return new object[] { "j", Key.J }; + yield return new object[] { "k", Key.K }; + yield return new object[] { "l", Key.L }; + yield return new object[] { "m", Key.M }; + yield return new object[] { "n", Key.N }; + yield return new object[] { "o", Key.O }; + yield return new object[] { "p", Key.P }; + yield return new object[] { "q", Key.Q }; + yield return new object[] { "r", Key.R }; + yield return new object[] { "s", Key.S }; + yield return new object[] { "t", Key.T }; + yield return new object[] { "u", Key.U }; + yield return new object[] { "v", Key.V }; + yield return new object[] { "w", Key.W }; + yield return new object[] { "x", Key.X }; + yield return new object[] { "y", Key.Y }; + yield return new object[] { "z", Key.Z }; + yield return new object[] { "ENTER", Key.Return }; + yield return new object[] { " ENTER ", Key.Return }; + yield return new object[] { " eNtEr ", Key.Return }; + yield return new object[] { "ESC", Key.Escape }; + yield return new object[] { "PGUP", Key.PageUp }; + yield return new object[] { "PGDN", Key.PageDown }; + yield return new object[] { "PRTSC", Key.PrintScreen }; + yield return new object[] { "INS", Key.Insert }; + yield return new object[] { "DEL", Key.Delete }; + yield return new object[] { "WINDOWS", Key.LWin }; + yield return new object[] { "WIN", Key.LWin }; + yield return new object[] { "LEFTWINDOWS", Key.LWin }; + yield return new object[] { "RIGHTWINDOWS", Key.RWin }; + yield return new object[] { "APPS", Key.Apps }; + yield return new object[] { "APPLICATION", Key.Apps }; + yield return new object[] { "BREAK", Key.Cancel }; + yield return new object[] { "BACKSPACE", Key.Back }; + yield return new object[] { "BKSP", Key.Back }; + yield return new object[] { "BS", Key.Back }; + yield return new object[] { "SHIFT", Key.LeftShift }; + yield return new object[] { "LEFTSHIFT", Key.LeftShift }; + yield return new object[] { "RIGHTSHIFT", Key.RightShift }; + yield return new object[] { "CONTROL", Key.LeftCtrl }; + yield return new object[] { "CTRL", Key.LeftCtrl }; + yield return new object[] { "LEFTCTRL", Key.LeftCtrl }; + yield return new object[] { "RIGHTCTRL", Key.RightCtrl }; + yield return new object[] { "ALT", Key.LeftAlt }; + yield return new object[] { "LEFTALT", Key.LeftAlt }; + yield return new object[] { "RIGHTALT", Key.RightAlt }; + yield return new object[] { "SEMICOLON", Key.OemSemicolon }; + yield return new object[] { "PLUS", Key.OemPlus }; + yield return new object[] { "COMMA", Key.OemComma }; + yield return new object[] { "MINUS", Key.OemMinus }; + yield return new object[] { "PERIOD", Key.OemPeriod }; + yield return new object[] { "QUESTION", Key.OemQuestion }; + yield return new object[] { "TILDE", Key.OemTilde }; + yield return new object[] { "OPENBRACKETS", Key.OemOpenBrackets }; + yield return new object[] { "PIPE", Key.OemPipe }; + yield return new object[] { "CLOSEBRACKETS", Key.OemCloseBrackets }; + yield return new object[] { "QUOTES", Key.OemQuotes }; + yield return new object[] { "BACKSLASH", Key.OemBackslash }; + yield return new object[] { "FINISH", Key.OemFinish }; + yield return new object[] { "ATTN", Key.Attn }; + yield return new object[] { "CRSEL", Key.CrSel }; + yield return new object[] { "EXSEL", Key.ExSel }; + yield return new object[] { "ERASEEOF", Key.EraseEof }; + yield return new object[] { "PLAY", Key.Play }; + yield return new object[] { "ZOOM", Key.Zoom }; + yield return new object[] { "PA1", Key.Pa1 }; + + yield return new object[] { "Cancel", Key.Cancel }; + yield return new object[] { "CANCEL", Key.Cancel }; + yield return new object[] { " CANCEL ", Key.Cancel }; + } + + [Theory] + [MemberData(nameof(ConvertFrom_TestData))] + public void ConvertFrom_InvokeStringValue_ReturnsExpected(string value, Key expected) + { + var converter = new KeyConverter(); + Assert.Equal(expected, converter.ConvertFrom(value)); + Assert.Equal(expected, converter.ConvertFrom(null, null, value)); + Assert.Equal(expected, converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Fact] + public void ConvertFrom_NullValue_ThrowsNotSupportedException() + { + var converter = new KeyConverter(); + Assert.Throws(() => converter.ConvertFrom(null!)); + Assert.Throws(() => converter.ConvertFrom(null, null, null)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, null)); + } + + public static IEnumerable ConvertFrom_InvalidValue_TestData() + { + yield return new object[] { "_" }; + yield return new object[] { " _ " }; + yield return new object[] { "\u0663" }; + yield return new object[] { " \u0663 " }; + yield return new object[] { "\u0409" }; + yield return new object[] { " \u0409 " }; + yield return new object[] { "NOSUCHKEY" }; + yield return new object[] { " NOSUCHKEY " }; + } + + [Theory] + [MemberData(nameof(ConvertFrom_InvalidValue_TestData))] + public void ConvertFrom_InvokeInvalidValue_ThrowsArgumentException(string value) + { + var converter = new KeyConverter(); + // TODO: add paramName. + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + public static IEnumerable ConvertFrom_CantConvert_TestData() + { + yield return new object[] { new object() }; + yield return new object[] { Key.A }; + yield return new object[] { ModifierKeys.None }; + } + + [Theory] + [MemberData(nameof(ConvertFrom_CantConvert_TestData))] + public void ConvertFrom_CantConvert_ThrowsNotSupportedException(object value) + { + var converter = new KeyConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + private class CustomTypeDescriptorContext : ITypeDescriptorContext + { + public IContainer Container => throw new NotImplementedException(); + + private object? _instance; + + public object Instance + { + get => _instance!; + set => _instance = value; + } + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/KeyInteropTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/KeyInteropTests.cs new file mode 100644 index 00000000000..929cbf5dcbf --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/KeyInteropTests.cs @@ -0,0 +1,688 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Input.Tests; + +public class KeyInteropTests +{ + [Theory] + [InlineData(VK_LBUTTON, Key.None)] + [InlineData(VK_RBUTTON, Key.None)] + [InlineData(VK_CANCEL, Key.Cancel)] + [InlineData(VK_MBUTTON, Key.None)] + [InlineData(VK_XBUTTON1, Key.None)] + [InlineData(VK_XBUTTON2, Key.None)] + [InlineData(0x07, Key.None)] + [InlineData(VK_BACK, Key.Back)] + [InlineData(VK_TAB, Key.Tab)] + [InlineData(0x0A, Key.None)] + [InlineData(0x0B, Key.None)] + [InlineData(VK_CLEAR, Key.Clear)] + [InlineData(VK_RETURN, Key.Return)] + [InlineData(0x0E, Key.None)] + [InlineData(0x0F, Key.None)] + [InlineData(VK_SHIFT, Key.LeftShift)] + [InlineData(VK_CONTROL, Key.LeftCtrl)] + [InlineData(VK_MENU, Key.LeftAlt)] + [InlineData(VK_PAUSE, Key.Pause)] + [InlineData(VK_CAPITAL, Key.Capital)] + [InlineData(VK_KANA, Key.KanaMode)] + [InlineData(0x16, Key.None)] + [InlineData(VK_JUNJA, Key.JunjaMode)] + [InlineData(VK_FINAL, Key.FinalMode)] + [InlineData(VK_KANJI, Key.KanjiMode)] + [InlineData(0x1A, Key.None)] + [InlineData(VK_ESCAPE, Key.Escape)] + [InlineData(VK_CONVERT, Key.ImeConvert)] + [InlineData(VK_NONCONVERT, Key.ImeNonConvert)] + [InlineData(VK_ACCEPT, Key.ImeAccept)] + [InlineData(VK_MODECHANGE, Key.ImeModeChange)] + [InlineData(VK_SPACE, Key.Space)] + [InlineData(VK_PRIOR, Key.Prior)] + [InlineData(VK_NEXT, Key.Next)] + [InlineData(VK_END, Key.End)] + [InlineData(VK_HOME, Key.Home)] + [InlineData(VK_LEFT, Key.Left)] + [InlineData(VK_UP, Key.Up)] + [InlineData(VK_RIGHT, Key.Right)] + [InlineData(VK_DOWN, Key.Down)] + [InlineData(VK_SELECT, Key.Select)] + [InlineData(VK_PRINT, Key.Print)] + [InlineData(VK_EXECUTE, Key.Execute)] + [InlineData(VK_SNAPSHOT, Key.Snapshot)] + [InlineData(VK_INSERT, Key.Insert)] + [InlineData(VK_DELETE, Key.Delete)] + [InlineData(VK_HELP, Key.Help)] + [InlineData(VK_0, Key.D0)] + [InlineData(VK_1, Key.D1)] + [InlineData(VK_2, Key.D2)] + [InlineData(VK_3, Key.D3)] + [InlineData(VK_4, Key.D4)] + [InlineData(VK_5, Key.D5)] + [InlineData(VK_6, Key.D6)] + [InlineData(VK_7, Key.D7)] + [InlineData(VK_8, Key.D8)] + [InlineData(VK_9, Key.D9)] + [InlineData(0x3A, Key.None)] + [InlineData(0x3B, Key.None)] + [InlineData(0x3C, Key.None)] + [InlineData(0x3D, Key.None)] + [InlineData(0x3E, Key.None)] + [InlineData(0x3F, Key.None)] + [InlineData(VK_A, Key.A)] + [InlineData(VK_B, Key.B)] + [InlineData(VK_C, Key.C)] + [InlineData(VK_D, Key.D)] + [InlineData(VK_E, Key.E)] + [InlineData(VK_F, Key.F)] + [InlineData(VK_G, Key.G)] + [InlineData(VK_H, Key.H)] + [InlineData(VK_I, Key.I)] + [InlineData(VK_J, Key.J)] + [InlineData(VK_K, Key.K)] + [InlineData(VK_L, Key.L)] + [InlineData(VK_M, Key.M)] + [InlineData(VK_N, Key.N)] + [InlineData(VK_O, Key.O)] + [InlineData(VK_P, Key.P)] + [InlineData(VK_Q, Key.Q)] + [InlineData(VK_R, Key.R)] + [InlineData(VK_S, Key.S)] + [InlineData(VK_T, Key.T)] + [InlineData(VK_U, Key.U)] + [InlineData(VK_V, Key.V)] + [InlineData(VK_W, Key.W)] + [InlineData(VK_X, Key.X)] + [InlineData(VK_Y, Key.Y)] + [InlineData(VK_Z, Key.Z)] + [InlineData(VK_LWIN, Key.LWin)] + [InlineData(VK_RWIN, Key.RWin)] + [InlineData(VK_APPS, Key.Apps)] + [InlineData(0x5E, Key.None)] + [InlineData(VK_SLEEP, Key.Sleep)] + [InlineData(VK_NUMPAD0, Key.NumPad0)] + [InlineData(VK_NUMPAD1, Key.NumPad1)] + [InlineData(VK_NUMPAD2, Key.NumPad2)] + [InlineData(VK_NUMPAD3, Key.NumPad3)] + [InlineData(VK_NUMPAD4, Key.NumPad4)] + [InlineData(VK_NUMPAD5, Key.NumPad5)] + [InlineData(VK_NUMPAD6, Key.NumPad6)] + [InlineData(VK_NUMPAD7, Key.NumPad7)] + [InlineData(VK_NUMPAD8, Key.NumPad8)] + [InlineData(VK_NUMPAD9, Key.NumPad9)] + [InlineData(VK_MULTIPLY, Key.Multiply)] + [InlineData(VK_ADD, Key.Add)] + [InlineData(VK_SEPARATOR, Key.Separator)] + [InlineData(VK_SUBTRACT, Key.Subtract)] + [InlineData(VK_DECIMAL, Key.Decimal)] + [InlineData(VK_DIVIDE, Key.Divide)] + [InlineData(VK_F1, Key.F1)] + [InlineData(VK_F2, Key.F2)] + [InlineData(VK_F3, Key.F3)] + [InlineData(VK_F4, Key.F4)] + [InlineData(VK_F5, Key.F5)] + [InlineData(VK_F6, Key.F6)] + [InlineData(VK_F7, Key.F7)] + [InlineData(VK_F8, Key.F8)] + [InlineData(VK_F9, Key.F9)] + [InlineData(VK_F10, Key.F10)] + [InlineData(VK_F11, Key.F11)] + [InlineData(VK_F12, Key.F12)] + [InlineData(VK_F13, Key.F13)] + [InlineData(VK_F14, Key.F14)] + [InlineData(VK_F15, Key.F15)] + [InlineData(VK_F16, Key.F16)] + [InlineData(VK_F17, Key.F17)] + [InlineData(VK_F18, Key.F18)] + [InlineData(VK_F19, Key.F19)] + [InlineData(VK_F20, Key.F20)] + [InlineData(VK_F21, Key.F21)] + [InlineData(VK_F22, Key.F22)] + [InlineData(VK_F23, Key.F23)] + [InlineData(VK_F24, Key.F24)] + [InlineData(VK_NUMLOCK, Key.NumLock)] + [InlineData(VK_SCROLL, Key.Scroll)] + [InlineData(VK_OEM_NEC_EQUAL, Key.None)] + //[InlineData(VK_OEM_FJ_JISHO, Key.None)] + [InlineData(VK_OEM_FJ_MASSHOU, Key.None)] + [InlineData(VK_OEM_FJ_TOUROKU, Key.None)] + [InlineData(VK_OEM_FJ_LOYA, Key.None)] + [InlineData(VK_OEM_FJ_ROYA, Key.None)] + [InlineData(VK_LSHIFT, Key.LeftShift)] + [InlineData(VK_RSHIFT, Key.RightShift)] + [InlineData(VK_LCONTROL, Key.LeftCtrl)] + [InlineData(VK_RCONTROL, Key.RightCtrl)] + [InlineData(VK_LMENU, Key.LeftAlt)] + [InlineData(VK_RMENU, Key.RightAlt)] + [InlineData(VK_BROWSER_BACK, Key.BrowserBack)] + [InlineData(VK_BROWSER_FORWARD, Key.BrowserForward)] + [InlineData(VK_BROWSER_REFRESH, Key.BrowserRefresh)] + [InlineData(VK_BROWSER_STOP, Key.BrowserStop)] + [InlineData(VK_BROWSER_SEARCH, Key.BrowserSearch)] + [InlineData(VK_BROWSER_FAVORITES, Key.BrowserFavorites)] + [InlineData(VK_BROWSER_HOME, Key.BrowserHome)] + [InlineData(VK_VOLUME_MUTE, Key.VolumeMute)] + [InlineData(VK_VOLUME_DOWN, Key.VolumeDown)] + [InlineData(VK_VOLUME_UP, Key.VolumeUp)] + [InlineData(VK_MEDIA_NEXT_TRACK, Key.MediaNextTrack)] + [InlineData(VK_MEDIA_PREV_TRACK, Key.MediaPreviousTrack)] + [InlineData(VK_MEDIA_STOP, Key.MediaStop)] + [InlineData(VK_MEDIA_PLAY_PAUSE, Key.MediaPlayPause)] + [InlineData(VK_LAUNCH_MAIL, Key.LaunchMail)] + [InlineData(VK_LAUNCH_MEDIA_SELECT, Key.SelectMedia)] + [InlineData(VK_LAUNCH_APP1, Key.LaunchApplication1)] + [InlineData(VK_LAUNCH_APP2, Key.LaunchApplication2)] + [InlineData(0xB8, Key.None)] + [InlineData(0xB9, Key.None)] + [InlineData(VK_OEM_1, Key.OemSemicolon)] + [InlineData(VK_OEM_PLUS, Key.OemPlus)] + [InlineData(VK_OEM_COMMA, Key.OemComma)] + [InlineData(VK_OEM_MINUS, Key.OemMinus)] + [InlineData(VK_OEM_PERIOD, Key.OemPeriod)] + [InlineData(VK_OEM_2, Key.OemQuestion)] + [InlineData(VK_OEM_3, Key.OemTilde)] + [InlineData(VK_C1, Key.AbntC1)] + [InlineData(VK_C2, Key.AbntC2)] + [InlineData(VK_GAMEPAD_A, Key.None)] + [InlineData(VK_GAMEPAD_B, Key.None)] + [InlineData(VK_GAMEPAD_X, Key.None)] + [InlineData(VK_GAMEPAD_Y, Key.None)] + [InlineData(VK_GAMEPAD_RIGHT_SHOULDER, Key.None)] + [InlineData(VK_GAMEPAD_LEFT_SHOULDER, Key.None)] + [InlineData(VK_GAMEPAD_LEFT_TRIGGER, Key.None)] + [InlineData(VK_GAMEPAD_RIGHT_TRIGGER, Key.None)] + [InlineData(VK_GAMEPAD_DPAD_UP, Key.None)] + [InlineData(VK_GAMEPAD_DPAD_DOWN, Key.None)] + [InlineData(VK_GAMEPAD_DPAD_LEFT, Key.None)] + [InlineData(VK_GAMEPAD_DPAD_RIGHT, Key.None)] + [InlineData(VK_GAMEPAD_MENU, Key.None)] + [InlineData(VK_GAMEPAD_VIEW, Key.None)] + [InlineData(VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON, Key.None)] + [InlineData(VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON, Key.None)] + [InlineData(VK_GAMEPAD_LEFT_THUMBSTICK_UP, Key.None)] + [InlineData(VK_GAMEPAD_LEFT_THUMBSTICK_DOWN, Key.None)] + [InlineData(VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT, Key.None)] + [InlineData(VK_GAMEPAD_LEFT_THUMBSTICK_LEFT, Key.None)] + [InlineData(VK_GAMEPAD_RIGHT_THUMBSTICK_UP, Key.None)] + [InlineData(VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN, Key.None)] + [InlineData(VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT, Key.None)] + [InlineData(VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT, Key.None)] + [InlineData(VK_OEM_4, Key.OemOpenBrackets)] + [InlineData(VK_OEM_5, Key.OemPipe)] + [InlineData(VK_OEM_6, Key.OemCloseBrackets)] + [InlineData(VK_OEM_7, Key.OemQuotes)] + [InlineData(VK_OEM_8, Key.Oem8)] + [InlineData(0xE0, Key.None)] + [InlineData(VK_OEM_AX, Key.None)] + [InlineData(VK_OEM_102, Key.OemBackslash)] + [InlineData(VK_ICO_HELP, Key.None)] + [InlineData(VK_ICO_00, Key.None)] + [InlineData(VK_PROCESSKEY, Key.ImeProcessed)] + [InlineData(VK_ICO_CLEAR, Key.None)] + [InlineData(VK_PACKET, Key.None)] + [InlineData(0xE8, Key.None)] + [InlineData(VK_OEM_RESET, Key.None)] + [InlineData(VK_OEM_JUMP, Key.None)] + [InlineData(VK_OEM_PA1, Key.None)] + [InlineData(VK_OEM_PA2, Key.None)] + [InlineData(VK_OEM_PA3, Key.None)] + [InlineData(VK_OEM_WSCTRL, Key.None)] + [InlineData(VK_OEM_CUSEL, Key.None)] + [InlineData(VK_OEM_ATTN, Key.OemAttn)] + [InlineData(VK_OEM_FINISH, Key.OemFinish)] + [InlineData(VK_OEM_COPY, Key.OemCopy)] + [InlineData(VK_OEM_AUTO, Key.OemAuto)] + [InlineData(VK_OEM_ENLW, Key.OemEnlw)] + [InlineData(VK_OEM_BACKTAB, Key.OemBackTab)] + [InlineData(VK_ATTN, Key.Attn)] + [InlineData(VK_CRSEL, Key.CrSel)] + [InlineData(VK_EXSEL, Key.ExSel)] + [InlineData(VK_EREOF, Key.EraseEof)] + [InlineData(VK_PLAY, Key.Play)] + [InlineData(VK_ZOOM, Key.Zoom)] + [InlineData(VK_NONAME, Key.NoName)] + [InlineData(VK_PA1, Key.Pa1)] + [InlineData(VK_OEM_CLEAR, Key.OemClear)] + [InlineData(0xFF, Key.None)] + [InlineData(0x100, Key.None)] + [InlineData(int.MinValue, Key.None)] + [InlineData(-1, Key.None)] + [InlineData(int.MaxValue, Key.None)] + public void KeyFromVirtualKey_Invoke_ReturnsExpected(int virtualKey, Key expected) + { + Assert.Equal(expected, KeyInterop.KeyFromVirtualKey(virtualKey)); + } + + [Theory] + [InlineData(Key.None, 0)] + [InlineData(Key.Cancel, VK_CANCEL)] + [InlineData(Key.Back, VK_BACK)] + [InlineData(Key.Tab, VK_TAB)] + [InlineData(Key.Clear, VK_CLEAR)] + [InlineData(Key.Return, VK_RETURN)] + //[InlineData(Key.Enter, VK_RETURN)] + [InlineData(Key.Pause, VK_PAUSE)] + [InlineData(Key.Capital, VK_CAPITAL)] + //[InlineData(Key.CapsLock, VK_CAPITAL)] + [InlineData(Key.KanaMode, VK_KANA)] + //[InlineData(Key.HangulMode, VK_KANA)] + [InlineData(Key.JunjaMode, VK_JUNJA)] + [InlineData(Key.FinalMode, VK_FINAL)] + [InlineData(Key.HanjaMode, VK_KANJI)] + //[InlineData(Key.KanjiMode, VK_KANJI)] + [InlineData(Key.Escape, VK_ESCAPE)] + [InlineData(Key.ImeConvert, VK_CONVERT)] + [InlineData(Key.ImeNonConvert, VK_NONCONVERT)] + [InlineData(Key.ImeAccept, VK_ACCEPT)] + [InlineData(Key.ImeModeChange, VK_MODECHANGE)] + [InlineData(Key.Space, VK_SPACE)] + [InlineData(Key.Prior, VK_PRIOR)] + [InlineData(Key.Next, VK_NEXT)] + //[InlineData(Key.PageDown, VK_NEXT)] + [InlineData(Key.End, VK_END)] + [InlineData(Key.Home, VK_HOME)] + [InlineData(Key.Left, VK_LEFT)] + [InlineData(Key.Up, VK_UP)] + [InlineData(Key.Right, VK_RIGHT)] + [InlineData(Key.Down, VK_DOWN)] + [InlineData(Key.Select, VK_SELECT)] + [InlineData(Key.Print, VK_PRINT)] + [InlineData(Key.Execute, VK_EXECUTE)] + [InlineData(Key.Snapshot, VK_SNAPSHOT)] + //[InlineData(Key.PrintScreen, VK_SNAPSHOT)] + [InlineData(Key.Insert, VK_INSERT)] + [InlineData(Key.Delete, VK_DELETE)] + [InlineData(Key.Help, VK_HELP)] + [InlineData(Key.D0, VK_0)] + [InlineData(Key.D1, VK_1)] + [InlineData(Key.D2, VK_2)] + [InlineData(Key.D3, VK_3)] + [InlineData(Key.D4, VK_4)] + [InlineData(Key.D5, VK_5)] + [InlineData(Key.D6, VK_6)] + [InlineData(Key.D7, VK_7)] + [InlineData(Key.D8, VK_8)] + [InlineData(Key.D9, VK_9)] + [InlineData(Key.A, VK_A)] + [InlineData(Key.B, VK_B)] + [InlineData(Key.C, VK_C)] + [InlineData(Key.D, VK_D)] + [InlineData(Key.E, VK_E)] + [InlineData(Key.F, VK_F)] + [InlineData(Key.G, VK_G)] + [InlineData(Key.H, VK_H)] + [InlineData(Key.I, VK_I)] + [InlineData(Key.J, VK_J)] + [InlineData(Key.K, VK_K)] + [InlineData(Key.L, VK_L)] + [InlineData(Key.M, VK_M)] + [InlineData(Key.N, VK_N)] + [InlineData(Key.O, VK_O)] + [InlineData(Key.P, VK_P)] + [InlineData(Key.Q, VK_Q)] + [InlineData(Key.R, VK_R)] + [InlineData(Key.S, VK_S)] + [InlineData(Key.T, VK_T)] + [InlineData(Key.U, VK_U)] + [InlineData(Key.V, VK_V)] + [InlineData(Key.W, VK_W)] + [InlineData(Key.X, VK_X)] + [InlineData(Key.Y, VK_Y)] + [InlineData(Key.Z, VK_Z)] + [InlineData(Key.LWin, VK_LWIN)] + [InlineData(Key.RWin, VK_RWIN)] + [InlineData(Key.Apps, VK_APPS)] + [InlineData(Key.Sleep, VK_SLEEP)] + [InlineData(Key.NumPad0, VK_NUMPAD0)] + [InlineData(Key.NumPad1, VK_NUMPAD1)] + [InlineData(Key.NumPad2, VK_NUMPAD2)] + [InlineData(Key.NumPad3, VK_NUMPAD3)] + [InlineData(Key.NumPad4, VK_NUMPAD4)] + [InlineData(Key.NumPad5, VK_NUMPAD5)] + [InlineData(Key.NumPad6, VK_NUMPAD6)] + [InlineData(Key.NumPad7, VK_NUMPAD7)] + [InlineData(Key.NumPad8, VK_NUMPAD8)] + [InlineData(Key.NumPad9, VK_NUMPAD9)] + [InlineData(Key.Multiply, VK_MULTIPLY)] + [InlineData(Key.Add, VK_ADD)] + [InlineData(Key.Separator, VK_SEPARATOR)] + [InlineData(Key.Subtract, VK_SUBTRACT)] + [InlineData(Key.Decimal, VK_DECIMAL)] + [InlineData(Key.Divide, VK_DIVIDE)] + [InlineData(Key.F1, VK_F1)] + [InlineData(Key.F2, VK_F2)] + [InlineData(Key.F3, VK_F3)] + [InlineData(Key.F4, VK_F4)] + [InlineData(Key.F5, VK_F5)] + [InlineData(Key.F6, VK_F6)] + [InlineData(Key.F7, VK_F7)] + [InlineData(Key.F8, VK_F8)] + [InlineData(Key.F9, VK_F9)] + [InlineData(Key.F10, VK_F10)] + [InlineData(Key.F11, VK_F11)] + [InlineData(Key.F12, VK_F12)] + [InlineData(Key.F13, VK_F13)] + [InlineData(Key.F14, VK_F14)] + [InlineData(Key.F15, VK_F15)] + [InlineData(Key.F16, VK_F16)] + [InlineData(Key.F17, VK_F17)] + [InlineData(Key.F18, VK_F18)] + [InlineData(Key.F19, VK_F19)] + [InlineData(Key.F20, VK_F20)] + [InlineData(Key.F21, VK_F21)] + [InlineData(Key.F22, VK_F22)] + [InlineData(Key.F23, VK_F23)] + [InlineData(Key.F24, VK_F24)] + [InlineData(Key.NumLock, VK_NUMLOCK)] + [InlineData(Key.Scroll, VK_SCROLL)] + [InlineData(Key.LeftShift, VK_LSHIFT)] + [InlineData(Key.RightShift, VK_RSHIFT)] + [InlineData(Key.LeftCtrl, VK_LCONTROL)] + [InlineData(Key.RightCtrl, VK_RCONTROL)] + [InlineData(Key.LeftAlt, VK_LMENU)] + [InlineData(Key.RightAlt, VK_RMENU)] + [InlineData(Key.BrowserBack, VK_BROWSER_BACK)] + [InlineData(Key.BrowserForward, VK_BROWSER_FORWARD)] + [InlineData(Key.BrowserRefresh, VK_BROWSER_REFRESH)] + [InlineData(Key.BrowserStop, VK_BROWSER_STOP)] + [InlineData(Key.BrowserSearch, VK_BROWSER_SEARCH)] + [InlineData(Key.BrowserFavorites, VK_BROWSER_FAVORITES)] + [InlineData(Key.BrowserHome, VK_BROWSER_HOME)] + [InlineData(Key.VolumeMute, VK_VOLUME_MUTE)] + [InlineData(Key.VolumeDown, VK_VOLUME_DOWN)] + [InlineData(Key.VolumeUp, VK_VOLUME_UP)] + [InlineData(Key.MediaNextTrack, VK_MEDIA_NEXT_TRACK)] + [InlineData(Key.MediaPreviousTrack, VK_MEDIA_PREV_TRACK)] + [InlineData(Key.MediaStop, VK_MEDIA_STOP)] + [InlineData(Key.MediaPlayPause, VK_MEDIA_PLAY_PAUSE)] + [InlineData(Key.LaunchMail, VK_LAUNCH_MAIL)] + [InlineData(Key.SelectMedia, VK_LAUNCH_MEDIA_SELECT)] + [InlineData(Key.LaunchApplication1, VK_LAUNCH_APP1)] + [InlineData(Key.LaunchApplication2, VK_LAUNCH_APP2)] + [InlineData(Key.OemSemicolon, VK_OEM_1)] + [InlineData(Key.OemPlus, VK_OEM_PLUS)] + [InlineData(Key.OemComma, VK_OEM_COMMA)] + [InlineData(Key.OemMinus, VK_OEM_MINUS)] + [InlineData(Key.OemPeriod, VK_OEM_PERIOD)] + [InlineData(Key.Oem2, VK_OEM_2)] + //[InlineData(Key.OemQuestion, VK_OEM_2)] + [InlineData(Key.Oem3, VK_OEM_3)] + //[InlineData(Key.OemTilde, VK_OEM_3)] + [InlineData(Key.AbntC1, VK_C1)] + [InlineData(Key.AbntC2, VK_C2)] + [InlineData(Key.Oem4, VK_OEM_4)] + //[InlineData(Key.OemOpenBrackets, VK_OEM_4)] + //[InlineData(Key.Oem5, VK_OEM_5)] + [InlineData(Key.OemPipe, VK_OEM_5)] + [InlineData(Key.Oem6, VK_OEM_6)] + //[InlineData(Key.OemCloseBrackets, VK_OEM_6)] + [InlineData(Key.Oem7, VK_OEM_7)] + //[InlineData(Key.OemQuotes, VK_OEM_7)] + [InlineData(Key.Oem8, VK_OEM_8)] + [InlineData(Key.Oem102, VK_OEM_102)] + //[InlineData(Key.OemBackslash, VK_OEM_102)] + [InlineData(Key.ImeProcessed, VK_PROCESSKEY)] + [InlineData(Key.OemAttn, VK_OEM_ATTN)] + //[InlineData(Key.DbeAlphanumeric, VK_OEM_ATTN)] + [InlineData(Key.OemFinish, VK_OEM_FINISH)] + //[InlineData(Key.DbeKatakana, VK_OEM_FINISH)] + [InlineData(Key.OemCopy, VK_OEM_COPY)] + //[InlineData(Key.DbeHiragana, VK_OEM_COPY)] + [InlineData(Key.OemAuto, VK_OEM_AUTO)] + //[InlineData(Key.DbeSbcsChar, VK_OEM_AUTO)] + [InlineData(Key.OemEnlw, VK_OEM_ENLW)] + //[InlineData(Key.DbeDbcsChar, VK_OEM_ENLW)] + [InlineData(Key.OemBackTab, VK_OEM_BACKTAB)] + //[InlineData(Key.DbeRoman, VK_OEM_BACKTAB)] + [InlineData(Key.Attn, VK_ATTN)] + //[InlineData(Key.DbeNoRoman, VK_ATTN)] + [InlineData(Key.CrSel, VK_CRSEL)] + //[InlineData(Key.DbeEnterWordRegisterMode, VK_CRSEL)] + [InlineData(Key.ExSel, VK_EXSEL)] + //[InlineData(Key.DbeEnterImeConfigureMode, VK_EXSEL)] + [InlineData(Key.EraseEof, VK_EREOF)] + //[InlineData(Key.DbeFlushString, VK_EREOF)] + [InlineData(Key.Play, VK_PLAY)] + //[InlineData(Key.DbeCodeInput, VK_PLAY)] + [InlineData(Key.Zoom, VK_ZOOM)] + //[InlineData(Key.DbeNoCodeInput, VK_ZOOM)] + [InlineData(Key.NoName, VK_NONAME)] + //[InlineData(Key.DbeDetermineString, VK_NONAME)] + [InlineData(Key.Pa1, VK_PA1)] + //[InlineData(Key.DbeEnterDialogConversionMode, VK_PA1)] + [InlineData(Key.OemClear, VK_OEM_CLEAR)] + [InlineData(Key.DeadCharProcessed, 0)] + [InlineData((Key)int.MinValue, 0)] + [InlineData((Key)(-1), 0)] + [InlineData(Key.DeadCharProcessed + 1, 0)] + [InlineData((Key)int.MaxValue, 0)] + public void VirtualKeyFromKey_Invoke_ReturnsExpected(Key key, int expected) + { + Assert.Equal(expected, KeyInterop.VirtualKeyFromKey(key)); + } + + private const int VK_LBUTTON = 0x01; + private const int VK_RBUTTON = 0x02; + private const int VK_CANCEL = 0x03; + private const int VK_MBUTTON = 0x04; + private const int VK_XBUTTON1 = 0x05; + private const int VK_XBUTTON2 = 0x06; + private const int VK_BACK = 0x08; + private const int VK_TAB = 0x09; + private const int VK_CLEAR = 0x0C; + private const int VK_RETURN = 0x0D; + private const int VK_SHIFT = 0x10; + private const int VK_CONTROL = 0x11; + private const int VK_MENU = 0x12; + private const int VK_PAUSE = 0x13; + private const int VK_CAPITAL = 0x14; + private const int VK_KANA = 0x15; + private const int VK_HANGEUL = 0x15; + private const int VK_HANGUL = 0x15; + private const int VK_JUNJA = 0x17; + private const int VK_FINAL = 0x18; + private const int VK_HANJA = 0x19; + private const int VK_KANJI = 0x19; + private const int VK_ESCAPE = 0x1B; + private const int VK_CONVERT = 0x1C; + private const int VK_NONCONVERT = 0x1D; + private const int VK_ACCEPT = 0x1E; + private const int VK_MODECHANGE = 0x1F; + private const int VK_SPACE = 0x20; + private const int VK_PRIOR = 0x21; + private const int VK_NEXT = 0x22; + private const int VK_END = 0x23; + private const int VK_HOME = 0x24; + private const int VK_LEFT = 0x25; + private const int VK_UP = 0x26; + private const int VK_RIGHT = 0x27; + private const int VK_DOWN = 0x28; + private const int VK_SELECT = 0x29; + private const int VK_PRINT = 0x2A; + private const int VK_EXECUTE = 0x2B; + private const int VK_SNAPSHOT = 0x2C; + private const int VK_INSERT = 0x2D; + private const int VK_DELETE = 0x2E; + private const int VK_HELP = 0x2F; + private const int VK_0 = 0x30; + private const int VK_1 = 0x31; + private const int VK_2 = 0x32; + private const int VK_3 = 0x33; + private const int VK_4 = 0x34; + private const int VK_5 = 0x35; + private const int VK_6 = 0x36; + private const int VK_7 = 0x37; + private const int VK_8 = 0x38; + private const int VK_9 = 0x39; + private const int VK_A = 0x41; + private const int VK_B = 0x42; + private const int VK_C = 0x43; + private const int VK_D = 0x44; + private const int VK_E = 0x45; + private const int VK_F = 0x46; + private const int VK_G = 0x47; + private const int VK_H = 0x48; + private const int VK_I = 0x49; + private const int VK_J = 0x4A; + private const int VK_K = 0x4B; + private const int VK_L = 0x4C; + private const int VK_M = 0x4D; + private const int VK_N = 0x4E; + private const int VK_O = 0x4F; + private const int VK_P = 0x50; + private const int VK_Q = 0x51; + private const int VK_R = 0x52; + private const int VK_S = 0x53; + private const int VK_T = 0x54; + private const int VK_U = 0x55; + private const int VK_V = 0x56; + private const int VK_W = 0x57; + private const int VK_X = 0x58; + private const int VK_Y = 0x59; + private const int VK_Z = 0x5A; + private const int VK_LWIN = 0x5B; + private const int VK_RWIN = 0x5C; + private const int VK_APPS = 0x5D; + private const int VK_POWER = 0x5E; + private const int VK_SLEEP = 0x5F; + private const int VK_NUMPAD0 = 0x60; + private const int VK_NUMPAD1 = 0x61; + private const int VK_NUMPAD2 = 0x62; + private const int VK_NUMPAD3 = 0x63; + private const int VK_NUMPAD4 = 0x64; + private const int VK_NUMPAD5 = 0x65; + private const int VK_NUMPAD6 = 0x66; + private const int VK_NUMPAD7 = 0x67; + private const int VK_NUMPAD8 = 0x68; + private const int VK_NUMPAD9 = 0x69; + private const int VK_MULTIPLY = 0x6A; + private const int VK_ADD = 0x6B; + private const int VK_SEPARATOR = 0x6C; + private const int VK_SUBTRACT = 0x6D; + private const int VK_DECIMAL = 0x6E; + private const int VK_DIVIDE = 0x6F; + private const int VK_F1 = 0x70; + private const int VK_F2 = 0x71; + private const int VK_F3 = 0x72; + private const int VK_F4 = 0x73; + private const int VK_F5 = 0x74; + private const int VK_F6 = 0x75; + private const int VK_F7 = 0x76; + private const int VK_F8 = 0x77; + private const int VK_F9 = 0x78; + private const int VK_F10 = 0x79; + private const int VK_F11 = 0x7A; + private const int VK_F12 = 0x7B; + private const int VK_F13 = 0x7C; + private const int VK_F14 = 0x7D; + private const int VK_F15 = 0x7E; + private const int VK_F16 = 0x7F; + private const int VK_F17 = 0x80; + private const int VK_F18 = 0x81; + private const int VK_F19 = 0x82; + private const int VK_F20 = 0x83; + private const int VK_F21 = 0x84; + private const int VK_F22 = 0x85; + private const int VK_F23 = 0x86; + private const int VK_F24 = 0x87; + private const int VK_NUMLOCK = 0x90; + private const int VK_SCROLL = 0x91; + private const int VK_OEM_NEC_EQUAL = 0x92; + private const int VK_OEM_FJ_JISHO = 0x92; + private const int VK_OEM_FJ_MASSHOU = 0x93; + private const int VK_OEM_FJ_TOUROKU = 0x94; + private const int VK_OEM_FJ_LOYA = 0x95; + private const int VK_OEM_FJ_ROYA = 0x96; + private const int VK_LSHIFT = 0xA0; + private const int VK_RSHIFT = 0xA1; + private const int VK_LCONTROL = 0xA2; + private const int VK_RCONTROL = 0xA3; + private const int VK_RMENU = 0xA5; + private const int VK_LMENU = 0xA4; + private const int VK_BROWSER_BACK = 0xA6; + private const int VK_BROWSER_FORWARD = 0xA7; + private const int VK_BROWSER_REFRESH = 0xA8; + private const int VK_BROWSER_STOP = 0xA9; + private const int VK_BROWSER_SEARCH = 0xAA; + private const int VK_BROWSER_FAVORITES = 0xAB; + private const int VK_BROWSER_HOME = 0xAC; + private const int VK_VOLUME_MUTE = 0xAD; + private const int VK_VOLUME_DOWN = 0xAE; + private const int VK_VOLUME_UP = 0xAF; + private const int VK_MEDIA_NEXT_TRACK = 0xB0; + private const int VK_MEDIA_PREV_TRACK = 0xB1; + private const int VK_MEDIA_STOP = 0xB2; + private const int VK_MEDIA_PLAY_PAUSE = 0xB3; + private const int VK_LAUNCH_MAIL = 0xB4; + private const int VK_LAUNCH_MEDIA_SELECT = 0xB5; + private const int VK_LAUNCH_APP1 = 0xB6; + private const int VK_LAUNCH_APP2 = 0xB7; + private const int VK_OEM_1 = 0xBA; + private const int VK_OEM_PLUS = 0xBB; + private const int VK_OEM_COMMA = 0xBC; + private const int VK_OEM_MINUS = 0xBD; + private const int VK_OEM_PERIOD = 0xBE; + private const int VK_OEM_2 = 0xBF; + private const int VK_OEM_3 = 0xC0; + private const int VK_C1 = 0xC1; // Brazilian ABNT_C1 key (not defined in winuser.h). + private const int VK_C2 = 0xC2; // Brazilian ABNT_C2 key (not defined in winuser.h). + private const int VK_GAMEPAD_A = 0xC3; + private const int VK_GAMEPAD_B = 0xC4; + private const int VK_GAMEPAD_X = 0xC5; + private const int VK_GAMEPAD_Y = 0xC6; + private const int VK_GAMEPAD_RIGHT_SHOULDER = 0xC7; + private const int VK_GAMEPAD_LEFT_SHOULDER = 0xC8; + private const int VK_GAMEPAD_LEFT_TRIGGER = 0xC9; + private const int VK_GAMEPAD_RIGHT_TRIGGER = 0xCA; + private const int VK_GAMEPAD_DPAD_UP = 0xCB; + private const int VK_GAMEPAD_DPAD_DOWN = 0xCC; + private const int VK_GAMEPAD_DPAD_LEFT = 0xCD; + private const int VK_GAMEPAD_DPAD_RIGHT = 0xCE; + private const int VK_GAMEPAD_MENU = 0xCF; + private const int VK_GAMEPAD_VIEW = 0xD0; + private const int VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON = 0xD1; + private const int VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON = 0xD2; + private const int VK_GAMEPAD_LEFT_THUMBSTICK_UP = 0xD3; + private const int VK_GAMEPAD_LEFT_THUMBSTICK_DOWN = 0xD4; + private const int VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT = 0xD5; + private const int VK_GAMEPAD_LEFT_THUMBSTICK_LEFT = 0xD6; + private const int VK_GAMEPAD_RIGHT_THUMBSTICK_UP = 0xD7; + private const int VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN = 0xD8; + private const int VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT = 0xD9; + private const int VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT = 0xDA; + private const int VK_OEM_4 = 0xDB; + private const int VK_OEM_5 = 0xDC; + private const int VK_OEM_6 = 0xDD; + private const int VK_OEM_7 = 0xDE; + private const int VK_OEM_8 = 0xDF; + private const int VK_OEM_AX = 0xE1; + private const int VK_OEM_102 = 0xE2; + private const int VK_ICO_HELP = 0xE3; + private const int VK_ICO_00= 0xE4; + private const int VK_PROCESSKEY = 0xE5; + private const int VK_ICO_CLEAR = 0xE6; + private const int VK_PACKET = 0xE7; + private const int VK_OEM_RESET = 0xE9; + private const int VK_OEM_JUMP = 0xEA; + private const int VK_OEM_PA1 = 0xEB; + private const int VK_OEM_PA2 = 0xEC; + private const int VK_OEM_PA3 = 0xED; + private const int VK_OEM_WSCTRL = 0xEE; + private const int VK_OEM_CUSEL = 0xEF; + private const int VK_OEM_ATTN = 0xF0; + private const int VK_OEM_FINISH = 0xF1; + private const int VK_OEM_COPY = 0xF2; + private const int VK_OEM_AUTO = 0xF3; + private const int VK_OEM_ENLW = 0xF4; + private const int VK_OEM_BACKTAB = 0xF5; + private const int VK_ATTN = 0xF6; + private const int VK_CRSEL = 0xF7; + private const int VK_EXSEL = 0xF8; + private const int VK_EREOF = 0xF9; + private const int VK_PLAY = 0xFA; + private const int VK_ZOOM = 0xFB; + private const int VK_NONAME = 0xFC; + private const int VK_PA1 = 0xFD; + private const int VK_OEM_CLEAR = 0xFE; +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/KeyTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/KeyTests.cs new file mode 100644 index 00000000000..c876e777ecb --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/KeyTests.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Windows.Markup; + +namespace System.Windows.Input.Tests; + +public class KeyTests +{ + [Fact] + public void TypeConverter_Get_ReturnsExpected() + { + Assert.IsType(TypeDescriptor.GetConverter(typeof(Key))); + } + + [Fact] + public void ValueSerializer_Get_ReturnsExpected() + { + Assert.IsType(ValueSerializer.GetSerializerFor(typeof(Key))); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/KeyValueSerializerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/KeyValueSerializerTests.cs new file mode 100644 index 00000000000..d3a2ebdc484 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/KeyValueSerializerTests.cs @@ -0,0 +1,144 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Windows.Markup; + +namespace System.Windows.Input.Tests; + +public class KeyValueSerializerTests +{ + public static IEnumerable CanConvertToString_TestData() + { + yield return new object?[] { null, false }; + yield return new object?[] { string.Empty, false }; + yield return new object?[] { "value", false }; + yield return new object?[] { new object(), false }; + yield return new object?[] { ModifierKeys.None, false }; + + yield return new object?[] { Key.None, true }; + yield return new object?[] { Key.Cancel, true }; + yield return new object?[] { Key.A, true }; + yield return new object?[] { Key.Pa1, true }; + yield return new object?[] { Key.OemClear, true }; + yield return new object?[] { Key.DeadCharProcessed, false }; + yield return new object?[] { Key.None - 1, false }; + yield return new object?[] { Key.DeadCharProcessed + 1, false }; + } + + [Theory] + [MemberData(nameof(CanConvertToString_TestData))] + public void CanConvertToString_Invoke_ReturnsExpected(object value, bool expected) + { + var serializer = new KeyValueSerializer(); + Assert.Equal(expected, serializer.CanConvertToString(value, null)); + Assert.Equal(expected, serializer.CanConvertToString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(KeyConverterTests.ConvertTo_KeyToString_TestData), MemberType = typeof(KeyConverterTests))] + public void ConvertToString_Invoke_ReturnsExpected(Key key, string expected) + { + var serializer = new KeyValueSerializer(); + Assert.Equal(expected, serializer.ConvertToString(key, null)); + Assert.Equal(expected, serializer.ConvertToString(key, new CustomValueSerializerContext())); + } + + [Theory] + [InlineData((Key)int.MinValue)] + [InlineData((Key)(-1))] + [InlineData(Key.DeadCharProcessed + 1)] + [InlineData((Key)int.MaxValue)] + public void ConvertToString_InvalidKey_ThrowsNotSupportedException(Key key) + { + var serializer = new KeyValueSerializer(); + Assert.Throws(() => serializer.ConvertToString(key, null)); + Assert.Throws(() => serializer.ConvertToString(key, new CustomValueSerializerContext())); + } + + [Theory] + [InlineData(null)] + // TODO: this should not throw InvalidCastException. + //[InlineData("", "")] + //[InlineData("value", "value")] + public void ConvertToString_InvokeNotKeyToStringNull_ThrowsNotSupportedException(object? value) + { + var serializer = new KeyValueSerializer(); + Assert.Throws(() => serializer.ConvertToString(value, null)); + Assert.Throws(() => serializer.ConvertToString(value, new CustomValueSerializerContext())); + } + + [Theory] + [InlineData("")] + [InlineData("value")] + public void ConvertToString_InvokeNotKeyNotNull_ThrowsInvalidCastException(object value) + { + // TODO: this should not throw InvalidCastException. + var serializer = new KeyValueSerializer(); + Assert.Throws(() => serializer.ConvertToString(value, null)); + Assert.Throws(() => serializer.ConvertToString(value, new CustomValueSerializerContext())); + } + + public static IEnumerable CanConvertFromString_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + } + + [Theory] + [MemberData(nameof(CanConvertFromString_TestData))] + public void CanConvertFromString_Invoke_ReturnsTrue(string value) + { + var serializer = new KeyValueSerializer(); + Assert.True(serializer.CanConvertFromString(value, null)); + Assert.True(serializer.CanConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(KeyConverterTests.ConvertFrom_TestData), MemberType = typeof(KeyConverterTests))] + public void ConvertFromString_Invoke_ReturnsExpected(string value, Key expected) + { + var serializer = new KeyValueSerializer(); + Assert.Equal(expected, serializer.ConvertFromString(value, null)); + Assert.Equal(expected, serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Fact] + public void ConvertFromString_NullValue_ThrowsNotSupportedException() + { + var serializer = new KeyValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(null, null)); + Assert.Throws(() => serializer.ConvertFromString(null, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(KeyConverterTests.ConvertFrom_InvalidValue_TestData), MemberType = typeof(KeyConverterTests))] + public void ConvertFromString_InvalidValue_ThrowsInvalidOperationException(string value) + { + var serializer = new KeyValueSerializer(); + // TODO: add paramName. + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + private class CustomValueSerializerContext : IValueSerializerContext + { + public IContainer Container => throw new NotImplementedException(); + + public object Instance => throw new NotImplementedException(); + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(PropertyDescriptor descriptor) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(Type type) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/ModifierKeysConverterTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/ModifierKeysConverterTests.cs new file mode 100644 index 00000000000..14688c7cad5 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/ModifierKeysConverterTests.cs @@ -0,0 +1,318 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.ComponentModel.Design.Serialization; +using System.Globalization; + +namespace System.Windows.Input.Tests; + +public class ModifierKeysConverterTests +{ + public static IEnumerable CanConvertTo_TestData() + { + yield return new object?[] { null, null, false }; + yield return new object?[] { null, typeof(object), false }; + yield return new object?[] { null, typeof(string), false }; + yield return new object?[] { null, typeof(InstanceDescriptor), false }; + yield return new object?[] { null, typeof(Key), false }; + yield return new object?[] { null, typeof(ModifierKeys), false }; + yield return new object?[] { new CustomTypeDescriptorContext(), null, false }; + yield return new object?[] { new CustomTypeDescriptorContext(), typeof(object), false }; + yield return new object?[] { new CustomTypeDescriptorContext(), typeof(string), false }; + yield return new object?[] { new CustomTypeDescriptorContext(), typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext(), typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext(), typeof(ModifierKeys), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, null, false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, typeof(object), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, typeof(string), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = new object() }, typeof(ModifierKeys), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.None }, null, false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.None }, typeof(object), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.None }, typeof(string), true }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.None }, typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.None }, typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.None }, typeof(ModifierKeys), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.Control }, null, false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.Control }, typeof(object), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.Control }, typeof(string), true }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.Control }, typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.Control }, typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.Control }, typeof(ModifierKeys), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Windows | ModifierKeys.Shift }, null, false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Windows | ModifierKeys.Shift }, typeof(object), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Windows | ModifierKeys.Shift }, typeof(string), true }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Windows | ModifierKeys.Shift }, typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Windows | ModifierKeys.Shift }, typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Windows | ModifierKeys.Shift }, typeof(ModifierKeys), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = (ModifierKeys)(-1) }, null, false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = (ModifierKeys)(-1) }, typeof(object), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = (ModifierKeys)(-1) }, typeof(string), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = (ModifierKeys)(-1) }, typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = (ModifierKeys)(-1) }, typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = (ModifierKeys)(-1) }, typeof(ModifierKeys), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = (ModifierKeys)0x10 }, null, false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = (ModifierKeys)0x10 }, typeof(object), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = (ModifierKeys)0x10 }, typeof(string), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = (ModifierKeys)0x10 }, typeof(InstanceDescriptor), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = (ModifierKeys)0x10 }, typeof(Key), false }; + yield return new object?[] { new CustomTypeDescriptorContext { Instance = (ModifierKeys)0x10 }, typeof(ModifierKeys), false }; + } + + [Theory] + [MemberData(nameof(CanConvertTo_TestData))] + public void CanConvertTo_Invoke_ReturnsExpected(ITypeDescriptorContext context, Type destinationType, bool expected) + { + var converter = new ModifierKeysConverter(); + Assert.Equal(expected, converter.CanConvertTo(context, destinationType)); + } + + public static IEnumerable ConvertTo_ModifierKeysToString_TestData() + { + yield return new object[] { ModifierKeys.None, "" }; + yield return new object[] { ModifierKeys.Alt, "Alt" }; + yield return new object[] { ModifierKeys.Control, "Ctrl" }; + yield return new object[] { ModifierKeys.Shift, "Shift" }; + yield return new object[] { ModifierKeys.Windows, "Windows" }; + + yield return new object[] { ModifierKeys.Control | ModifierKeys.Alt, "Ctrl+Alt" }; + yield return new object[] { ModifierKeys.Control | ModifierKeys.Windows, "Ctrl+Windows" }; + yield return new object[] { ModifierKeys.Control | ModifierKeys.Shift, "Ctrl+Shift" }; + yield return new object[] { ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Windows, "Ctrl+Alt+Windows" }; + yield return new object[] { ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Windows | ModifierKeys.Shift, "Ctrl+Alt+Windows+Shift" }; + yield return new object[] { ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Shift, "Ctrl+Alt+Shift" }; + yield return new object[] { ModifierKeys.Control | ModifierKeys.Windows | ModifierKeys.Shift, "Ctrl+Windows+Shift" }; + yield return new object[] { ModifierKeys.Alt | ModifierKeys.Windows, "Alt+Windows" }; + yield return new object[] { ModifierKeys.Alt | ModifierKeys.Windows | ModifierKeys.Shift, "Alt+Windows+Shift" }; + yield return new object[] { ModifierKeys.Alt | ModifierKeys.Shift, "Alt+Shift" }; + yield return new object[] { ModifierKeys.Windows | ModifierKeys.Shift, "Windows+Shift" }; + } + + [Theory] + [MemberData(nameof(ConvertTo_ModifierKeysToString_TestData))] + public void ConvertTo_InvokeModifierKeysToString_ReturnsExpected(ModifierKeys value, string expected) + { + var converter = new ModifierKeysConverter(); + Assert.Equal(expected, converter.ConvertTo(value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + } + + public static IEnumerable ConvertTo_InvalidModifierKeys_TestData() + { + yield return new object[] { (ModifierKeys)int.MinValue }; + yield return new object[] { (ModifierKeys)(-1) }; + yield return new object[] { (ModifierKeys)32 }; + yield return new object[] { (ModifierKeys)int.MaxValue }; + } + + [Theory] + [MemberData(nameof(ConvertTo_InvalidModifierKeys_TestData))] + public void ConvertTo_InvalidModifierKeys_ThrowsInvalidEnumArgumentException(ModifierKeys value) + { + var converter = new ModifierKeysConverter(); + Assert.Throws("value", () => converter.ConvertTo(value, typeof(string))); + Assert.Throws("value", () => converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + Assert.Throws("value", () => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + } + + [Theory] + [InlineData(null)] + // TODO: this should not throw InvalidCastException. + //[InlineData("", "")] + //[InlineData("value", "value")] + public void ConvertTo_InvokeNotModifierKeysToStringNull_ThrowsNotSupportedException(object? value) + { + var converter = new ModifierKeysConverter(); + // TODO: should not throw NullReferenceException + //Assert.Throws(() => converter.ConvertTo(value, typeof(string))); + //Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + //Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + Assert.Throws(() => converter.ConvertTo(value, typeof(string))); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + } + + [Theory] + [InlineData("")] + [InlineData("value")] + public void ConvertTo_InvokeNotModifierKeysToStringNotNull_ThrowsInvalidCastException(object value) + { + // TODO: this should not throw InvalidCastException. + var converter = new ModifierKeysConverter(); + Assert.Throws(() => converter.ConvertTo(value, typeof(string))); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + } + + public static IEnumerable ConvertTo_CantConvert_TestData() + { + yield return new object?[] { null, typeof(object) }; + yield return new object?[] { string.Empty, typeof(object) }; + yield return new object?[] { "value", typeof(object) }; + yield return new object?[] { new object(), typeof(object) }; + yield return new object?[] { Key.None, typeof(object) }; + + yield return new object?[] { null, typeof(Key) }; + yield return new object?[] { string.Empty, typeof(Key) }; + yield return new object?[] { "value", typeof(Key) }; + yield return new object?[] { new object(), typeof(Key) }; + yield return new object?[] { Key.None, typeof(Key) }; + } + + [Theory] + [MemberData(nameof(ConvertTo_CantConvert_TestData))] + public void ConvertTo_CantConvert_ThrowsNotSupportedException(object value, Type destinationType) + { + var converter = new ModifierKeysConverter(); + Assert.Throws(() => converter.ConvertTo(value, destinationType)); + Assert.Throws(() => converter.ConvertTo(null, null, value, destinationType)); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, destinationType)); + } + + public static IEnumerable ConvertTo_NullDestinationType_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + yield return new object?[] { new object() }; + yield return new object?[] { ModifierKeys.None }; + } + + [Theory] + [MemberData(nameof(ConvertTo_NullDestinationType_TestData))] + public void ConvertTo_NullDestinationType_ThrowsArgumentNullException(object value) + { + var converter = new ModifierKeysConverter(); + Assert.Throws("destinationType", () => converter.ConvertTo(value, null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(null, null, Key.None, null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, Key.None, null!)); + } + + [Theory] + [InlineData(null, false)] + [InlineData(typeof(object), false)] + [InlineData(typeof(string), true)] + [InlineData(typeof(InstanceDescriptor), false)] + [InlineData(typeof(Key), false)] + public void CanConvertFrom_Invoke_ReturnsExpected(Type? sourceType, bool expected) + { + var converter = new ModifierKeysConverter(); + Assert.Equal(expected, converter.CanConvertFrom(sourceType!)); + Assert.Equal(expected, converter.CanConvertFrom(null, sourceType)); + Assert.Equal(expected, converter.CanConvertFrom(new CustomTypeDescriptorContext(), sourceType)); + } + + public static IEnumerable ConvertFrom_TestData() + { + yield return new object[] { "", ModifierKeys.None }; + yield return new object[] { " ", ModifierKeys.None }; + yield return new object[] { "+", ModifierKeys.None }; + yield return new object[] { " + ", ModifierKeys.None }; + yield return new object[] { "Alt", ModifierKeys.Alt }; + yield return new object[] { "ALT", ModifierKeys.Alt }; + yield return new object[] { " Alt ", ModifierKeys.Alt }; + yield return new object[] { "Ctrl", ModifierKeys.Control }; + yield return new object[] { "Control", ModifierKeys.Control }; + yield return new object[] { "Shift", ModifierKeys.Shift }; + yield return new object[] { "Windows", ModifierKeys.Windows }; + yield return new object[] { "Win", ModifierKeys.Windows }; + + yield return new object[] { "Ctrl+Alt", ModifierKeys.Control | ModifierKeys.Alt }; + yield return new object[] { " Ctrl + Alt ", ModifierKeys.Control | ModifierKeys.Alt }; + yield return new object[] { "Ctrl+Windows", ModifierKeys.Control | ModifierKeys.Windows }; + yield return new object[] { "Control+Win", ModifierKeys.Control | ModifierKeys.Windows }; + yield return new object[] { "Ctrl+Shift", ModifierKeys.Control | ModifierKeys.Shift }; + yield return new object[] { "Ctrl+Alt+Windows", ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Windows }; + yield return new object[] { "Ctrl+Alt+Windows+Shift", ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Windows | ModifierKeys.Shift }; + yield return new object[] { "Ctrl+Alt+Shift", ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Shift }; + yield return new object[] { "Ctrl+Windows+Shift", ModifierKeys.Control | ModifierKeys.Windows | ModifierKeys.Shift }; + yield return new object[] { "Alt+Windows", ModifierKeys.Alt | ModifierKeys.Windows }; + yield return new object[] { "Alt+Windows+Shift", ModifierKeys.Alt | ModifierKeys.Windows | ModifierKeys.Shift }; + yield return new object[] { "Alt+Shift", ModifierKeys.Alt | ModifierKeys.Shift }; + yield return new object[] { "Windows+Shift", ModifierKeys.Windows | ModifierKeys.Shift }; + + yield return new object[] { "Ctrl+", ModifierKeys.Control }; + yield return new object[] { "Ctrl+Windows+", ModifierKeys.Control | ModifierKeys.Windows }; + yield return new object[] { "Ctrl+Ctrl+Ctrl", ModifierKeys.Control }; + } + + [Theory] + [MemberData(nameof(ConvertFrom_TestData))] + public void ConvertFrom_InvokeStringValue_ReturnsExpected(string value, ModifierKeys expected) + { + var converter = new ModifierKeysConverter(); + Assert.Equal(expected, converter.ConvertFrom(value)); + Assert.Equal(expected, converter.ConvertFrom(null, null, value)); + Assert.Equal(expected, converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Fact] + public void ConvertFrom_NullValue_ThrowsNotSupportedException() + { + var converter = new ModifierKeysConverter(); + Assert.Throws(() => converter.ConvertFrom(null!)); + Assert.Throws(() => converter.ConvertFrom(null, null, null)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, null)); + } + + public static IEnumerable ConvertFrom_InvalidValue_TestData() + { + yield return new object[] { "_" }; + yield return new object[] { " _ " }; + yield return new object[] { "NOSUCHKEY" }; + yield return new object[] { " NOSUCHKEY " }; + yield return new object[] { "Control+NOSUCHKEY" }; + } + + [Theory] + [MemberData(nameof(ConvertFrom_InvalidValue_TestData))] + public void ConvertFrom_InvokeInvalidValue_ThrowsNotSupportedException(string value) + { + var converter = new ModifierKeysConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + public static IEnumerable ConvertFrom_CantConvert_TestData() + { + yield return new object[] { new object() }; + yield return new object[] { Key.A }; + yield return new object[] { ModifierKeys.None }; + } + + [Theory] + [MemberData(nameof(ConvertFrom_CantConvert_TestData))] + public void ConvertFrom_CantConvert_ThrowsNotSupportedException(object value) + { + var converter = new ModifierKeysConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + private class CustomTypeDescriptorContext : ITypeDescriptorContext + { + public IContainer Container => throw new NotImplementedException(); + + private object? _instance; + + public object Instance + { + get => _instance!; + set => _instance = value; + } + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/ModifierKeysTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/ModifierKeysTests.cs new file mode 100644 index 00000000000..4bcc92d1985 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/ModifierKeysTests.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Windows.Markup; + +namespace System.Windows.Input.Tests; + +public class ModifierKeysTests +{ + [Fact] + public void TypeConverter_Get_ReturnsExpected() + { + Assert.IsType(TypeDescriptor.GetConverter(typeof(ModifierKeys))); + } + + [Fact] + public void ValueSerializer_Get_ReturnsExpected() + { + Assert.IsType(ValueSerializer.GetSerializerFor(typeof(ModifierKeys))); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/ModifierKeysValueSerializerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/ModifierKeysValueSerializerTests.cs new file mode 100644 index 00000000000..6289eb9c505 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/ModifierKeysValueSerializerTests.cs @@ -0,0 +1,139 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Windows.Markup; + +namespace System.Windows.Input.Tests; + +public class ModifierKeysValueSerializerTests +{ + public static IEnumerable CanConvertToString_TestData() + { + yield return new object?[] { null, false }; + yield return new object?[] { string.Empty, false }; + yield return new object?[] { "value", false }; + yield return new object?[] { new object(), false }; + + yield return new object?[] { ModifierKeys.None, true }; + yield return new object?[] { ModifierKeys.Control, true }; + yield return new object?[] { ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Windows | ModifierKeys.Shift, true }; + yield return new object?[] { (ModifierKeys)(-1), false }; + yield return new object?[] { (ModifierKeys)0x10, false }; + } + + [Theory] + [MemberData(nameof(CanConvertToString_TestData))] + public void CanConvertToString_Invoke_ReturnsExpected(object value, bool expected) + { + var serializer = new ModifierKeysValueSerializer(); + Assert.Equal(expected, serializer.CanConvertToString(value, null)); + Assert.Equal(expected, serializer.CanConvertToString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(ModifierKeysConverterTests.ConvertTo_ModifierKeysToString_TestData), MemberType = typeof(ModifierKeysConverterTests))] + public void ConvertToString_Invoke_ReturnsExpected(ModifierKeys key, string expected) + { + var serializer = new ModifierKeysValueSerializer(); + Assert.Equal(expected, serializer.ConvertToString(key, null)); + Assert.Equal(expected, serializer.ConvertToString(key, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(ModifierKeysConverterTests.ConvertTo_InvalidModifierKeys_TestData), MemberType = typeof(ModifierKeysConverterTests))] + public void ConvertToString_InvalidModifierKeys_ThrowsInvalidEnumArgumentException(ModifierKeys key) + { + var serializer = new ModifierKeysValueSerializer(); + Assert.Throws("value", () => serializer.ConvertToString(key, null)); + Assert.Throws("value", () => serializer.ConvertToString(key, new CustomValueSerializerContext())); + } + + [Theory] + [InlineData(null)] + // TODO: this should not throw InvalidCastException. + //[InlineData("", "")] + //[InlineData("value", "value")] + public void ConvertToString_InvokeNotModifierKeysToStringNull_ThrowsNotSupportedException(object? value) + { + var serializer = new ModifierKeysValueSerializer(); + // TODO: should not throw NullReferenceException + //Assert.Throws(() => serializer.ConvertToString(value, null)); + //Assert.Throws(() => serializer.ConvertToString(value, new CustomValueSerializerContext())); + Assert.Throws(() => serializer.ConvertToString(value, null)); + Assert.Throws(() => serializer.ConvertToString(value, new CustomValueSerializerContext())); + } + + [Theory] + [InlineData("")] + [InlineData("value")] + public void ConvertToString_InvokeNotModifierKeysNotNull_ThrowsInvalidCastException(object value) + { + // TODO: this should not throw InvalidCastException. + var serializer = new ModifierKeysValueSerializer(); + Assert.Throws(() => serializer.ConvertToString(value, null)); + Assert.Throws(() => serializer.ConvertToString(value, new CustomValueSerializerContext())); + } + + public static IEnumerable CanConvertFromString_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + } + + [Theory] + [MemberData(nameof(CanConvertFromString_TestData))] + public void CanConvertFromString_Invoke_ReturnsTrue(string value) + { + var serializer = new ModifierKeysValueSerializer(); + Assert.True(serializer.CanConvertFromString(value, null)); + Assert.True(serializer.CanConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(ModifierKeysConverterTests.ConvertFrom_TestData), MemberType = typeof(ModifierKeysConverterTests))] + public void ConvertFromString_Invoke_ReturnsExpected(string value, ModifierKeys expected) + { + var serializer = new ModifierKeysValueSerializer(); + Assert.Equal(expected, serializer.ConvertFromString(value, null)); + Assert.Equal(expected, serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Fact] + public void ConvertFromString_NullValue_ThrowsNotSupportedException() + { + var serializer = new ModifierKeysValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(null, null)); + Assert.Throws(() => serializer.ConvertFromString(null, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(ModifierKeysConverterTests.ConvertFrom_InvalidValue_TestData), MemberType = typeof(ModifierKeysConverterTests))] + public void ConvertFromString_InvalidValue_ThrowsNotSupportedException(string value) + { + var serializer = new ModifierKeysValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + private class CustomValueSerializerContext : IValueSerializerContext + { + public IContainer Container => throw new NotImplementedException(); + + public object Instance => throw new NotImplementedException(); + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(PropertyDescriptor descriptor) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(Type type) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/TraversalRequestTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/TraversalRequestTests.cs new file mode 100644 index 00000000000..97a78085277 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Input/TraversalRequestTests.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; + +namespace System.Windows.Input.Tests; + +public class TraversalRequestTests +{ + [Theory] + [InlineData(FocusNavigationDirection.Next)] + [InlineData(FocusNavigationDirection.Previous)] + [InlineData(FocusNavigationDirection.First)] + [InlineData(FocusNavigationDirection.Last)] + [InlineData(FocusNavigationDirection.Left)] + [InlineData(FocusNavigationDirection.Right)] + [InlineData(FocusNavigationDirection.Up)] + [InlineData(FocusNavigationDirection.Down)] + public void Ctor_FocusNavigationDirection(FocusNavigationDirection focusNavigationDirection) + { + var request = new TraversalRequest(focusNavigationDirection); + Assert.Equal(focusNavigationDirection, request.FocusNavigationDirection); + Assert.False(request.Wrapped); + } + + [Theory] + [InlineData(FocusNavigationDirection.Next - 1)] + [InlineData(FocusNavigationDirection.Down + 1)] + public void Ctor_InvalidFocusNavigationDirection_ThrowsInvalidEnumArgumentException(FocusNavigationDirection focusNavigationDirection) + { + Assert.Throws("focusNavigationDirection", () => new TraversalRequest(focusNavigationDirection)); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Wrapped_Set_GetReturnsExpected(bool value) + { + var request = new TraversalRequest(FocusNavigationDirection.Next); + + // Set. + request.Wrapped = value; + Assert.Equal(value, request.Wrapped); + + // Set same. + request.Wrapped = value; + Assert.Equal(value, request.Wrapped); + + // Set different. + request.Wrapped = !value; + Assert.Equal(!value, request.Wrapped); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Int32RectConverterTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Int32RectConverterTests.cs new file mode 100644 index 00000000000..b0b7fc6800b --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Int32RectConverterTests.cs @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.ComponentModel.Design.Serialization; +using System.Globalization; + +namespace System.Windows.Tests; + +public class Int32RectConverterTests +{ + [Theory] + [InlineData(null, false)] + [InlineData(typeof(object), false)] + [InlineData(typeof(string), true)] + [InlineData(typeof(InstanceDescriptor), false)] + [InlineData(typeof(Int32Rect), false)] + public void CanConvertTo_Invoke_ReturnsExpected(Type? destinationType, bool expected) + { + var converter = new Int32RectConverter(); + Assert.Equal(expected, converter.CanConvertTo(null, destinationType)); + Assert.Equal(expected, converter.CanConvertTo(new CustomTypeDescriptorContext(), destinationType)); + } + + [Theory] + [MemberData(nameof(Int32RectTests.ToString_TestData), MemberType = typeof(Int32RectTests))] + public void ConvertTo_InvokeInt32RectToString_ReturnsExpected(Int32Rect matrix, string expected) + { + var converter = new Int32RectConverter(); + Assert.Equal(expected, converter.ConvertTo(matrix, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), null, matrix, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, matrix, typeof(string))); + } + + [Theory] + [MemberData(nameof(Int32RectTests.ToString_IFormatProviderCustom_TestData), MemberType = typeof(Int32RectTests))] + public void ConvertTo_InvokeInt32RectToStringCustomCulture_ReturnsExpected(Int32Rect matrix, string numberDecimalSeparator, string expected) + { + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + var converter = new Int32RectConverter(); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), culture, matrix, typeof(string))); + } + + [Theory] + [InlineData(null, "")] + [InlineData("", "")] + [InlineData("value", "value")] + [InlineData(1, "1")] + public void ConvertTo_InvokeNotInt32RectToString_ReturnsExpected(object? value, string expected) + { + var converter = new Int32RectConverter(); + Assert.Equal(expected, converter.ConvertTo(value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + } + + public static IEnumerable ConvertTo_CantConvert_TestData() + { + yield return new object?[] { null, typeof(object) }; + yield return new object?[] { string.Empty, typeof(object) }; + yield return new object?[] { "value", typeof(object) }; + yield return new object?[] { new object(), typeof(object) }; + yield return new object?[] { new Int32Rect(), typeof(object) }; + + yield return new object?[] { null, typeof(Int32Rect) }; + yield return new object?[] { string.Empty, typeof(Int32Rect) }; + yield return new object?[] { "value", typeof(Int32Rect) }; + yield return new object?[] { new object(), typeof(Int32Rect) }; + yield return new object?[] { new Int32Rect(), typeof(Int32Rect) }; + } + + [Theory] + [MemberData(nameof(ConvertTo_CantConvert_TestData))] + public void ConvertTo_CantConvert_ThrowsNotSupportedException(object value, Type destinationType) + { + var converter = new Int32RectConverter(); + Assert.Throws(() => converter.ConvertTo(value, destinationType)); + Assert.Throws(() => converter.ConvertTo(null, null, value, destinationType)); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, destinationType)); + } + + public static IEnumerable ConvertTo_NullDestinationType_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + yield return new object?[] { new object() }; + yield return new object?[] { new Int32Rect() }; + } + + [Theory] + [MemberData(nameof(ConvertTo_NullDestinationType_TestData))] + public void ConvertTo_NullDestinationType_ThrowsArgumentNullException(object value) + { + var converter = new Int32RectConverter(); + Assert.Throws("destinationType", () => converter.ConvertTo(value, null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(null, null, new Int32Rect(), null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, new Int32Rect(), null!)); + } + + [Theory] + [InlineData(null, false)] + [InlineData(typeof(object), false)] + [InlineData(typeof(string), true)] + [InlineData(typeof(InstanceDescriptor), true)] + [InlineData(typeof(Int32Rect), false)] + public void CanConvertFrom_Invoke_ReturnsExpected(Type? sourceType, bool expected) + { + var converter = new Int32RectConverter(); + Assert.Equal(expected, converter.CanConvertFrom(sourceType!)); + Assert.Equal(expected, converter.CanConvertFrom(null, sourceType)); + Assert.Equal(expected, converter.CanConvertFrom(new CustomTypeDescriptorContext(), sourceType)); + } + + [Theory] + [MemberData(nameof(Int32RectTests.Parse_TestData), MemberType = typeof(Int32RectTests))] + public void ConvertFrom_InvokeStringValue_ReturnsExpected(object value, Int32Rect expected) + { + var converter = new Int32RectConverter(); + Assert.Equal(expected, converter.ConvertFrom(value)); + Assert.Equal(expected, converter.ConvertFrom(null, null, value)); + Assert.Equal(expected, converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Fact] + public void ConvertFrom_NullValue_ThrowsNotSupportedException() + { + var converter = new Int32RectConverter(); + Assert.Throws(() => converter.ConvertFrom(null!)); + Assert.Throws(() => converter.ConvertFrom(null, null, null)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, null)); + } + + [Theory] + [MemberData(nameof(Int32RectTests.Parse_InvalidSource_TestData), MemberType = typeof(Int32RectTests))] + public void ConvertFrom_InvalidStringValue_ThrowsInvalidOperationException(object value) + { + var converter = new Int32RectConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Theory] + [MemberData(nameof(Int32RectTests.Parse_NotInt32_TestData), MemberType = typeof(Int32RectTests))] + public void ConvertFrom_NotInt_ThrowsFormatException(object value) + { + var converter = new Int32RectConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + public static IEnumerable ConvertFrom_CantConvert_TestData() + { + yield return new object[] { new object() }; + yield return new object[] { new Int32Rect() }; + } + + [Theory] + [MemberData(nameof(ConvertFrom_CantConvert_TestData))] + public void ConvertFrom_CantConvert_ThrowsNotSupportedException(object value) + { + var converter = new Int32RectConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + private class CustomTypeDescriptorContext : ITypeDescriptorContext + { + public IContainer Container => throw new NotImplementedException(); + + public object Instance => throw new NotImplementedException(); + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Int32RectTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Int32RectTests.cs new file mode 100644 index 00000000000..5a34be40743 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Int32RectTests.cs @@ -0,0 +1,459 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Globalization; +using System.Windows.Converters; +using System.Windows.Markup; + +namespace System.Windows.Tests; + +public class Int32RectTests +{ + [Fact] + public void Ctor_Default() + { + var rect = new Int32Rect(); + Assert.Equal(0, rect.X); + Assert.Equal(0, rect.Y); + Assert.Equal(0, rect.Width); + Assert.Equal(0, rect.Height); + Assert.True(rect.IsEmpty); + Assert.False(rect.HasArea); + } + + [Theory] + [InlineData(-1, -2, -3, -4, false, false)] + [InlineData(0, 0, 0, 0, true, false)] + [InlineData(1, 0, 0, 0, false, false)] + [InlineData(-1, 0, 0, 0, false, false)] + [InlineData(0, 1, 0, 0, false, false)] + [InlineData(0, -1, 0, 0, false, false)] + [InlineData(0, 0, 1, 0, false, false)] + [InlineData(0, 0, -1, 0, false, false)] + [InlineData(0, 0, 0, 1, false, false)] + [InlineData(0, 0, 0, -1, false, false)] + [InlineData(1, 2, -1, 4, false, false)] + [InlineData(1, 2, 0, 4, false, false)] + [InlineData(1, 2, 3, -1, false, false)] + [InlineData(1, 2, 3, 0, false, false)] + [InlineData(1, 2, 3, 4, false, true)] + [InlineData(0, 0, 3, 4, false, true)] + public void Ctor_Int_Int_Int_Int(int x, int y, int width, int height, bool expectedIsEmpty, bool expectedHasArea) + { + var rect = new Int32Rect(x, y, width, height); + Assert.Equal(x, rect.X); + Assert.Equal(y, rect.Y); + Assert.Equal(width, rect.Width); + Assert.Equal(height, rect.Height); + Assert.Equal(expectedIsEmpty, rect.IsEmpty); + Assert.Equal(expectedHasArea, rect.HasArea); + } + + [Fact] + public void Empty_Get_ReturnsExpected() + { + Int32Rect rect = Int32Rect.Empty; + Assert.Equal(0, rect.X); + Assert.Equal(0, rect.Y); + Assert.Equal(0, rect.Width); + Assert.Equal(0, rect.Height); + Assert.True(rect.IsEmpty); + Assert.False(rect.HasArea); + Assert.Equal(rect, Int32Rect.Empty); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4 )] + public void X_Set_GetReturnsExpected(int value) + { + var rect = new Int32Rect(1, 2, 3, 4); + + // Set. + rect.X = value; + Assert.Equal(value, rect.X); + Assert.Equal(2, rect.Y); + Assert.Equal(3, rect.Width); + Assert.Equal(4, rect.Height); + Assert.False(rect.IsEmpty); + Assert.True(rect.HasArea); + + // Set again. + rect.X = value; + Assert.Equal(value, rect.X); + Assert.Equal(2, rect.Y); + Assert.Equal(3, rect.Width); + Assert.Equal(4, rect.Height); + Assert.False(rect.IsEmpty); + Assert.True(rect.HasArea); + } + + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4 )] + public void Y_Set_GetReturnsExpected(int value) + { + var rect = new Int32Rect(1, 2, 3, 4); + + // Set. + rect.Y = value; + Assert.Equal(1, rect.X); + Assert.Equal(value, rect.Y); + Assert.Equal(3, rect.Width); + Assert.Equal(4, rect.Height); + Assert.False(rect.IsEmpty); + Assert.True(rect.HasArea); + + // Set again. + rect.Y = value; + Assert.Equal(1, rect.X); + Assert.Equal(value, rect.Y); + Assert.Equal(3, rect.Width); + Assert.Equal(4, rect.Height); + Assert.False(rect.IsEmpty); + Assert.True(rect.HasArea); + } + + + [Theory] + [InlineData(-1, false)] + [InlineData(0, false)] + [InlineData(1, true)] + [InlineData(2, true)] + [InlineData(3, true)] + [InlineData(4, true)] + public void Width_Set_GetReturnsExpected(int value, bool expectedHasArea) + { + var rect = new Int32Rect(1, 2, 3, 4); + + // Set. + rect.Width = value; + Assert.Equal(1, rect.X); + Assert.Equal(2, rect.Y); + Assert.Equal(value, rect.Width); + Assert.Equal(4, rect.Height); + Assert.False(rect.IsEmpty); + Assert.Equal(expectedHasArea, rect.HasArea); + + // Set again. + rect.Width = value; + Assert.Equal(1, rect.X); + Assert.Equal(2, rect.Y); + Assert.Equal(value, rect.Width); + Assert.Equal(4, rect.Height); + Assert.False(rect.IsEmpty); + Assert.Equal(expectedHasArea, rect.HasArea); + } + + [Theory] + [InlineData(-1, false)] + [InlineData(0, false)] + [InlineData(1, true)] + [InlineData(2, true)] + [InlineData(3, true)] + [InlineData(4, true)] + public void Height_Set_GetReturnsExpected(int value, bool expectedHasArea) + { + var rect = new Int32Rect(1, 2, 3, 4); + + // Set. + rect.Height = value; + Assert.Equal(1, rect.X); + Assert.Equal(2, rect.Y); + Assert.Equal(3, rect.Width); + Assert.Equal(value, rect.Height); + Assert.False(rect.IsEmpty); + Assert.Equal(expectedHasArea, rect.HasArea); + + // Set again. + rect.Height = value; + Assert.Equal(1, rect.X); + Assert.Equal(2, rect.Y); + Assert.Equal(3, rect.Width); + Assert.Equal(value, rect.Height); + Assert.False(rect.IsEmpty); + Assert.Equal(expectedHasArea, rect.HasArea); + } + + public static IEnumerable Equals_TestData() + { + // Zero. + yield return new object?[] { new Int32Rect(1, 2, 3, 4), new Int32Rect(1, 2, 3, 4), true }; + yield return new object?[] { new Int32Rect(1, 2, 3, 4), new Int32Rect(2, 2, 3, 4), false }; + yield return new object?[] { new Int32Rect(1, 2, 3, 4), new Int32Rect(1, 3, 3, 4), false }; + yield return new object?[] { new Int32Rect(1, 2, 3, 4), new Int32Rect(1, 2, 4, 4), false }; + yield return new object?[] { new Int32Rect(1, 2, 3, 4), new Int32Rect(1, 2, 3, 5), false }; + yield return new object?[] { new Int32Rect(1, 2, 3, 4), new Int32Rect(0, 0, 0, 0), false }; + yield return new object?[] { new Int32Rect(1, 2, 3, 4), new Int32Rect(), false }; + yield return new object?[] { new Int32Rect(1, 2, 3, 4), Int32Rect.Empty, false }; + yield return new object?[] { new Int32Rect(1, 2, 3, 4), new object(), false }; + yield return new object?[] { new Int32Rect(1, 2, 3, 4), null, false }; + + // Zero. + yield return new object?[] { new Int32Rect(0, 0, 0, 0), Int32Rect.Empty, true }; + yield return new object?[] { new Int32Rect(0, 0, 0, 0), new Int32Rect(), true }; + yield return new object?[] { new Int32Rect(0, 0, 0, 0), new Int32Rect(0, 0, 0, 0), true }; + yield return new object?[] { new Int32Rect(0, 0, 0, 0), new Int32Rect(1, 0, 0, 0), false }; + yield return new object?[] { new Int32Rect(0, 0, 0, 0), new Int32Rect(0, 2, 0, 0), false }; + yield return new object?[] { new Int32Rect(0, 0, 0, 0), new Int32Rect(0, 0, 3, 0), false }; + yield return new object?[] { new Int32Rect(0, 0, 0, 0), new Int32Rect(0, 0, 0, 4), false }; + yield return new object?[] { new Int32Rect(0, 0, 0, 0), new Int32Rect(1, 2, 3, 4), false }; + yield return new object?[] { new Int32Rect(0, 0, 0, 0), new object(), false }; + yield return new object?[] { new Int32Rect(0, 0, 0, 0), null, false }; + + // Default. + yield return new object?[] { new Int32Rect(), Int32Rect.Empty, true }; + yield return new object?[] { new Int32Rect(), new Int32Rect(), true }; + yield return new object?[] { new Int32Rect(), new Int32Rect(0, 0, 0, 0), true }; + yield return new object?[] { new Int32Rect(), new Int32Rect(1, 0, 0, 0), false }; + yield return new object?[] { new Int32Rect(), new Int32Rect(0, 2, 0, 0), false }; + yield return new object?[] { new Int32Rect(), new Int32Rect(0, 0, 3, 0), false }; + yield return new object?[] { new Int32Rect(), new Int32Rect(0, 0, 0, 4), false }; + yield return new object?[] { new Int32Rect(), new Int32Rect(1, 2, 3, 4), false }; + yield return new object?[] { new Int32Rect(), new object(), false }; + yield return new object?[] { new Int32Rect(), null, false }; + + // Empty. + yield return new object?[] { Int32Rect.Empty, Int32Rect.Empty, true }; + yield return new object?[] { Int32Rect.Empty, new Int32Rect(), true }; + yield return new object?[] { Int32Rect.Empty, new Int32Rect(0, 0, 0, 0), true }; + yield return new object?[] { Int32Rect.Empty, new Int32Rect(1, 0, 0, 0), false }; + yield return new object?[] { Int32Rect.Empty, new Int32Rect(0, 2, 0, 0), false }; + yield return new object?[] { Int32Rect.Empty, new Int32Rect(0, 0, 3, 0), false }; + yield return new object?[] { Int32Rect.Empty, new Int32Rect(0, 0, 0, 4), false }; + yield return new object?[] { Int32Rect.Empty, new Int32Rect(1, 2, 3, 4), false }; + yield return new object?[] { Int32Rect.Empty, new object(), false }; + yield return new object?[] { Int32Rect.Empty, null, false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void Equals_Invoke_ReturnsExpected(Int32Rect rect, object o, bool expected) + { + Assert.Equal(expected, rect.Equals(o)); + if (o is Int32Rect value) + { + Assert.Equal(expected, rect.Equals(value)); + Assert.Equal(expected, value.Equals(rect)); + Assert.Equal(expected, rect == value); + Assert.Equal(expected, value == rect); + Assert.Equal(!expected, rect != value); + Assert.Equal(!expected, value != rect); + Assert.Equal(expected, rect.GetHashCode().Equals(value.GetHashCode())); + } + } + + [Fact] + public void GetHashCode_InvokeDefault_ReturnsEqual() + { + var rect = new Int32Rect(); + Assert.Equal(0, rect.GetHashCode()); + Assert.Equal(rect.GetHashCode(), rect.GetHashCode()); + } + + [Fact] + public void GetHashCode_InvokeEmpty_ReturnsEqual() + { + Int32Rect rect = Int32Rect.Empty; + Assert.Equal(0, rect.GetHashCode()); + Assert.Equal(rect.GetHashCode(), rect.GetHashCode()); + } + + [Fact] + public void GetHashCode_InvokeNormal_ReturnsEqual() + { + var rect = new Int32Rect(1, 2, 3, 4); + Assert.NotEqual(0, rect.GetHashCode()); + Assert.Equal(rect.GetHashCode(), rect.GetHashCode()); + } + + public static IEnumerable Parse_TestData() + { + yield return new object[] { "Empty", Int32Rect.Empty }; + yield return new object[] { " Empty ", Int32Rect.Empty }; + yield return new object[] { "Empty", new Int32Rect() }; + yield return new object[] { "1,2,3,4", new Int32Rect(1, 2, 3, 4) }; + yield return new object[] { " 1 , 2 , 3, 4", new Int32Rect(1, 2, 3, 4) }; + yield return new object[] { "-1,-2,-3,-4", new Int32Rect(-1, -2, -3, -4) }; + } + + [Theory] + [MemberData(nameof(Parse_TestData))] + public void Parse_Invoke_Success(string source, Int32Rect expected) + { + Int32Rect result = Int32Rect.Parse(source); + Assert.Equal(expected.X, result.X); + Assert.Equal(expected.Y, result.Y); + Assert.Equal(expected.Width, result.Width); + Assert.Equal(expected.Height, result.Height); + } + + [Fact] + public void Parse_NullSource_ThrowsInvalidOperationException() + { + Assert.Throws(() => Int32Rect.Parse(null)); + } + + public static IEnumerable Parse_InvalidSource_TestData() + { + yield return new object[] { "" }; + yield return new object[] { " " }; + yield return new object[] { "," }; + yield return new object[] { "1" }; + yield return new object[] { "1," }; + yield return new object[] { "1,2" }; + yield return new object[] { "1,2," }; + yield return new object[] { "1,2,3" }; + yield return new object[] { "1,2,3," }; + yield return new object[] { "1,2,3,4," }; + yield return new object[] { "1,2,3,4,5" }; + yield return new object[] { "1,2,4,5,test" }; + yield return new object[] { "Empty," }; + yield return new object[] { "Empty,2,3,4" }; + yield return new object[] { "Identity," }; + } + + [Theory] + [MemberData(nameof(Parse_InvalidSource_TestData))] + public void Parse_InvalidSource_ThrowsInvalidOperationException(string source) + { + Assert.Throws(() => Int32Rect.Parse(source)); + } + + public static IEnumerable Parse_NotInt32_TestData() + { + yield return new object[] { "Identity" }; + yield return new object[] { " Identity " }; + yield return new object[] { "Identity,2,3,4" }; + yield return new object[] { "test" }; + yield return new object[] { "test,2,3,4" }; + yield return new object[] { "1,test,3,4" }; + yield return new object[] { "1,2,test,4" }; + yield return new object[] { "1,2,3,test" }; + yield return new object[] { "1;2;3;4" }; + yield return new object[] { """1"",""2"",""3"",""4""" }; + yield return new object[] { "1.1,2,3,4" }; + } + + [Theory] + [MemberData(nameof(Parse_NotInt32_TestData))] + public void Parse_NotInt_ThrowsFormatException(string source) + { + Assert.Throws(() => Int32Rect.Parse(source)); + } + + public static IEnumerable ToString_TestData() + { + yield return new object[] { Int32Rect.Empty, "Empty" }; + yield return new object[] { new Int32Rect(), "Empty" }; + yield return new object[] { new Int32Rect(0, 0, 0, 0), "Empty" }; + yield return new object[] { new Int32Rect(1, 2, 3, 4), "1,2,3,4" }; + yield return new object[] { new Int32Rect(-1, -2, 3, 4), "-1,-2,3,4" }; + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_Invoke_ReturnsExpected(Int32Rect rect, string expected) + { + Assert.Equal(expected, rect.ToString()); + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_InvokeIFormatProviderInvariantCulture_ReturnsExpected(Int32Rect rect, string expected) + { + Assert.Equal(expected, rect.ToString(CultureInfo.InvariantCulture)); + } + + public static IEnumerable ToString_IFormatProviderCustom_TestData() + { + yield return new object[] { new Int32Rect(), "|", "Empty" }; + yield return new object[] { new Int32Rect(), "|_", "Empty" }; + yield return new object[] { new Int32Rect(), ",_", "Empty" }; + yield return new object[] { new Int32Rect(), ",", "Empty" }; + yield return new object[] { new Int32Rect(), ";", "Empty" }; + yield return new object[] { new Int32Rect(), " ", "Empty" }; + + yield return new object[] { new Int32Rect(0, 0, 0, 0), "|", "Empty" }; + yield return new object[] { new Int32Rect(0, 0, 0, 0), "|_", "Empty" }; + yield return new object[] { new Int32Rect(0, 0, 0, 0), ",_", "Empty" }; + yield return new object[] { new Int32Rect(0, 0, 0, 0), ",", "Empty" }; + yield return new object[] { new Int32Rect(0, 0, 0, 0), ";", "Empty" }; + yield return new object[] { new Int32Rect(0, 0, 0, 0), " ", "Empty" }; + + yield return new object[] { new Int32Rect(1, 2, 3, 4), "|", "1,2,3,4" }; + yield return new object[] { new Int32Rect(1, 2, 3, 4), "|_", "1,2,3,4" }; + yield return new object[] { new Int32Rect(1, 2, 3, 4), ",_", "1;2;3;4" }; + yield return new object[] { new Int32Rect(1, 2, 3, 4), ",", "1;2;3;4" }; + yield return new object[] { new Int32Rect(1, 2, 3, 4), ";", "1,2,3,4" }; + yield return new object[] { new Int32Rect(1, 2, 3, 4), " ", "1,2,3,4" }; + + yield return new object[] { new Int32Rect(-1, -2, -3, -4), "|", "-1,-2,-3,-4" }; + yield return new object[] { new Int32Rect(-1, -2, -3, -4), "|_", "-1,-2,-3,-4" }; + yield return new object[] { new Int32Rect(-1, -2, -3, -4), ",_", "-1;-2;-3;-4" }; + yield return new object[] { new Int32Rect(-1, -2, -3, -4), ",", "-1;-2;-3;-4" }; + yield return new object[] { new Int32Rect(-1, -2, -3, -4), ";", "-1,-2,-3,-4" }; + yield return new object[] { new Int32Rect(-1, -2, -3, -4), " ", "-1,-2,-3,-4" }; + } + + [Theory] + [MemberData(nameof(ToString_IFormatProviderCustom_TestData))] + public void ToString_InvokeIFormatProviderCustom_ReturnsExpected(Int32Rect rect, string numberDecimalSeparator, string expected) + { + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + Assert.Equal(expected, rect.ToString(formatInfo)); + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_InvokeIFormattableInvariantCulture_ReturnsExpected(Int32Rect rect, string expected) + { + IFormattable formattable = rect; + Assert.Equal(expected, formattable.ToString(null, null)); + Assert.Equal(expected, formattable.ToString(null, CultureInfo.InvariantCulture)); + } + + [Theory] + [InlineData("|", "1|00,2|00,3|00,4|00")] + [InlineData("|_", "1|_00,2|_00,3|_00,4|_00")] + [InlineData(",_", "1,_00;2,_00;3,_00;4,_00")] + [InlineData(",", "1,00;2,00;3,00;4,00")] + [InlineData(";", "1;00,2;00,3;00,4;00")] + [InlineData(" ", "1 00,2 00,3 00,4 00")] + public void ToString_InvokeIFormattableCustomFormat_ReturnsExpected(string numberDecimalSeparator, string expected) + { + var rect = new Int32Rect(1, 2, 3, 4); + IFormattable formattable = rect; + + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + Assert.Equal(expected, formattable.ToString("F2", formatInfo)); + } + + [Fact] + public void TypeConverter_Get_ReturnsExpected() + { + Assert.IsType(TypeDescriptor.GetConverter(typeof(Int32Rect))); + } + + [Fact] + public void ValueSerializer_Get_ReturnsExpected() + { + Assert.IsType(ValueSerializer.GetSerializerFor(typeof(Int32Rect))); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Interop/ComponentDispatcherTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Interop/ComponentDispatcherTests.cs new file mode 100644 index 00000000000..61377b82898 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Interop/ComponentDispatcherTests.cs @@ -0,0 +1,279 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Interop.Tests; + +public class ComponentDispatcherTests +{ + // TODO: + // - Invoke tests (needs RemoteExecutor) + + [Fact] + public void CurrentKeyboardMessage_Get_ReturnsExpected() + { + MSG msg = ComponentDispatcher.CurrentKeyboardMessage; + Assert.Equal(msg, ComponentDispatcher.CurrentKeyboardMessage); + } + + [Fact] + public void IsThreadModal_Get_ReturnsExpected() + { + bool isThreadModal = ComponentDispatcher.IsThreadModal; + Assert.False(isThreadModal); + Assert.Equal(isThreadModal, ComponentDispatcher.IsThreadModal); + } + + [Fact] + public void EnterThreadModal_AddRemove_Success() + { + int callCount = 0; + EventHandler handler = (s, e) => callCount++; + ComponentDispatcher.EnterThreadModal += handler; + Assert.Equal(0, callCount); + + ComponentDispatcher.EnterThreadModal -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + ComponentDispatcher.EnterThreadModal -= handler; + Assert.Equal(0, callCount); + + // Add null. + ComponentDispatcher.EnterThreadModal += null; + Assert.Equal(0, callCount); + + // Remove null. + ComponentDispatcher.EnterThreadModal -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void LeaveThreadModal_AddRemove_Success() + { + int callCount = 0; + EventHandler handler = (s, e) => callCount++; + ComponentDispatcher.LeaveThreadModal += handler; + Assert.Equal(0, callCount); + + ComponentDispatcher.LeaveThreadModal -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + ComponentDispatcher.LeaveThreadModal -= handler; + Assert.Equal(0, callCount); + + // Add null. + ComponentDispatcher.LeaveThreadModal += null; + Assert.Equal(0, callCount); + + // Remove null. + ComponentDispatcher.LeaveThreadModal -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void ThreadFilterMessage_AddRemove_Success() + { + int callCount = 0; + ThreadMessageEventHandler handler = (ref MSG m, ref bool h) => callCount++; + ComponentDispatcher.ThreadFilterMessage += handler; + Assert.Equal(0, callCount); + + ComponentDispatcher.ThreadFilterMessage -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + ComponentDispatcher.ThreadFilterMessage -= handler; + Assert.Equal(0, callCount); + + // Add null. + ComponentDispatcher.ThreadFilterMessage += null; + Assert.Equal(0, callCount); + + // Remove null. + ComponentDispatcher.ThreadFilterMessage -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void ThreadPreprocessMessage_AddRemove_Success() + { + int callCount = 0; + ThreadMessageEventHandler handler = (ref MSG m, ref bool h) => callCount++; + ComponentDispatcher.ThreadPreprocessMessage += handler; + Assert.Equal(0, callCount); + + ComponentDispatcher.ThreadPreprocessMessage -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + ComponentDispatcher.ThreadPreprocessMessage -= handler; + Assert.Equal(0, callCount); + + // Add null. + ComponentDispatcher.ThreadPreprocessMessage += null; + Assert.Equal(0, callCount); + + // Remove null. + ComponentDispatcher.ThreadPreprocessMessage -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void ThreadIdle_AddRemove_Success() + { + int callCount = 0; + EventHandler handler = (s, e) => callCount++; + ComponentDispatcher.ThreadIdle += handler; + Assert.Equal(0, callCount); + + ComponentDispatcher.ThreadIdle -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + ComponentDispatcher.ThreadIdle -= handler; + Assert.Equal(0, callCount); + + // Add null. + ComponentDispatcher.ThreadIdle += null; + Assert.Equal(0, callCount); + + // Remove null. + ComponentDispatcher.ThreadIdle -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void RaiseIdle_Invoke_Success() + { + // Raise. + ComponentDispatcher.RaiseIdle(); + + // Raise again. + ComponentDispatcher.RaiseIdle(); + } + + [Fact] + public void RaiseIdle_InvokeWithHandler_Success() + { + int callCount = 0; + EventHandler handler = (s, e) => + { + Assert.Null(s); + Assert.Same(EventArgs.Empty, e); + callCount++; + }; + ComponentDispatcher.ThreadIdle += handler; + + // Raise. + ComponentDispatcher.RaiseIdle(); + Assert.Equal(1, callCount); + + // Raise again. + ComponentDispatcher.RaiseIdle(); + Assert.Equal(2, callCount); + + // Remove handler. + ComponentDispatcher.ThreadIdle -= handler; + ComponentDispatcher.RaiseIdle(); + Assert.Equal(2, callCount); + } + + [Fact] + public void RaiseThreadMessage_Invoke_Success() + { + var msg = new MSG(); + + // Raise. + ComponentDispatcher.RaiseThreadMessage(ref msg); + + // Raise again. + ComponentDispatcher.RaiseThreadMessage(ref msg); + } + + [Fact] + public void PushModal_Invoke_Success() + { + bool original = ComponentDispatcher.IsThreadModal; + + // Push. + ComponentDispatcher.PushModal(); + Assert.True(ComponentDispatcher.IsThreadModal); + + // Pop. + ComponentDispatcher.PopModal(); + Assert.Equal(original, ComponentDispatcher.IsThreadModal); + } + + [Fact] + public void PushModal_InvokeMultipleTimes_Success() + { + bool original = ComponentDispatcher.IsThreadModal; + + // Push. + ComponentDispatcher.PushModal(); + Assert.True(ComponentDispatcher.IsThreadModal); + + // Push again. + ComponentDispatcher.PushModal(); + Assert.True(ComponentDispatcher.IsThreadModal); + + // Pop. + ComponentDispatcher.PopModal(); + Assert.Equal(original, ComponentDispatcher.IsThreadModal); + + // Pop again. + ComponentDispatcher.PopModal(); + Assert.False(ComponentDispatcher.IsThreadModal); + } + + [Fact] + public void PopModal_Invoke_Success() + { + // Push. + ComponentDispatcher.PushModal(); + Assert.True(ComponentDispatcher.IsThreadModal); + + // Pop. + ComponentDispatcher.PopModal(); + Assert.False(ComponentDispatcher.IsThreadModal); + + // Pop again. + ComponentDispatcher.PopModal(); + Assert.False(ComponentDispatcher.IsThreadModal); + } + + [Fact] + public void PopModal_InvokeNoPush_Success() + { + // Pop. + ComponentDispatcher.PopModal(); + Assert.False(ComponentDispatcher.IsThreadModal); + + // Pop again. + ComponentDispatcher.PopModal(); + Assert.False(ComponentDispatcher.IsThreadModal); + + // Push. + ComponentDispatcher.PushModal(); + Assert.True(ComponentDispatcher.IsThreadModal); + + // Push again. + ComponentDispatcher.PushModal(); + Assert.True(ComponentDispatcher.IsThreadModal); + } + + [Fact] + public void PopModal_InvokeMultipleTimes_Success() + { + // Push. + ComponentDispatcher.PushModal(); + + // Pop. + ComponentDispatcher.PopModal(); + + // Pop again. + ComponentDispatcher.PopModal(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Interop/MSGTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Interop/MSGTests.cs new file mode 100644 index 00000000000..1fb03716878 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Interop/MSGTests.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Interop.Tests; + +public class MSGTests +{ + [Fact] + public void Ctor_Default() + { + var msg = new MSG(); + Assert.Equal(IntPtr.Zero, msg.hwnd); + Assert.Equal(0, msg.message); + Assert.Equal(IntPtr.Zero, msg.wParam); + Assert.Equal(IntPtr.Zero, msg.lParam); + Assert.Equal(0, msg.time); + Assert.Equal(0, msg.pt_x); + Assert.Equal(0, msg.pt_y); + } + + public static IEnumerable IntPtr_TestData() + { + yield return new object[] { (IntPtr)(-1) }; + yield return new object[] { IntPtr.Zero }; + yield return new object[] { (IntPtr)1 }; + } + + [Theory] + [MemberData(nameof(IntPtr_TestData))] + public void hwnd_Set_GetReturnsExpected(IntPtr value) + { + var msg = new MSG(); + + // Set. + msg.hwnd = value; + Assert.Equal(value, msg.hwnd); + + // Set same. + msg.hwnd = value; + Assert.Equal(value, msg.hwnd); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + public void message_Set_GetReturnsExpected(int value) + { + var msg = new MSG(); + + // Set. + msg.message = value; + Assert.Equal(value, msg.message); + + // Set same. + msg.message = value; + Assert.Equal(value, msg.message); + } + + [Theory] + [MemberData(nameof(IntPtr_TestData))] + public void wParam_Set_GetReturnsExpected(IntPtr value) + { + var msg = new MSG(); + + // Set. + msg.wParam = value; + Assert.Equal(value, msg.wParam); + + // Set same. + msg.wParam = value; + Assert.Equal(value, msg.wParam); + } + + [Theory] + [MemberData(nameof(IntPtr_TestData))] + public void lParam_Set_GetReturnsExpected(IntPtr value) + { + var msg = new MSG(); + + // Set. + msg.lParam = value; + Assert.Equal(value, msg.lParam); + + // Set same. + msg.lParam = value; + Assert.Equal(value, msg.lParam); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + public void time_Set_GetReturnsExpected(int value) + { + var msg = new MSG(); + + // Set. + msg.time = value; + Assert.Equal(value, msg.time); + + // Set same. + msg.time = value; + Assert.Equal(value, msg.time); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + public void pt_x_Set_GetReturnsExpected(int value) + { + var msg = new MSG(); + + // Set. + msg.pt_x = value; + Assert.Equal(value, msg.pt_x); + + // Set same. + msg.pt_x = value; + Assert.Equal(value, msg.pt_x); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + public void pt_y_Set_GetReturnsExpected(int value) + { + var msg = new MSG(); + + // Set. + msg.pt_y = value; + Assert.Equal(value, msg.pt_y); + + // Set same. + msg.pt_y = value; + Assert.Equal(value, msg.pt_y); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/LocalValueEntryTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/LocalValueEntryTests.cs new file mode 100644 index 00000000000..72f8195940f --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/LocalValueEntryTests.cs @@ -0,0 +1,115 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; + +namespace System.Windows.Tests; + +public class LocalValueEntryTests +{ + [Fact] + public void Ctor_Default() + { + var entry = new LocalValueEntry(); + Assert.Null(entry.Property); + Assert.Null(entry.Value); + } + + public static IEnumerable Equals_TestData() + { + yield return new object?[] { new LocalValueEntry(), new LocalValueEntry(), true }; + //yield return new object?[] { new LocalValueEntry(), new object(), false }; + //yield return new object?[] { new LocalValueEntry(), null, false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void Equals_Invoke_ReturnsExpected(LocalValueEntry entry, object? obj, bool expected) + { + Assert.Equal(expected, entry.Equals(obj)); + if (obj is LocalValueEntry other) + { + Assert.Equal(expected, other.Equals(entry)); + Assert.Equal(expected, entry == other); + Assert.Equal(expected, other == entry); + Assert.Equal(!expected, entry != other); + Assert.Equal(!expected, other != entry); + } + } + + [Fact] + public void Equals_InvokeCustom_ReturnsExpected() + { + DependencyProperty property1 = DependencyProperty.Register(nameof(LocalValueEntryTests) + MethodBase.GetCurrentMethod()!.Name + "1", typeof(string), typeof(DependencyObject)); + DependencyProperty property2 = DependencyProperty.Register(nameof(LocalValueEntryTests) + MethodBase.GetCurrentMethod()!.Name + "2", typeof(string), typeof(DependencyObject)); + var obj1 = new DependencyObject(); + var obj2 = new DependencyObject(); + var obj3 = new DependencyObject(); + var obj4 = new DependencyObject(); + obj1.SetValue(property1, "a"); + obj2.SetValue(property1, "a"); + obj3.SetValue(property1, "b"); + obj4.SetValue(property2, "a"); + + LocalValueEnumerator enumerator1 = obj1.GetLocalValueEnumerator(); + enumerator1.MoveNext(); + LocalValueEnumerator enumerator1Copy = obj1.GetLocalValueEnumerator(); + enumerator1Copy.MoveNext(); + LocalValueEnumerator enumerator2 = obj2.GetLocalValueEnumerator(); + enumerator2.MoveNext(); + LocalValueEnumerator enumerator3 = obj3.GetLocalValueEnumerator(); + enumerator3.MoveNext(); + LocalValueEnumerator enumerator4 = obj4.GetLocalValueEnumerator(); + enumerator4.MoveNext(); + + Equals_Invoke_ReturnsExpected(enumerator1.Current, enumerator1.Current, true); + Equals_Invoke_ReturnsExpected(enumerator1.Current, enumerator1Copy.Current, true); + Equals_Invoke_ReturnsExpected(enumerator1.Current, enumerator2.Current, true); + Equals_Invoke_ReturnsExpected(enumerator1.Current, enumerator3.Current, false); + Equals_Invoke_ReturnsExpected(enumerator1.Current, enumerator4.Current, false); + Equals_Invoke_ReturnsExpected(enumerator1.Current, new LocalValueEntry(), false); + Equals_Invoke_ReturnsExpected(new LocalValueEntry(), enumerator1.Current, false); + // TODO: should return false. + //Equals_Invoke_ReturnsExpected(enumerator1.Current, new object(), false); + //Equals_Invoke_ReturnsExpected(enumerator1.Current, null!, false); + } + + [Fact] + public void Equals_ObjNull_ThrowsNullReferenceException() + { + var entry = new LocalValueEntry(); + // TODO: should return false. + Assert.Throws(() => entry.Equals(null)); + } + + [Fact] + public void Equals_ObjNotLocalValueEntry_ThrowsInvalidCastException() + { + var entry = new LocalValueEntry(); + Assert.Throws(() => entry.Equals(new object())); + } + + [Fact] + public void GetHashCode_InvokeDefault_ReturnsExpected() + { + var entry = new LocalValueEntry(); + Assert.NotEqual(0, entry.GetHashCode()); + Assert.Equal(entry.GetHashCode(), entry.GetHashCode()); + } + + [Fact] + public void GetHashCode_InvokeCustom_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(LocalValueEntryTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "a"); + + LocalValueEnumerator enumerator = obj.GetLocalValueEnumerator(); + enumerator.MoveNext(); + + LocalValueEntry entry = enumerator.Current; + Assert.NotEqual(0, entry.GetHashCode()); + Assert.Equal(entry.GetHashCode(), entry.GetHashCode()); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/LocalValueEnumeratorTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/LocalValueEnumeratorTests.cs new file mode 100644 index 00000000000..0f8821a5084 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/LocalValueEnumeratorTests.cs @@ -0,0 +1,302 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Reflection; + +namespace System.Windows.Tests; + +public class LocalValueEnumeratorTests +{ + [Fact] + public void Ctor_Default() + { + var enumerator = new LocalValueEnumerator(); + Assert.Equal(0, enumerator.Count); + Assert.Throws(() => enumerator.Current); + } + + [Fact] + public void Current_GetDefault_ThrowsInvalidOperationException() + { + var enumerator = new LocalValueEnumerator(); + Assert.Throws(() => enumerator.Current); + } + + [Fact] + public void Current_GetDefaultFinished_ThrowsInvalidOperationException() + { + var enumerator = new LocalValueEnumerator(); + enumerator.MoveNext(); + + Assert.Throws(() => enumerator.Current); + } + + [Fact] + public void Current_GetDefaultReset_ThrowsInvalidOperationException() + { + var enumerator = new LocalValueEnumerator(); + enumerator.Reset(); + + Assert.Throws(() => enumerator.Current); + } + + [Fact] + public void Current_GetCustom_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.Register(nameof(LocalValueEnumeratorTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "a"); + + LocalValueEnumerator enumerator = obj.GetLocalValueEnumerator(); + Assert.Throws(() => enumerator.Current); + } + + [Fact] + public void Current_GetCustomFinished_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.Register(nameof(LocalValueEnumeratorTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "a"); + + LocalValueEnumerator enumerator = obj.GetLocalValueEnumerator(); + enumerator.MoveNext(); + enumerator.MoveNext(); + Assert.Throws(() => enumerator.Current); + } + + [Fact] + public void Current_GetCustomReset_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.Register(nameof(LocalValueEnumeratorTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "a"); + + LocalValueEnumerator enumerator = obj.GetLocalValueEnumerator(); + enumerator.Reset(); + Assert.Throws(() => enumerator.Current); + } + + [Fact] + public void IEnumeratorCurrent_GetDefault_ThrowsInvalidOperationException() + { + IEnumerator enumerator = new LocalValueEnumerator(); + Assert.Throws(() => ((IEnumerator)enumerator).Current); + } + + [Fact] + public void IEnumeratorCurrent_GetDefaultFinished_ThrowsInvalidOperationException() + { + IEnumerator enumerator = new LocalValueEnumerator(); + enumerator.MoveNext(); + + Assert.Throws(() => ((IEnumerator)enumerator).Current); + } + + [Fact] + public void IEnumeratorCurrent_GetDefaultReset_ThrowsInvalidOperationException() + { + IEnumerator enumerator = new LocalValueEnumerator(); + enumerator.Reset(); + + Assert.Throws(() => ((IEnumerator)enumerator).Current); + } + + [Fact] + public void IEnumeratorCurrent_GetCustom_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.Register(nameof(LocalValueEnumeratorTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "a"); + + LocalValueEnumerator enumerator = obj.GetLocalValueEnumerator(); + Assert.Throws(() => ((IEnumerator)enumerator).Current); + } + + [Fact] + public void IEnumeratorCurrent_GetCustomFinished_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.Register(nameof(LocalValueEnumeratorTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "a"); + + LocalValueEnumerator enumerator = obj.GetLocalValueEnumerator(); + enumerator.MoveNext(); + enumerator.MoveNext(); + Assert.Throws(() => ((IEnumerator)enumerator).Current); + } + + [Fact] + public void IEnumeratorCurrent_GetCustomReset_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.Register(nameof(LocalValueEnumeratorTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "a"); + + LocalValueEnumerator enumerator = obj.GetLocalValueEnumerator(); + enumerator.Reset(); + Assert.Throws(() => ((IEnumerator)enumerator).Current); + } + + public static IEnumerable Equals_TestData() + { + yield return new object?[] { new LocalValueEnumerator(), new LocalValueEnumerator(), true, true }; + + var enumerator1 = new LocalValueEnumerator(); + enumerator1.MoveNext(); + yield return new object?[] { new LocalValueEnumerator(), enumerator1, false, false }; + + var enumerator2 = new LocalValueEnumerator(); + enumerator2.MoveNext(); + var enumerator3 = new LocalValueEnumerator(); + enumerator3.MoveNext(); + enumerator3.MoveNext(); + yield return new object?[] { enumerator2, enumerator2, true, true }; + yield return new object?[] { enumerator2, enumerator1, true, true }; + yield return new object?[] { enumerator2, enumerator3, false, false }; + + yield return new object?[] { new LocalValueEnumerator(), new object(), false, false }; + yield return new object?[] { new LocalValueEnumerator(), null, false, false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void Equals_Invoke_ReturnsExpected(LocalValueEnumerator enumerator, object? obj, bool expected, bool expectedHashCode) + { + Assert.Equal(expected, enumerator.Equals(obj)); + if (obj is LocalValueEnumerator other) + { + Assert.Equal(expected, other.Equals(enumerator)); + Assert.Equal(expected, enumerator == other); + Assert.Equal(expected, other == enumerator); + Assert.Equal(!expected, enumerator != other); + Assert.Equal(!expected, other != enumerator); + Assert.Equal(expectedHashCode, enumerator.GetHashCode().Equals(other.GetHashCode())); + } + } + + [Fact] + public void Equals_InvokeCustom_ReturnsExpected() + { + DependencyProperty property1 = DependencyProperty.Register(nameof(LocalValueEnumeratorTests) + MethodBase.GetCurrentMethod()!.Name + "1", typeof(string), typeof(DependencyObject)); + DependencyProperty property2 = DependencyProperty.Register(nameof(LocalValueEnumeratorTests) + MethodBase.GetCurrentMethod()!.Name + "2", typeof(string), typeof(DependencyObject)); + var obj1 = new DependencyObject(); + var obj2 = new DependencyObject(); + var obj3 = new DependencyObject(); + var obj4 = new DependencyObject(); + var obj5 = new DependencyObject(); + obj1.SetValue(property1, "a"); + obj2.SetValue(property1, "a"); + obj3.SetValue(property1, "b"); + obj4.SetValue(property1, "a"); + obj4.SetValue(property2, "b"); + obj5.SetValue(property2, "a"); + + LocalValueEnumerator enumerator1 = obj1.GetLocalValueEnumerator(); + enumerator1.MoveNext(); + LocalValueEnumerator enumerator1Copy1 = obj1.GetLocalValueEnumerator(); + enumerator1Copy1.MoveNext(); + LocalValueEnumerator enumerator1Copy2 = enumerator1; + LocalValueEnumerator enumerator1Copy3 = enumerator1; + enumerator1Copy3.MoveNext(); + LocalValueEnumerator enumerator2 = obj2.GetLocalValueEnumerator(); + enumerator2.MoveNext(); + LocalValueEnumerator enumerator3 = obj3.GetLocalValueEnumerator(); + enumerator3.MoveNext(); + LocalValueEnumerator enumerator4 = obj4.GetLocalValueEnumerator(); + enumerator4.MoveNext(); + LocalValueEnumerator enumerator5 = obj5.GetLocalValueEnumerator(); + enumerator5.MoveNext(); + + Equals_Invoke_ReturnsExpected(enumerator1, enumerator1, true, true); + Equals_Invoke_ReturnsExpected(enumerator1, enumerator1Copy1, false, true); + Equals_Invoke_ReturnsExpected(enumerator1, enumerator1Copy2, true, true); + Equals_Invoke_ReturnsExpected(enumerator1, enumerator1Copy3, false, false); + Equals_Invoke_ReturnsExpected(enumerator1, enumerator2, false, true); + Equals_Invoke_ReturnsExpected(enumerator1, enumerator3, false, true); + Equals_Invoke_ReturnsExpected(enumerator1, enumerator4, false, true); + Equals_Invoke_ReturnsExpected(enumerator1, enumerator5, false, true); + Equals_Invoke_ReturnsExpected(enumerator1, new LocalValueEnumerator(), false, true); + Equals_Invoke_ReturnsExpected(new LocalValueEnumerator(), enumerator1, false, true); + Equals_Invoke_ReturnsExpected(enumerator1, new object(), false, false); + Equals_Invoke_ReturnsExpected(enumerator1, null, false, false); + } + + [Fact] + public void GetHashCode_InvokeDefault_ReturnsExpected() + { + var enumerator = new LocalValueEnumerator(); + Assert.NotEqual(0, enumerator.GetHashCode()); + Assert.Equal(enumerator.GetHashCode(), enumerator.GetHashCode()); + } + + [Fact] + public void GetHashCode_InvokeCustom_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(LocalValueEnumeratorTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "a"); + + LocalValueEnumerator enumerator = obj.GetLocalValueEnumerator(); + Assert.NotEqual(0, enumerator.GetHashCode()); + Assert.Equal(enumerator.GetHashCode(), enumerator.GetHashCode()); + } + + [Fact] + public void MoveNext_InvokeDefault_ReturnsFalse() + { + var enumerator = new LocalValueEnumerator(); + + // Move end. + Assert.False(enumerator.MoveNext()); + + // Move again. + Assert.False(enumerator.MoveNext()); + } + + [Fact] + public void MoveNext_InvokeCustom_ReturnsExpected() + { + DependencyProperty property = DependencyProperty.Register(nameof(LocalValueEnumeratorTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "a"); + + LocalValueEnumerator enumerator = obj.GetLocalValueEnumerator(); + Assert.True(enumerator.MoveNext()); + + // Move end. + Assert.False(enumerator.MoveNext()); + + // Move again. + Assert.False(enumerator.MoveNext()); + } + + [Fact] + public void Reset_InvokeDefault_Success() + { + var enumerator = new LocalValueEnumerator(); + enumerator.Reset(); + Assert.Throws(() => enumerator.Current); + + // Reset again. + enumerator.Reset(); + Assert.Throws(() => enumerator.Current); + } + + [Fact] + public void Reset_InvokeCustom_Success() + { + DependencyProperty property = DependencyProperty.Register(nameof(LocalValueEnumeratorTests) + MethodBase.GetCurrentMethod()!.Name, typeof(string), typeof(DependencyObject)); + var obj = new DependencyObject(); + obj.SetValue(property, "a"); + + LocalValueEnumerator enumerator = obj.GetLocalValueEnumerator(); + enumerator.Reset(); + Assert.Throws(() => enumerator.Current); + + // Reset again. + enumerator.Reset(); + Assert.Throws(() => enumerator.Current); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/InternalTypeHelperTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/InternalTypeHelperTests.cs new file mode 100644 index 00000000000..6dcee3c2f48 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/InternalTypeHelperTests.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; +using System.Reflection; + +namespace System.Windows.Markup.Tests; + +public class InternalTypeHelperTests +{ + [Fact] + public void Ctor_Default() + { + // Verify doesn't throw. + new SubInternalTypeHelper(); + } + + private class SubInternalTypeHelper : InternalTypeHelper + { + protected override void AddEventHandler(EventInfo eventInfo, object target, Delegate handler) + => throw new NotImplementedException(); + + protected override Delegate CreateDelegate(Type delegateType, object target, string handler) + => throw new NotImplementedException(); + + protected override object CreateInstance(Type type, CultureInfo culture) + => throw new NotImplementedException(); + + protected override object GetPropertyValue(PropertyInfo propertyInfo, object target, CultureInfo culture) + => throw new NotImplementedException(); + + protected override void SetPropertyValue(PropertyInfo propertyInfo, object target, object value, CultureInfo culture) + => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/Primitives/MarkupObjectTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/Primitives/MarkupObjectTests.cs new file mode 100644 index 00000000000..e7a6e7ceb05 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/Primitives/MarkupObjectTests.cs @@ -0,0 +1,531 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design.Serialization; +using System.Globalization; +using System.Linq; + +namespace System.Windows.Markup.Primitives.Tests; + +public class MarkupObjectTests +{ + [Fact] + public void Properties_GetObject_ReturnsExpected() + { + var instance = new object(); + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Same(instance, obj.Instance); + Assert.Equal(typeof(object), obj.ObjectType); + Assert.Empty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + } + + [Fact] + public void Properties_GetConvertableToString_ReturnsExpected() + { + var instance = Rect.Empty; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Equal(instance, obj.Instance); + Assert.Equal(typeof(Rect), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.Empty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.False(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.False(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.True(properties[0].IsValueAsString); + Assert.Equal("StringValue", properties[0].Name); + Assert.Null(properties[0].PropertyDescriptor); + Assert.Equal(typeof(string), properties[0].PropertyType); + Assert.Equal("Empty", properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.Null(properties[0].Items); + Assert.Equal("Empty", properties[0].Value); + } + + [Fact] + public void Properties_GetArray_ReturnsExpected() + { + var instance = new int[] { 1, 2, 3 }; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Equal(instance, obj.Instance); + Assert.Equal(typeof(int[]), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.Empty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.True(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.True(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal("Items", properties[0].Name); + Assert.Null(properties[0].PropertyDescriptor); + Assert.Equal(typeof(IEnumerable), properties[0].PropertyType); + Assert.Equal("", properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.Equal(instance.Length, properties[0].Items.Count()); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Equal(instance, Assert.IsType(properties[0].Value).Items); + } + + [Fact] + public void Properties_GetList_ReturnsExpected() + { + var instance = new List { 1, 2, 3 }; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Equal(instance, obj.Instance); + Assert.Equal(typeof(List), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Equal(2, properties.Count); + Assert.NotEmpty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.False(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.False(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal("Capacity", properties[0].Name); + Assert.NotNull(properties[0].PropertyDescriptor); + Assert.Equal(typeof(int), properties[0].PropertyType); + Assert.NotEmpty(properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.NotSame(obj, Assert.Single(properties[0].Items)); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.NotEqual(0, properties[0].Value); + + Assert.Empty(properties[1].Attributes); + Assert.Same(properties[1].Attributes, properties[1].Attributes); + Assert.Null(properties[1].DependencyProperty); + Assert.False(properties[1].IsAttached); + Assert.True(properties[1].IsComposite); + Assert.False(properties[1].IsConstructorArgument); + Assert.True(properties[1].IsContent); + Assert.False(properties[1].IsKey); + Assert.False(properties[1].IsValueAsString); + Assert.Equal("Items", properties[1].Name); + Assert.Null(properties[1].PropertyDescriptor); + Assert.Equal(typeof(IEnumerable), properties[1].PropertyType); + Assert.Empty(properties[1].StringValue); + Assert.Empty(properties[1].TypeReferences); + Assert.Same(properties[1].TypeReferences, properties[1].TypeReferences); + Assert.Equal(instance.Count, properties[1].Items.Count()); + Assert.NotSame(properties[1].Items, properties[1].Items); + Assert.Same(instance, properties[1].Value); + } + + [Fact] + public void Properties_GetDictionary_ReturnsExpected() + { + var instance = new Dictionary + { + { 1, "value1" }, + { 2, "value2" }, + { 3, "value3" } + }; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Equal(instance, obj.Instance); + Assert.Equal(typeof(Dictionary), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.Empty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.True(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.True(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal("Entries", properties[0].Name); + Assert.Null(properties[0].PropertyDescriptor); + Assert.Equal(typeof(IDictionary), properties[0].PropertyType); + Assert.Equal("", properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.Equal(instance.Count, properties[0].Items.Count()); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Same(instance, properties[0].Value); + } + + [Fact] + public void Properties_GetEnumerable_ReturnsExpected() + { + var list = new List { 1, 2, 3 }; + var instance = new IEnumerableWrapper(list); + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Equal(instance, obj.Instance); + Assert.Equal(typeof(IEnumerableWrapper), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.Empty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.True(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.True(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal("Items", properties[0].Name); + Assert.Null(properties[0].PropertyDescriptor); + Assert.Equal(typeof(IEnumerable), properties[0].PropertyType); + Assert.Equal("", properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.Equal(list.Count, properties[0].Items.Count()); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Same(instance, properties[0].Value); + } + + [Fact] + public void Properties_GetNormalProperty_ReturnsExpected() + { + var instance = new CustomClass + { + Property = 1 + }; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.Empty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Same(instance, obj.Instance); + Assert.Equal(typeof(CustomClass), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.NotEmpty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.False(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.False(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal(nameof(CustomClass.Property), properties[0].Name); + Assert.NotNull(properties[0].PropertyDescriptor); + Assert.Equal(typeof(int), properties[0].PropertyType); + Assert.Equal("1", properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.NotSame(obj, Assert.Single(properties[0].Items)); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Equal(1, properties[0].Value); + } + + [Fact] + public void Properties_GetArrayProperty_ReturnsExpected() + { + var array = new int[] { 1, 2, 3 }; + var instance = new ArrayClass + { + Property = array + }; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Same(instance, obj.Instance); + Assert.Equal(typeof(ArrayClass), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.NotEmpty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.True(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.False(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal(nameof(ArrayClass.Property), properties[0].Name); + Assert.NotNull(properties[0].PropertyDescriptor); + Assert.Equal(typeof(int[]), properties[0].PropertyType); + Assert.Empty(properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.NotSame(obj, Assert.Single(properties[0].Items)); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Equal(array, Assert.IsType(properties[0].Value).Items); + } + + [Fact] + public void Properties_GetListProperty_ReturnsExpected() + { + var list = new List { 1, 2, 3 }; + var instance = new ListClass + { + Property = list + }; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Same(instance, obj.Instance); + Assert.Equal(typeof(ListClass), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.NotEmpty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.True(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.False(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal(nameof(ListClass.Property), properties[0].Name); + Assert.NotNull(properties[0].PropertyDescriptor); + Assert.Equal(typeof(IList), properties[0].PropertyType); + Assert.Empty(properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.NotSame(obj, Assert.Single(properties[0].Items)); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Equal(list, properties[0].Value); + } + + [Fact] + public void Properties_GetDictionaryProperty_ReturnsExpected() + { + var dictionary = new Dictionary + { + { 1, "value1" }, + { 2, "value2" }, + { 3, "value3" } + }; + var instance = new DictionaryClass + { + Property = dictionary + }; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Same(instance, obj.Instance); + Assert.Equal(typeof(DictionaryClass), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.NotEmpty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.True(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.False(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal(nameof(DictionaryClass.Property), properties[0].Name); + Assert.NotNull(properties[0].PropertyDescriptor); + Assert.Equal(typeof(IDictionary), properties[0].PropertyType); + Assert.Empty(properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.NotSame(obj, Assert.Single(properties[0].Items)); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Equal(dictionary, properties[0].Value); + } + + [Fact] + public void Properties_GetMarkupExtensionDefault_ReturnsExpected() + { + var instance = new DefaultMarkupExtension(); + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Same(instance, obj.Instance); + Assert.Equal(typeof(DefaultMarkupExtension), obj.ObjectType); + Assert.Empty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + } + + [Fact] + public void Properties_GetMarkupExtensionWithTypeConverter_ReturnsExpected() + { + var instance = new MarkupExtensionWithTypeConverter(); + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Same(instance, obj.Instance); + Assert.Equal(typeof(MarkupExtensionWithTypeConverter), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Equal(2, properties.Count); + Assert.Empty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.False(properties[0].IsComposite); + Assert.True(properties[0].IsConstructorArgument); + Assert.False(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal("Argument", properties[0].Name); + Assert.Null(properties[0].PropertyDescriptor); + Assert.Equal(typeof(int), properties[0].PropertyType); + Assert.Equal("1", properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.NotSame(obj, Assert.Single(properties[0].Items)); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Equal(1, properties[0].Value); + + Assert.Empty(properties[1].Attributes); + Assert.Same(properties[1].Attributes, properties[1].Attributes); + Assert.Null(properties[1].DependencyProperty); + Assert.False(properties[1].IsAttached); + Assert.False(properties[1].IsComposite); + Assert.True(properties[1].IsConstructorArgument); + Assert.False(properties[1].IsContent); + Assert.False(properties[1].IsKey); + Assert.False(properties[1].IsValueAsString); + Assert.Equal("Argument", properties[1].Name); + Assert.Null(properties[1].PropertyDescriptor); + Assert.Equal(typeof(int), properties[1].PropertyType); + Assert.Equal("2", properties[1].StringValue); + Assert.Empty(properties[1].TypeReferences); + Assert.Same(properties[1].TypeReferences, properties[1].TypeReferences); + Assert.NotSame(obj, Assert.Single(properties[1].Items)); + Assert.NotSame(properties[1].Items, properties[1].Items); + Assert.Equal(2, properties[1].Value); + } + + private class DefaultMarkupExtension : MarkupExtension + { + public override object ProvideValue(IServiceProvider serviceProvider) => throw new NotImplementedException(); + } + + private class CustomTypeConverter : TypeConverter + { + public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) + { + if (destinationType == typeof(InstanceDescriptor)) + { + return true; + } + + return base.CanConvertTo(context, destinationType); + } + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (destinationType == typeof(InstanceDescriptor)) + { + var ctor = typeof(MarkupExtensionWithTypeConverter).GetConstructor(new[] { typeof(int), typeof(int) }); + Assert.NotNull(ctor); + return new InstanceDescriptor(ctor, new object[] { 1, 2 }); + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } + + [TypeConverter(typeof(CustomTypeConverter))] + private class MarkupExtensionWithTypeConverter : MarkupExtension + { + public MarkupExtensionWithTypeConverter() + { + } + + public MarkupExtensionWithTypeConverter(int property1, int property2) + { + } + + public override object ProvideValue(IServiceProvider serviceProvider) => throw new NotImplementedException(); + } + + private class CustomClass + { + public int Property { get; set; } + } + + private class ArrayClass + { + public int[]? Property { get; set; } + } + + private class ListClass + { + public IList? Property { get; set; } + } + + private class DictionaryClass + { + public IDictionary? Property { get; set; } + } + + private class IEnumerableWrapper : IEnumerable + { + private IEnumerable _enumerable; + + public IEnumerableWrapper(IEnumerable enumerable) + { + _enumerable = enumerable; + } + + public IEnumerator GetEnumerator() => _enumerable.GetEnumerator(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/Primitives/MarkupPropertyTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/Primitives/MarkupPropertyTests.cs new file mode 100644 index 00000000000..beac30b6741 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/Primitives/MarkupPropertyTests.cs @@ -0,0 +1,619 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.ComponentModel; +using System.ComponentModel.Design.Serialization; +using System.Globalization; +using System.Linq; +using System.Windows.Markup.Primitives.Tests.InternalClasses; + +namespace System.Windows.Markup.Primitives.Tests +{ + public class MarkupPropertyTests + { + [Fact] + public void Properties_GetConvertableToString_ReturnsExpected() + { + var instance = Rect.Empty; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Equal(instance, obj.Instance); + Assert.Equal(typeof(Rect), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.Empty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.False(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.False(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.True(properties[0].IsValueAsString); + Assert.Equal("StringValue", properties[0].Name); + Assert.Null(properties[0].PropertyDescriptor); + Assert.Equal(typeof(string), properties[0].PropertyType); + Assert.Equal("Empty", properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.Null(properties[0].Items); + Assert.Equal("Empty", properties[0].Value); + } + + [Fact] + public void Properties_GetArray_ReturnsExpected() + { + var instance = new int[] { 1, 2, 3 }; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Equal(instance, obj.Instance); + Assert.Equal(typeof(int[]), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.Empty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.True(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.True(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal("Items", properties[0].Name); + Assert.Null(properties[0].PropertyDescriptor); + Assert.Equal(typeof(IEnumerable), properties[0].PropertyType); + Assert.Empty(properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.Equal(instance.Length, properties[0].Items.Count()); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Equal(instance, Assert.IsType(properties[0].Value).Items); + } + + [Fact] + public void Properties_GetList_ReturnsExpected() + { + var instance = new List { 1, 2, 3 }; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Equal(instance, obj.Instance); + Assert.Equal(typeof(List), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Equal(2, properties.Count); + Assert.NotEmpty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.False(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.False(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal("Capacity", properties[0].Name); + Assert.NotNull(properties[0].PropertyDescriptor); + Assert.Equal(typeof(int), properties[0].PropertyType); + Assert.NotEmpty(properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.NotSame(obj, Assert.Single(properties[0].Items)); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.NotEqual(0, properties[0].Value); + + Assert.Empty(properties[1].Attributes); + Assert.Same(properties[1].Attributes, properties[1].Attributes); + Assert.Null(properties[1].DependencyProperty); + Assert.False(properties[1].IsAttached); + Assert.True(properties[1].IsComposite); + Assert.False(properties[1].IsConstructorArgument); + Assert.True(properties[1].IsContent); + Assert.False(properties[1].IsKey); + Assert.False(properties[1].IsValueAsString); + Assert.Equal("Items", properties[1].Name); + Assert.Null(properties[1].PropertyDescriptor); + Assert.Equal(typeof(IEnumerable), properties[1].PropertyType); + Assert.Empty(properties[1].StringValue); + Assert.Empty(properties[1].TypeReferences); + Assert.Same(properties[1].TypeReferences, properties[1].TypeReferences); + Assert.Equal(instance.Count, properties[1].Items.Count()); + Assert.NotSame(properties[1].Items, properties[1].Items); + Assert.Same(instance, properties[1].Value); + } + + [Fact] + public void Properties_GetDictionary_ReturnsExpected() + { + var instance = new Dictionary + { + { 1, "value1" }, + { 2, "value2" }, + { 3, "value3" } + }; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Equal(instance, obj.Instance); + Assert.Equal(typeof(Dictionary), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.Empty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.True(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.True(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal("Entries", properties[0].Name); + Assert.Null(properties[0].PropertyDescriptor); + Assert.Equal(typeof(IDictionary), properties[0].PropertyType); + Assert.Empty(properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.Equal(instance.Count, properties[0].Items.Count()); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Same(instance, properties[0].Value); + } + + [Fact] + public void Properties_GetEnumerable_ReturnsExpected() + { + var list = new List { 1, 2, 3 }; + var instance = new IEnumerableWrapper(list); + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Equal(instance, obj.Instance); + Assert.Equal(typeof(IEnumerableWrapper), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.Empty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.True(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.True(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal("Items", properties[0].Name); + Assert.Null(properties[0].PropertyDescriptor); + Assert.Equal(typeof(IEnumerable), properties[0].PropertyType); + Assert.Empty(properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.Equal(list.Count, properties[0].Items.Count()); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Same(instance, properties[0].Value); + } + + [Fact] + public void Properties_GetNormalProperty_ReturnsExpected() + { + var instance = new CustomClass + { + Property = 1 + }; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.Empty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Same(instance, obj.Instance); + Assert.Equal(typeof(CustomClass), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.NotEmpty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.False(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.False(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal(nameof(CustomClass.Property), properties[0].Name); + Assert.NotNull(properties[0].PropertyDescriptor); + Assert.Equal(typeof(int), properties[0].PropertyType); + Assert.Equal("1", properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.NotSame(obj, Assert.Single(properties[0].Items)); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Equal(1, properties[0].Value); + } + + [Fact] + public void Properties_GetArrayProperty_ReturnsExpected() + { + var array = new int[] { 1, 2, 3 }; + var instance = new ArrayClass + { + Property = array + }; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Same(instance, obj.Instance); + Assert.Equal(typeof(ArrayClass), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.NotEmpty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.True(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.False(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal(nameof(ArrayClass.Property), properties[0].Name); + Assert.NotNull(properties[0].PropertyDescriptor); + Assert.Equal(typeof(int[]), properties[0].PropertyType); + Assert.Empty(properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.NotSame(obj, Assert.Single(properties[0].Items)); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Equal(array, Assert.IsType(properties[0].Value).Items); + } + + [Fact] + public void Properties_GetListProperty_ReturnsExpected() + { + var list = new List { 1, 2, 3 }; + var instance = new ListClass + { + Property = list + }; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Same(instance, obj.Instance); + Assert.Equal(typeof(ListClass), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.NotEmpty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.True(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.False(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal(nameof(ListClass.Property), properties[0].Name); + Assert.NotNull(properties[0].PropertyDescriptor); + Assert.Equal(typeof(IList), properties[0].PropertyType); + Assert.Empty(properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.NotSame(obj, Assert.Single(properties[0].Items)); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Equal(list, properties[0].Value); + } + + [Fact] + public void Properties_GetDictionaryProperty_ReturnsExpected() + { + var dictionary = new Dictionary + { + { 1, "value1" }, + { 2, "value2" }, + { 3, "value3" } + }; + var instance = new DictionaryClass + { + Property = dictionary + }; + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Same(instance, obj.Instance); + Assert.Equal(typeof(DictionaryClass), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Single(properties); + Assert.NotEmpty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.True(properties[0].IsComposite); + Assert.False(properties[0].IsConstructorArgument); + Assert.False(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal(nameof(DictionaryClass.Property), properties[0].Name); + Assert.NotNull(properties[0].PropertyDescriptor); + Assert.Equal(typeof(IDictionary), properties[0].PropertyType); + Assert.Empty(properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.NotSame(obj, Assert.Single(properties[0].Items)); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Equal(dictionary, properties[0].Value); + } + + [Fact] + public void Properties_GetMarkupExtensionWithTypeConverter_ReturnsExpected() + { + var instance = new MarkupExtensionWithTypeConverter(); + MarkupObject obj = MarkupWriter.GetMarkupObjectFor(instance); + Assert.NotNull(obj); + Assert.NotEmpty(obj.Attributes); + Assert.Same(obj.Attributes, obj.Attributes); + Assert.Same(instance, obj.Instance); + Assert.Equal(typeof(MarkupExtensionWithTypeConverter), obj.ObjectType); + Assert.NotEmpty(obj.Properties); + Assert.NotSame(obj.Properties, obj.Properties); + + List properties = obj.Properties.ToList(); + Assert.Equal(2, properties.Count); + Assert.Empty(properties[0].Attributes); + Assert.Same(properties[0].Attributes, properties[0].Attributes); + Assert.Null(properties[0].DependencyProperty); + Assert.False(properties[0].IsAttached); + Assert.False(properties[0].IsComposite); + Assert.True(properties[0].IsConstructorArgument); + Assert.False(properties[0].IsContent); + Assert.False(properties[0].IsKey); + Assert.False(properties[0].IsValueAsString); + Assert.Equal("Argument", properties[0].Name); + Assert.Null(properties[0].PropertyDescriptor); + Assert.Equal(typeof(int), properties[0].PropertyType); + Assert.Equal("1", properties[0].StringValue); + Assert.Empty(properties[0].TypeReferences); + Assert.Same(properties[0].TypeReferences, properties[0].TypeReferences); + Assert.NotSame(obj, Assert.Single(properties[0].Items)); + Assert.NotSame(properties[0].Items, properties[0].Items); + Assert.Equal(1, properties[0].Value); + + Assert.Empty(properties[1].Attributes); + Assert.Same(properties[1].Attributes, properties[1].Attributes); + Assert.Null(properties[1].DependencyProperty); + Assert.False(properties[1].IsAttached); + Assert.False(properties[1].IsComposite); + Assert.True(properties[1].IsConstructorArgument); + Assert.False(properties[1].IsContent); + Assert.False(properties[1].IsKey); + Assert.False(properties[1].IsValueAsString); + Assert.Equal("Argument", properties[1].Name); + Assert.Null(properties[1].PropertyDescriptor); + Assert.Equal(typeof(int), properties[1].PropertyType); + Assert.Equal("2", properties[1].StringValue); + Assert.Empty(properties[1].TypeReferences); + Assert.Same(properties[1].TypeReferences, properties[1].TypeReferences); + Assert.NotSame(obj, Assert.Single(properties[1].Items)); + Assert.NotSame(properties[1].Items, properties[1].Items); + Assert.Equal(2, properties[1].Value); + } + + [Fact] + public void Write_ConvertableToString_ReturnsExpected() + { + // Test the default implementation of VerifyOnlySerializableTypes. + // Don't test the output. + var instance = Rect.Empty; + Assert.NotEmpty(XamlWriter.Save(instance)); + } + + [Fact] + public void Write_Array_ReturnsExpected() + { + // Test the default implementation of VerifyOnlySerializableTypes. + // Don't test the output. + var instance = new int[] { 1, 2, 3 }; + Assert.NotEmpty(XamlWriter.Save(instance)); + } + + [Fact] + public void Write_List_ThrowsInvalidOperationException() + { + // Test the default implementation of VerifyOnlySerializableTypes. + // Don't test the output. + var instance = new List { 1, 2, 3 }; + Assert.Throws(() => XamlWriter.Save(instance)); + } + + [Fact] + public void Write_Dictionary_ThrowsInvalidOperationException() + { + // Test the default implementation of VerifyOnlySerializableTypes. + // Don't test the output. + var instance = new Dictionary + { + { 1, "value1" }, + { 2, "value2" }, + { 3, "value3" } + }; + Assert.Throws(() => XamlWriter.Save(instance)); + } + + [Fact] + public void Write_Enumerable_ReturnsExpected() + { + // Test the default implementation of VerifyOnlySerializableTypes. + // Don't test the output. + var list = new List { 1, 2, 3 }; + var instance = new IEnumerableWrapper(list); + Assert.NotEmpty(XamlWriter.Save(instance)); + } + + [Fact] + public void Write_NormalProperty_ReturnsExpected() + { + // Test the default implementation of VerifyOnlySerializableTypes. + // Don't test the output. + var instance = new CustomClass + { + Property = 1 + }; + Assert.NotEmpty(XamlWriter.Save(instance)); + } + + [Fact] + public void Write_ArrayProperty_ReturnsExpected() + { + // Test the default implementation of VerifyOnlySerializableTypes. + // Don't test the output. + var array = new int[] { 1, 2, 3 }; + var instance = new ArrayClass + { + Property = array + }; + Assert.NotEmpty(XamlWriter.Save(instance)); + } + + [Fact] + public void Write_ListProperty_ThrowsInvalidOperationException() + { + // Test the default implementation of VerifyOnlySerializableTypes. + // Don't test the output. + var list = new List { 1, 2, 3 }; + var instance = new ListClass + { + Property = list + }; + Assert.Throws(() => XamlWriter.Save(instance)); + } + + [Fact] + public void Write_DictionaryProperty_ThrowsInvalidOperationException() + { + // Test the default implementation of VerifyOnlySerializableTypes. + // Don't test the output. + var dictionary = new Dictionary + { + { 1, "value1" }, + { 2, "value2" }, + { 3, "value3" } + }; + var instance = new DictionaryClass + { + Property = dictionary + }; + Assert.Throws(() => XamlWriter.Save(instance)); + } + + [Fact] + public void Write_MarkupExtensionWithTypeConverter_ReturnsExpected() + { + // Test the default implementation of VerifyOnlySerializableTypes. + // Don't test the output. + var instance = new MarkupExtensionWithTypeConverter(); + Assert.NotEmpty(XamlWriter.Save(instance)); + } + } +} + +namespace System.Windows.Markup.Primitives.Tests.InternalClasses +{ + public class CustomTypeConverter : TypeConverter + { + public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType) + { + if (destinationType == typeof(InstanceDescriptor)) + { + return true; + } + + return base.CanConvertTo(context, destinationType); + } + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (destinationType == typeof(InstanceDescriptor)) + { + var ctor = typeof(MarkupExtensionWithTypeConverter).GetConstructor(new[] { typeof(int), typeof(int) }); + Assert.NotNull(ctor); + return new InstanceDescriptor(ctor, new object[] { 1, 2 }); + } + + return base.ConvertTo(context, culture, value, destinationType); + } + } + + [TypeConverter(typeof(CustomTypeConverter))] + public class MarkupExtensionWithTypeConverter : MarkupExtension + { + public MarkupExtensionWithTypeConverter() + { + } + + public MarkupExtensionWithTypeConverter(int property1, int property2) + { + } + + public override object ProvideValue(IServiceProvider serviceProvider) => throw new NotImplementedException(); + } + + public class CustomClass + { + public int Property { get; set; } + } + + public class ArrayClass + { + public int[]? Property { get; set; } + } + + public class ListClass + { + public IList? Property { get; set; } + } + + public class DictionaryClass + { + public IDictionary? Property { get; set; } + } + + public class IEnumerableWrapper : IEnumerable + { + private IEnumerable _enumerable; + + public IEnumerableWrapper(IEnumerable enumerable) + { + _enumerable = enumerable; + } + + public IEnumerator GetEnumerator() => _enumerable.GetEnumerator(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/ServiceProvidersTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/ServiceProvidersTests.cs new file mode 100644 index 00000000000..a823bd1ceea --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/ServiceProvidersTests.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Markup.Tests; + +public class ServiceProvidersTests +{ + [Fact] + public void AddService_Invoke_GetServiceReturnsExpected() + { + var serviceProviders = new ServiceProviders(); + + var service1 = new object(); + serviceProviders.AddService(typeof(object), service1); + Assert.Same(service1, serviceProviders.GetService(typeof(object))); + + // Add again. + serviceProviders.AddService(typeof(object), service1); + Assert.Same(service1, serviceProviders.GetService(typeof(object))); + + // Add another. + var service2 = new object(); + serviceProviders.AddService(typeof(string), service2); + Assert.Same(service1, serviceProviders.GetService(typeof(object))); + Assert.Same(service2, serviceProviders.GetService(typeof(string))); + } + + [Fact] + public void AddService_NullServiceType_ThrowsArgumentNullException() + { + var serviceProviders = new ServiceProviders(); + Assert.Throws("serviceType", () => serviceProviders.AddService(null, new object())); + } + + [Fact] + public void AddService_NullService_ThrowsArgumentNullException() + { + var serviceProviders = new ServiceProviders(); + Assert.Throws("service", () => serviceProviders.AddService(typeof(object), null)); + } + + [Fact] + public void AddService_ServiceTypeExists_ThrowsArgumentException() + { + var serviceProviders = new ServiceProviders(); + serviceProviders.AddService(typeof(object), new object()); + Assert.Throws("serviceType", () => serviceProviders.AddService(typeof(object), new object())); + } + + [Fact] + public void GetService_NoSuchServiceTypeEmpty_ReturnsNull() + { + var serviceProviders = new ServiceProviders(); + Assert.Null(serviceProviders.GetService(typeof(object))); + } + + [Fact] + public void GetService_NoSuchServiceTypeNotEmpty_ReturnsNull() + { + var serviceProviders = new ServiceProviders(); + serviceProviders.AddService(typeof(string), new object()); + + Assert.Null(serviceProviders.GetService(typeof(object))); + } + + [Fact] + public void GetService_NullServiceTypeEmpty_ThrowsArgumentNullException() + { + var serviceProviders = new ServiceProviders(); + // TODO: should have correct paramName + Assert.Throws("key", () => serviceProviders.GetService(null)); + } + + [Fact] + public void GetService_NullServiceTypeNotEmpty_ThrowsArgumentNullException() + { + var serviceProviders = new ServiceProviders(); + serviceProviders.AddService(typeof(string), new object()); + + // TODO: should have correct paramName + Assert.Throws("key", () => serviceProviders.GetService(null)); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Media/Converters/MatrixValueSerializerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Media/Converters/MatrixValueSerializerTests.cs new file mode 100644 index 00000000000..d78c7b34020 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Media/Converters/MatrixValueSerializerTests.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Windows.Markup; +using System.Windows.Media.Tests; + +namespace System.Windows.Media.Converters.Tests; + +public class MatrixValueSerializerTests +{ + public static IEnumerable CanConvertToString_TestData() + { + yield return new object?[] { null, false }; + yield return new object?[] { string.Empty, false }; + yield return new object?[] { "value", false }; + yield return new object?[] { new object(), false }; + + yield return new object?[] { new Matrix(), true }; + } + + [Theory] + [MemberData(nameof(CanConvertToString_TestData))] + public void CanConvertToString_Invoke_ReturnsExpected(object value, bool expected) + { + var serializer = new MatrixValueSerializer(); + Assert.Equal(expected, serializer.CanConvertToString(value, null)); + Assert.Equal(expected, serializer.CanConvertToString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(MatrixTests.ToString_TestData), MemberType = typeof(MatrixTests))] + public void ConvertToString_Invoke_ReturnsExpected(Matrix matrix, string expected) + { + var serializer = new MatrixValueSerializer(); + Assert.Equal(expected, serializer.ConvertToString(matrix, null)); + Assert.Equal(expected, serializer.ConvertToString(matrix, new CustomValueSerializerContext())); + } + + public static IEnumerable ConvertToString_CantConvert_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + yield return new object?[] { new object() }; + } + + [Theory] + [MemberData(nameof(ConvertToString_CantConvert_TestData))] + public void ConvertToString_CantConvert_ThrowsNotSupportedException(object value) + { + var serializer = new MatrixValueSerializer(); + Assert.Throws(() => serializer.ConvertToString(value, null)); + Assert.Throws(() => serializer.ConvertToString(value, new CustomValueSerializerContext())); + } + + public static IEnumerable CanConvertFromString_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + } + + [Theory] + [MemberData(nameof(CanConvertFromString_TestData))] + public void CanConvertFromString_Invoke_ReturnsTrue(string value) + { + var serializer = new MatrixValueSerializer(); + Assert.True(serializer.CanConvertFromString(value, null)); + Assert.True(serializer.CanConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(MatrixTests.Parse_TestData), MemberType = typeof(MatrixTests))] + public void ConvertFromString_Invoke_ReturnsExpected(string value, Matrix expected) + { + var serializer = new MatrixValueSerializer(); + Assert.Equal(expected, serializer.ConvertFromString(value, null)); + Assert.Equal(expected, serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Fact] + public void ConvertFromString_NullValue_ThrowsNotSupportedException() + { + var serializer = new MatrixValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(null, null)); + Assert.Throws(() => serializer.ConvertFromString(null, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(MatrixTests.Parse_InvalidSource_TestData), MemberType = typeof(MatrixTests))] + public void ConvertFromString_InvalidValue_ThrowsInvalidOperationException(string value) + { + var serializer = new MatrixValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + [Theory] + [MemberData(nameof(MatrixTests.Parse_NotDouble_TestData), MemberType = typeof(MatrixTests))] + public void ConvertFromString_NotDouble_ThrowsFormatException(string value) + { + var serializer = new MatrixValueSerializer(); + Assert.Throws(() => serializer.ConvertFromString(value, null)); + Assert.Throws(() => serializer.ConvertFromString(value, new CustomValueSerializerContext())); + } + + private class CustomValueSerializerContext : IValueSerializerContext + { + public IContainer Container => throw new NotImplementedException(); + + public object Instance => throw new NotImplementedException(); + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(PropertyDescriptor descriptor) => throw new NotImplementedException(); + + public ValueSerializer GetValueSerializerFor(Type type) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Media/DisableDpiAwarenessAttributeTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Media/DisableDpiAwarenessAttributeTests.cs new file mode 100644 index 00000000000..be9c37b058d --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Media/DisableDpiAwarenessAttributeTests.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Media.Test; + +public class DisableDpiAwarenessAttributeTests +{ + [Fact] + public void Ctor_Default() + { + var attribute = new DisableDpiAwarenessAttribute(); + Assert.Equal(typeof(DisableDpiAwarenessAttribute), attribute.TypeId); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Media/MatrixConverterTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Media/MatrixConverterTests.cs new file mode 100644 index 00000000000..c0230180133 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Media/MatrixConverterTests.cs @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.ComponentModel.Design.Serialization; +using System.Globalization; + +namespace System.Windows.Media.Tests; + +public class MatrixConverterTests +{ + [Theory] + [InlineData(null, false)] + [InlineData(typeof(object), false)] + [InlineData(typeof(string), true)] + [InlineData(typeof(InstanceDescriptor), false)] + [InlineData(typeof(Matrix), false)] + public void CanConvertTo_Invoke_ReturnsExpected(Type? destinationType, bool expected) + { + var converter = new MatrixConverter(); + Assert.Equal(expected, converter.CanConvertTo(null, destinationType)); + Assert.Equal(expected, converter.CanConvertTo(new CustomTypeDescriptorContext(), destinationType)); + } + + [Theory] + [MemberData(nameof(MatrixTests.ToString_TestData), MemberType = typeof(MatrixTests))] + public void ConvertTo_InvokeMatrixToString_ReturnsExpected(Matrix value, string expected) + { + var converter = new MatrixConverter(); + Assert.Equal(expected, converter.ConvertTo(value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + } + + [Theory] + [MemberData(nameof(MatrixTests.ToString_IFormatProviderCustom_TestData), MemberType = typeof(MatrixTests))] + public void ConvertTo_InvokeMatrixToStringCustomCulture_ReturnsExpected(Matrix value, string numberDecimalSeparator, string expected) + { + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + var converter = new MatrixConverter(); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), culture, value, typeof(string))); + } + + [Theory] + [InlineData(null, "")] + [InlineData("", "")] + [InlineData("value", "value")] + [InlineData(1, "1")] + public void ConvertTo_InvokeNotMatrixToString_ReturnsExpected(object? value, string expected) + { + var converter = new MatrixConverter(); + Assert.Equal(expected, converter.ConvertTo(value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + } + + public static IEnumerable ConvertTo_CantConvert_TestData() + { + yield return new object?[] { null, typeof(object) }; + yield return new object?[] { string.Empty, typeof(object) }; + yield return new object?[] { "value", typeof(object) }; + yield return new object?[] { new object(), typeof(object) }; + yield return new object?[] { new Matrix(), typeof(object) }; + + yield return new object?[] { null, typeof(Matrix) }; + yield return new object?[] { string.Empty, typeof(Matrix) }; + yield return new object?[] { "value", typeof(Matrix) }; + yield return new object?[] { new object(), typeof(Matrix) }; + yield return new object?[] { new Matrix(), typeof(Matrix) }; + } + + [Theory] + [MemberData(nameof(ConvertTo_CantConvert_TestData))] + public void ConvertTo_CantConvert_ThrowsNotSupportedException(object value, Type destinationType) + { + var converter = new MatrixConverter(); + Assert.Throws(() => converter.ConvertTo(value, destinationType)); + Assert.Throws(() => converter.ConvertTo(null, null, value, destinationType)); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, destinationType)); + } + + public static IEnumerable ConvertTo_NullDestinationType_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + yield return new object?[] { new object() }; + yield return new object?[] { new Matrix() }; + } + + [Theory] + [MemberData(nameof(ConvertTo_NullDestinationType_TestData))] + public void ConvertTo_NullDestinationType_ThrowsArgumentNullException(object value) + { + var converter = new MatrixConverter(); + Assert.Throws("destinationType", () => converter.ConvertTo(value, null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(null, null, new Matrix(), null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, new Matrix(), null!)); + } + + [Theory] + [InlineData(null, false)] + [InlineData(typeof(object), false)] + [InlineData(typeof(string), true)] + [InlineData(typeof(InstanceDescriptor), true)] + [InlineData(typeof(Matrix), false)] + public void CanConvertFrom_Invoke_ReturnsExpected(Type? sourceType, bool expected) + { + var converter = new MatrixConverter(); + Assert.Equal(expected, converter.CanConvertFrom(sourceType!)); + Assert.Equal(expected, converter.CanConvertFrom(null, sourceType)); + Assert.Equal(expected, converter.CanConvertFrom(new CustomTypeDescriptorContext(), sourceType)); + } + + [Theory] + [MemberData(nameof(MatrixTests.Parse_TestData), MemberType = typeof(MatrixTests))] + public void ConvertFrom_InvokeStringValue_ReturnsExpected(object value, Matrix expected) + { + var converter = new MatrixConverter(); + Assert.Equal(expected, converter.ConvertFrom(value)); + Assert.Equal(expected, converter.ConvertFrom(null, null, value)); + Assert.Equal(expected, converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Fact] + public void ConvertFrom_NullValue_ThrowsNotSupportedException() + { + var converter = new MatrixConverter(); + Assert.Throws(() => converter.ConvertFrom(null!)); + Assert.Throws(() => converter.ConvertFrom(null, null, null)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, null)); + } + + [Theory] + [MemberData(nameof(MatrixTests.Parse_InvalidSource_TestData), MemberType = typeof(MatrixTests))] + public void ConvertFrom_InvalidStringValue_ThrowsInvalidOperationException(object value) + { + var converter = new MatrixConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Theory] + [MemberData(nameof(MatrixTests.Parse_NotDouble_TestData), MemberType = typeof(MatrixTests))] + public void ConvertFrom_NotDoubleStringValue_ThrowsFormatException(object value) + { + var converter = new MatrixConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + public static IEnumerable ConvertFrom_CantConvert_TestData() + { + yield return new object[] { new object() }; + yield return new object[] { new Matrix() }; + } + + [Theory] + [MemberData(nameof(ConvertFrom_CantConvert_TestData))] + public void ConvertFrom_CantConvert_ThrowsNotSupportedException(object value) + { + var converter = new MatrixConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + private class CustomTypeDescriptorContext : ITypeDescriptorContext + { + public IContainer Container => throw new NotImplementedException(); + + public object Instance => throw new NotImplementedException(); + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Media/MatrixTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Media/MatrixTests.cs new file mode 100644 index 00000000000..f7d1c6185b4 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Media/MatrixTests.cs @@ -0,0 +1,5635 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Globalization; +using System.Windows.Markup; +using System.Windows.Media.Converters; + + +namespace System.Windows.Media.Tests; + +public class MatrixTests +{ + [Fact] + public void Ctor_Default() + { + var matrix = new Matrix(); + + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.True(matrix.HasInverse); + Assert.True(matrix.IsIdentity); + Assert.Equal(1, matrix.Determinant); + Assert.Equal(Matrix.Identity, matrix); + } + + public static IEnumerable Ctor_MatrixElements_TestData() + { + // Identity. + yield return new object[] { 1, 0, 0, 1, 0, 0, true, true, 1 }; + + // Scale. + yield return new object[] { 1, 0, 0, 2, 0, 0, true, false, 2 }; + yield return new object[] { 2, 0, 0, 1, 0, 0, true, false, 2 }; + yield return new object[] { 2, 0, 0, 3, 0, 0, true, false, 6 }; + + // Skew. + yield return new object[] { 1, 1, 0, 1, 0, 0, true, false, 1 }; + yield return new object[] { 1, 0, 2, 1, 0, 0, true, false, 1 }; + yield return new object[] { 1, 1, 2, 1, 0, 0, true, false, -1 }; + + // Translate. + yield return new object[] { 1, 0, 0, 1, 1, 0, true, false, 1 }; + yield return new object[] { 1, 0, 0, 1, 0, 2, true, false, 1 }; + yield return new object[] { 1, 0, 0, 1, 1, 2, true, false, 1 }; + + // Translate + Scale. + yield return new object[] { 2, 0, 0, 3, 4, 5, true, false, 6 }; + + // Translate + Skew. + yield return new object[] { 1, 2, 3, 1, 4, 5, true, false, -5 }; + + // Skew + Scale. + yield return new object[] { 1, 2, 3, 1, 0, 0, true, false, -5 }; + + // Complex. + yield return new object[] { 2, 3, 4, 5, 6, 7, true, false, -2 }; + yield return new object[] { -1, -2, -3, -4, -5, -6, true, false, -2 }; + yield return new object[] { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, true, false, -2.42 }; + + // Zero. + yield return new object[] { 0, 0, 0, 0, 0, 0, false, false, 0 }; + + // Infinity. + yield return new object[] { double.PositiveInfinity, 0, 0, 1, 0, 0, true, false, double.PositiveInfinity }; + yield return new object[] { 1, double.PositiveInfinity, 0, 1, 0, 0, true, false, double.NaN }; + yield return new object[] { 1, 0, double.PositiveInfinity, 1, 0, 0, true, false, double.NaN }; + yield return new object[] { 1, 0, 0, double.PositiveInfinity, 0, 0, true, false, double.PositiveInfinity }; + yield return new object[] { 1, 0, 0, 1, double.PositiveInfinity, 0, true, false, 1 }; + yield return new object[] { 1, 0, 0, 1, 0, double.PositiveInfinity, true, false, 1 }; + yield return new object[] { double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity, true, false, double.NaN }; + yield return new object[] { double.NegativeInfinity, 0, 0, 1, 0, 0, true, false, double.NegativeInfinity }; + yield return new object[] { 1, double.NegativeInfinity, 0, 1, 0, 0, true, false, double.NaN }; + yield return new object[] { 1, 0, double.NegativeInfinity, 1, 0, 0, true, false, double.NaN }; + yield return new object[] { 1, 0, 0, double.NegativeInfinity, 0, 0, true, false, double.NegativeInfinity }; + yield return new object[] { 1, 0, 0, 1, double.NegativeInfinity, 0, true, false, 1 }; + yield return new object[] { 1, 0, 0, 1, 0, double.NegativeInfinity, true, false, 1 }; + yield return new object[] { double.NegativeInfinity, double.NegativeInfinity, double.NegativeInfinity, double.NegativeInfinity, double.NegativeInfinity, double.NegativeInfinity, true, false, double.NaN }; + + // NaN. + yield return new object[] { double.NaN, 0, 0, 1, 0, 0, true, false, double.NaN }; + yield return new object[] { 1, double.NaN, 0, 1, 0, 0, true, false, double.NaN }; + yield return new object[] { 1, 0, double.NaN, 1, 0, 0, true, false, double.NaN }; + yield return new object[] { 1, 0, 0, double.NaN, 0, 0, true, false, double.NaN }; + yield return new object[] { 1, 0, 0, 1, double.NaN, 0, true, false, 1 }; + yield return new object[] { 1, 0, 0, 1, 0, double.NaN, true, false, 1 }; + yield return new object[] { double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, true, false, double.NaN }; + } + + [Theory] + [MemberData(nameof(Ctor_MatrixElements_TestData))] + public void Ctor_MatrixElements(double m11, double m12, double m21, double m22, double offsetX, double offsetY, bool hasInverse, bool isIdentity, double expectedDeterminant) + { + var matrix = new Matrix(m11, m12, m21, m22, offsetX, offsetY); + Assert.Equal(m11, matrix.M11); + Assert.Equal(m12, matrix.M12); + Assert.Equal(m21, matrix.M21); + Assert.Equal(m22, matrix.M22); + Assert.Equal(offsetX, matrix.OffsetX); + Assert.Equal(offsetY, matrix.OffsetY); + Assert.Equal(hasInverse, matrix.HasInverse); + Assert.Equal(isIdentity, matrix.IsIdentity); + Assert.Equal(expectedDeterminant, matrix.Determinant, precision: 5); + } + + [Fact] + public void Identity_Get_ReturnsExpected() + { + Matrix matrix = Matrix.Identity; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.True(matrix.HasInverse); + Assert.True(matrix.IsIdentity); + Assert.Equal(1, matrix.Determinant); + Assert.Equal(Matrix.Identity, matrix); + Assert.Equal(new Matrix(), matrix); + } + + public static IEnumerable Determinant_Get_TestData() + { + // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, 1 }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 6 }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1 }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 6 }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -2 }; + + // No inverse. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0 }; + } + + [Theory] + [MemberData(nameof(Determinant_Get_TestData))] + public void Determinant_Get_ReturnsExpected(Matrix matrix, double expected) + { + Assert.Equal(expected, matrix.Determinant, precision: 5); + } + public static IEnumerable HasInverse_Get_TestData() + { + // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, true }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), true }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), true }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), true }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), true }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), true }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), true }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), true }; + + // No inverse. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), false }; + } + + [Theory] + [MemberData(nameof(HasInverse_Get_TestData))] + public void HasInverse_Get_ReturnsExpected(Matrix matrix, bool expected) + { + Assert.Equal(expected, matrix.HasInverse); + } + + [Theory] + [MemberData(nameof(Multiply_TestData))] + public void Append_Invoke_ReturnsExpected(Matrix matrix1, Matrix matrix2, Matrix expected) + { + Matrix copy1 = matrix1; + Matrix copy2 = matrix2; + + matrix1.Append(matrix2); + Assert.Equal(expected, matrix1); + Assert.Equal(copy2, matrix2); + } + + public static IEnumerable Equals_TestData() + { + // Identity. + foreach (Matrix identity1 in IdentityMatrices()) + { + // Identity + identity. + foreach (Matrix identity2 in IdentityMatrices()) + { + yield return new object?[] { identity1, identity2, true, true }; + } + + // Identity + scaled. + yield return new object?[] { identity1, new Matrix(2, 0, 0, 1, 0, 0), false, false }; + yield return new object?[] { identity1, new Matrix(1, 0, 0, 5, 0, 0), false, false }; + yield return new object?[] { identity1, new Matrix(2, 0, 0, 5, 0, 0), false, false }; + + // Identity + skewed. + yield return new object?[] { identity1, new Matrix(1, 3, 0, 1, 0, 0), false, false }; + yield return new object?[] { identity1, new Matrix(1, 0, 4, 1, 0, 0), false, false }; + yield return new object?[] { identity1, new Matrix(1, 3, 4, 1, 0, 0), false, false }; + + // Identity + translated. + yield return new object?[] { identity1, new Matrix(1, 0, 0, 1, 6, 0), false, false }; + yield return new object?[] { identity1, new Matrix(1, 0, 0, 1, 0, 7), false, false }; + yield return new object?[] { identity1, new Matrix(1, 0, 0, 1, 6, 7), false, false }; + + // Identity + translated and scaled. + yield return new object?[] { identity1, new Matrix(2, 0, 0, 1, 6, 7), false, false }; + yield return new object?[] { identity1, new Matrix(1, 0, 0, 5, 6, 7), false, false }; + yield return new object?[] { identity1, new Matrix(2, 0, 0, 5, 6, 0), false, false }; + yield return new object?[] { identity1, new Matrix(2, 0, 0, 5, 0, 7), false, false }; + yield return new object?[] { identity1, new Matrix(2, 0, 0, 5, 6, 7), false, false }; + + // Identity + translate and skewed. + yield return new object?[] { identity1, new Matrix(1, 3, 0, 1, 6, 7), false, false }; + yield return new object?[] { identity1, new Matrix(1, 0, 4, 1, 6, 7), false, false }; + yield return new object?[] { identity1, new Matrix(1, 3, 4, 1, 6, 0), false, false }; + yield return new object?[] { identity1, new Matrix(1, 3, 4, 1, 0, 7), false, false }; + yield return new object?[] { identity1, new Matrix(1, 3, 4, 1, 6, 7), false, false }; + + // Identity + skewed and scaled. + yield return new object?[] { identity1, new Matrix(2, 3, 4, 5, 0, 0), false, false }; + yield return new object?[] { identity1, new Matrix(1, 3, 4, 5, 0, 0), false, false }; + yield return new object?[] { identity1, new Matrix(2, 3, 0, 5, 0, 0), false, false }; + yield return new object?[] { identity1, new Matrix(2, 0, 4, 5, 0, 0), false, false }; + yield return new object?[] { identity1, new Matrix(2, 3, 4, 5, 0, 0), false, false }; + + // Identity + complex. + yield return new object?[] { identity1, new Matrix(2, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { identity1, new Matrix(0, 0, 0, 0, 0, 0), false, true }; + } + + // Scaled + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), identity, false, false }; + } + + // Scaled + scaled. + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 5, 0, 0), true, true }; + + // Scaled + skewed. + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 4, 1, 0, 0), false, false }; + + // Scaled + translated. + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 1, 6, 7), false, false }; + + // Scaled + translated and scaled. + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 5, 0, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 5, 6, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 5, 6, 7), false, false }; + + // Scaled + translated and skewed. + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 4, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 4, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 4, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 4, 1, 6, 7), false, false }; + + // Scaled + skewed and scaled. + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 3, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 3, 0, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 3, 4, 5, 0, 0), false, false }; + + // Scaled + complex. + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(0, 0, 0, 0, 0, 0), false, false }; + + // Skewed + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), identity, false, false }; + } + + // Skewed + scaled. + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 0, 2, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 5, 0, 0), false, false }; + + // Skewed + skewed. + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 1, 0, 0), true, true }; + + // Skewed + translated. + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 0, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 0, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 0, 1, 6, 7), false, false }; + + // Skewed + translated and scaled. + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 0, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 5, 6, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 5, 0, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 5, 6, 7), false, false }; + + // Skewed + translated and skewed. + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 4, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 1, 6, 7), false, false }; + + // Skewed + skewed and scaled. + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 3, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 3, 0, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 3, 4, 5, 0, 0), false, false }; + + // Skewed + complex. + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(0, 0, 0, 0, 0, 0), false, false }; + + // Translated + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), identity, false, false }; + } + + // Translated + scaled. + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 2, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 5, 0, 0), false, false }; + + // Translated + skewed. + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 1, 0, 0), false, false }; + + // Translated + translated. + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 1, 6, 7), true, true }; + + // Translated + translated and scaled. + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 5, 6, 0), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 5, 0, 7), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 5, 6, 7), false, false }; + + // Translated + translated and skewed. + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 4, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 1, 6, 7), false, false }; + + // Translated + skewed and scaled. + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 3, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 3, 0, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 3, 4, 5, 0, 0), false, false }; + + // Translated + complex. + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(0, 0, 0, 0, 0, 0), false, false }; + + // Translated and scaled + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), identity, false, false }; + } + + // Translated and scaled + scaled. + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 2, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 5, 0, 0), false, false }; + + // Translated and scaled + skewed. + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 4, 1, 0, 0), false, false }; + + // Translated and scaled + translated. + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 1, 6, 7), false, false }; + + // Translated and scaled + translated and scaled. + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 5, 6, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 5, 0, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 5, 6, 7), true, true }; + + // Translated and scaled + translated and skewed. + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 4, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 4, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 4, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 4, 1, 6, 7), false, false }; + + // Translated and scaled + skewed and scaled. + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 3, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 3, 0, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 3, 4, 5, 0, 0), false, false }; + + // Translated and scaled + complex. + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(0, 0, 0, 0, 0, 0), false, false }; + + // Translated and skewed + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), identity, false, false }; + } + + // Translated and skewed + scaled. + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 0, 2, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 5, 0, 0), false, false }; + + // Translated and skewed + skewed. + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 1, 0, 0), false, false }; + + // Translated and skewed + translated. + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 0, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 0, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 0, 1, 6, 7), false, false }; + + // Translated and skewed + translated and scaled. + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 0, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 5, 6, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 5, 0, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 5, 6, 7), false, false }; + + // Translated and skewed + translated and skewed. + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 4, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 1, 6, 7), true, true }; + + // Translated and skewed + skewed and scaled. + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 3, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 3, 0, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 3, 4, 5, 0, 0), false, false }; + + // Translated and skewed + complex. + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(0, 0, 0, 0, 0, 0), false, false }; + + // Skewed and scaled + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), identity, false, false }; + } + + // Skewed and scaled + scaled. + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 0, 2, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 5, 0, 0), false, false }; + + // Skewed and scaled + skewed. + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 1, 0, 0), false, false }; + + // Skewed and scaled + translated. + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 0, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 0, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 0, 1, 6, 7), false, false }; + + // Skewed and scaled + translated and scaled. + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 0, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 5, 6, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 5, 0, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 5, 6, 7), false, false }; + + // Skewed and scaled + translated and skewed. + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 4, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 1, 6, 7), false, false }; + + // Skewed and scaled + skewed and scaled. + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 3, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 3, 0, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 3, 4, 5, 0, 0), true, true }; + + // Skewed and scaled + complex. + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(0, 0, 0, 0, 0, 0), false, false }; + + // Complex + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), identity, false, false }; + } + + // Complex + scaled. + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 0, 2, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 5, 0, 0), false, false }; + + // Complex + skewed. + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 4, 1, 0, 0), false, false }; + + // Complex + translated. + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 0, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 0, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 0, 1, 6, 7), false, false }; + + // Complex + translated and scaled. + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 0, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 5, 6, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 5, 0, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 5, 6, 7), false, false }; + + // Complex + translated and skewed. + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 4, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 4, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 4, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 4, 1, 6, 7), false, false }; + + // Complex + skewed and scaled. + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 0, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 0, 0), false, false }; + + // Complex + complex. + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), true, true }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(0, 0, 0, 0, 0, 0), false, false }; + + // Zero + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), identity, false, true }; + } + + // Zero + scaled. + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 0, 2, 0, 0), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 5, 0, 0), false, false }; + + // Zero + skewed. + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 0, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 4, 1, 0, 0), false, false }; + + // Zero + translated. + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 0, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 0, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 0, 1, 6, 7), false, false }; + + // Zero + translated and scaled. + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 0, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 5, 6, 0), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 5, 0, 7), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 5, 6, 7), false, false }; + + // Zero + translated and skewed. + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 0, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 4, 1, 6, 7), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 4, 1, 6, 0), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 4, 1, 0, 7), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 4, 1, 6, 7), false, false }; + + // Zero + skewed and scaled. + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 3, 4, 1, 0, 0), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 3, 0, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 4, 5, 0, 0), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 3, 4, 5, 0, 0), false, false }; + + // Zero + complex. + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(0, 0, 0, 0, 0, 0), true, true }; + + // NaN. + yield return new object?[] { new Matrix(double.NaN, 3, 4, 5, 6, 7), new Matrix(double.NaN, 3, 4, 5, 6, 7), true, true }; + yield return new object?[] { new Matrix(double.NaN, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, double.NaN, 4, 5, 6, 7), new Matrix(2, double.NaN, 4, 5, 6, 7), true, true }; + yield return new object?[] { new Matrix(2, double.NaN, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, double.NaN, 5, 6, 7), new Matrix(2, 3, double.NaN, 5, 6, 7), true, true }; + yield return new object?[] { new Matrix(2, 3, double.NaN, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, double.NaN, 6, 7), new Matrix(2, 3, 4, double.NaN, 6, 7), true, true }; + yield return new object?[] { new Matrix(2, 3, 4, double.NaN, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, double.NaN, 7), new Matrix(2, 3, 4, 5, double.NaN, 7), true, true }; + yield return new object?[] { new Matrix(2, 3, 4, 5, double.NaN, 7), new Matrix(2, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, double.NaN), new Matrix(2, 3, 4, 5, 6, double.NaN), true, true }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, double.NaN), new Matrix(2, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), true, true }; + yield return new object?[] { new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), new Matrix(2, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(double.NaN, 3, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, double.NaN, 4, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, double.NaN, 5, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, double.NaN, 6, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, double.NaN, 7), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, double.NaN), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), false, false }; + + // Others. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object?[] { identity, new object(), false, false }; + yield return new object?[] { identity, null, false, false }; + } + + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), new object(), false, false }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), null, false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), new object(), false, false }; + yield return new object?[] { new Matrix(0, 0, 0, 0, 0, 0), null, false, false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void Equals_Object_ReturnsExpected(Matrix matrix, object o, bool expected, bool expectedHashCode) + { + Assert.Equal(expected, matrix.Equals(o)); + if (o is Matrix value) + { + Assert.Equal(expected, matrix.Equals(value)); + Assert.Equal(expected, value.Equals(matrix)); + Assert.Equal(expected, Matrix.Equals(matrix, value)); + Assert.Equal(expected, Matrix.Equals(value, matrix)); + Assert.Equal(expectedHashCode, matrix.GetHashCode().Equals(value.GetHashCode())); + } + } + + public static IEnumerable EqualityOperator_TestData() + { + // Identity. + foreach (Matrix identity1 in IdentityMatrices()) + { + // Identity + identity. + foreach (Matrix identity2 in IdentityMatrices()) + { + yield return new object[] { identity1, identity2, true }; + } + + // Identity + scaled. + yield return new object[] { identity1, new Matrix(2, 0, 0, 1, 0, 0), false }; + yield return new object[] { identity1, new Matrix(1, 0, 0, 5, 0, 0), false }; + yield return new object[] { identity1, new Matrix(2, 0, 0, 5, 0, 0), false }; + + // Identity + skewed. + yield return new object[] { identity1, new Matrix(1, 3, 0, 1, 0, 0), false }; + yield return new object[] { identity1, new Matrix(1, 0, 4, 1, 0, 0), false }; + yield return new object[] { identity1, new Matrix(1, 3, 4, 1, 0, 0), false }; + + // Identity + translated. + yield return new object[] { identity1, new Matrix(1, 0, 0, 1, 6, 0), false }; + yield return new object[] { identity1, new Matrix(1, 0, 0, 1, 0, 7), false }; + yield return new object[] { identity1, new Matrix(1, 0, 0, 1, 6, 7), false }; + + // Identity + translated and scaled. + yield return new object[] { identity1, new Matrix(2, 0, 0, 1, 6, 7), false }; + yield return new object[] { identity1, new Matrix(1, 0, 0, 5, 6, 7), false }; + yield return new object[] { identity1, new Matrix(2, 0, 0, 5, 6, 0), false }; + yield return new object[] { identity1, new Matrix(2, 0, 0, 5, 0, 7), false }; + yield return new object[] { identity1, new Matrix(2, 0, 0, 5, 6, 7), false }; + + // Identity + translate and skewed. + yield return new object[] { identity1, new Matrix(1, 3, 0, 1, 6, 7), false }; + yield return new object[] { identity1, new Matrix(1, 0, 4, 1, 6, 7), false }; + yield return new object[] { identity1, new Matrix(1, 3, 4, 1, 6, 0), false }; + yield return new object[] { identity1, new Matrix(1, 3, 4, 1, 0, 7), false }; + yield return new object[] { identity1, new Matrix(1, 3, 4, 1, 6, 7), false }; + + // Identity + skewed and scaled. + yield return new object[] { identity1, new Matrix(2, 3, 4, 5, 0, 0), false }; + yield return new object[] { identity1, new Matrix(1, 3, 4, 5, 0, 0), false }; + yield return new object[] { identity1, new Matrix(2, 3, 0, 5, 0, 0), false }; + yield return new object[] { identity1, new Matrix(2, 0, 4, 5, 0, 0), false }; + yield return new object[] { identity1, new Matrix(2, 3, 4, 5, 0, 0), false }; + + // Identity + complex. + yield return new object[] { identity1, new Matrix(2, 3, 4, 5, 6, 7), false }; + yield return new object[] { identity1, new Matrix(0, 0, 0, 0, 0, 0), false }; + } + + // Scaled + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), identity, false }; + } + + // Scaled + scaled. + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 5, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 5, 0, 0), true }; + + // Scaled + skewed. + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 4, 1, 0, 0), false }; + + // Scaled + translated. + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 1, 6, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 1, 0, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 1, 6, 7), false }; + + // Scaled + translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 5, 0, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 5, 6, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 5, 6, 7), false }; + + // Scaled + translated and skewed. + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 4, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 4, 1, 0, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 4, 1, 6, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 4, 1, 6, 7), false }; + + // Scaled + skewed and scaled. + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 3, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 3, 0, 5, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 3, 4, 5, 0, 0), false }; + + // Scaled + complex. + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(0, 0, 0, 0, 0, 0), false }; + + // Skewed + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), identity, false }; + } + + // Skewed + scaled. + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 0, 2, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 5, 0, 0), false }; + + // Skewed + skewed. + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 1, 0, 0), true }; + + // Skewed + translated. + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 0, 1, 6, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 0, 1, 0, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 0, 1, 6, 7), false }; + + // Skewed + translated and scaled. + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 0, 5, 6, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 5, 6, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 5, 0, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 5, 6, 7), false }; + + // Skewed + translated and skewed. + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 4, 1, 6, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 1, 6, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 1, 0, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 1, 6, 7), false }; + + // Skewed + skewed and scaled. + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 3, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 3, 0, 5, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 3, 4, 5, 0, 0), false }; + + // Skewed + complex. + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(0, 0, 0, 0, 0, 0), false }; + + // Translated + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), identity, false }; + } + + // Translated + scaled. + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 2, 0, 0), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 5, 0, 0), false }; + + // Translated + skewed. + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 1, 0, 0), false }; + + // Translated + translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 1, 6, 0), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 1, 0, 7), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 1, 6, 7), true }; + + // Translated + translated and scaled. + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 5, 6, 7), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 5, 6, 0), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 5, 0, 7), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 5, 6, 7), false }; + + // Translated + translated and skewed. + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 4, 1, 6, 7), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 1, 6, 0), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 1, 0, 7), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 1, 6, 7), false }; + + // Translated + skewed and scaled. + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 3, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 3, 0, 5, 0, 0), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 3, 4, 5, 0, 0), false }; + + // Translated + complex. + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(0, 0, 0, 0, 0, 0), false }; + + // Translated and scaled + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), identity, false }; + } + + // Translated and scaled + scaled. + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 2, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 5, 0, 0), false }; + + // Translated and scaled + skewed. + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 4, 1, 0, 0), false }; + + // Translated and scaled + translated. + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 1, 6, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 1, 0, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 1, 6, 7), false }; + + // Translated and scaled + translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 5, 6, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 5, 0, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 5, 6, 7), true }; + + // Translated and scaled + translated and skewed. + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 4, 1, 6, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 4, 1, 6, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 4, 1, 0, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 4, 1, 6, 7), false }; + + // Translated and scaled + skewed and scaled. + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 3, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 3, 0, 5, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 3, 4, 5, 0, 0), false }; + + // Translated and scaled + complex. + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(0, 0, 0, 0, 0, 0), false }; + + // Translated and skewed + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), identity, false }; + } + + // Translated and skewed + scaled. + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 0, 2, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 5, 0, 0), false }; + + // Translated and skewed + skewed. + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 1, 0, 0), false }; + + // Translated and skewed + translated. + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 0, 1, 6, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 0, 1, 0, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 0, 1, 6, 7), false }; + + // Translated and skewed + translated and scaled. + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 0, 5, 6, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 5, 6, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 5, 0, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 5, 6, 7), false }; + + // Translated and skewed + translated and skewed. + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 4, 1, 6, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 1, 6, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 1, 0, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 1, 6, 7), true }; + + // Translated and skewed + skewed and scaled. + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 3, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 3, 0, 5, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 3, 4, 5, 0, 0), false }; + + // Translated and skewed + complex. + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(0, 0, 0, 0, 0, 0), false }; + + // Skewed and scaled + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), identity, false }; + } + + // Skewed and scaled + scaled. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 0, 2, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 5, 0, 0), false }; + + // Skewed and scaled + skewed. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 1, 0, 0), false }; + + // Skewed and scaled + translated. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 0, 1, 6, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 0, 1, 0, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 0, 1, 6, 7), false }; + + // Skewed and scaled + translated and scaled. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 0, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 5, 6, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 5, 0, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 5, 6, 7), false }; + + // Skewed and scaled + translated and skewed. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 4, 1, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 1, 6, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 1, 0, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 1, 6, 7), false }; + + // Skewed and scaled + skewed and scaled. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 3, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 3, 0, 5, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 3, 4, 5, 0, 0), true }; + + // Skewed and scaled + complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(0, 0, 0, 0, 0, 0), false }; + + // Complex + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), identity, false }; + } + + // Complex + scaled. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 0, 2, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 5, 0, 0), false }; + + // Complex + skewed. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 4, 1, 0, 0), false }; + + // Complex + translated. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 0, 1, 6, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 0, 1, 0, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 0, 1, 6, 7), false }; + + // Complex + translated and scaled. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 0, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 5, 6, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 5, 0, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 5, 6, 7), false }; + + // Complex + translated and skewed. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 4, 1, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 4, 1, 6, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 4, 1, 0, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 4, 1, 6, 7), false }; + + // Complex + skewed and scaled. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 0, 5, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 0, 0), false }; + + // Complex + complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), true }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(0, 0, 0, 0, 0, 0), false }; + + // Zero + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), identity, false }; + } + + // Zero + scaled. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 0, 2, 0, 0), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 5, 0, 0), false }; + + // Zero + skewed. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 0, 1, 0, 0), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 4, 1, 0, 0), false }; + + // Zero + translated. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 0, 1, 6, 0), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 0, 1, 0, 7), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 0, 1, 6, 7), false }; + + // Zero + translated and scaled. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 0, 5, 6, 7), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 5, 6, 0), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 5, 0, 7), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 5, 6, 7), false }; + + // Zero + translated and skewed. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 0, 1, 6, 7), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 4, 1, 6, 7), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 4, 1, 6, 0), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 4, 1, 0, 7), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 4, 1, 6, 7), false }; + + // Zero + skewed and scaled. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 3, 4, 1, 0, 0), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 3, 0, 5, 0, 0), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 4, 5, 0, 0), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 3, 4, 5, 0, 0), false }; + + // Zero + complex. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(0, 0, 0, 0, 0, 0), true }; + + // NaN. + yield return new object[] { new Matrix(double.NaN, 3, 4, 5, 6, 7), new Matrix(double.NaN, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(double.NaN, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, double.NaN, 4, 5, 6, 7), new Matrix(2, double.NaN, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, double.NaN, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, double.NaN, 5, 6, 7), new Matrix(2, 3, double.NaN, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, double.NaN, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, double.NaN, 6, 7), new Matrix(2, 3, 4, double.NaN, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, double.NaN, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, double.NaN, 7), new Matrix(2, 3, 4, 5, double.NaN, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, double.NaN, 7), new Matrix(2, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, double.NaN), new Matrix(2, 3, 4, 5, 6, double.NaN), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, double.NaN), new Matrix(2, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), false }; + yield return new object[] { new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), new Matrix(2, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(double.NaN, 3, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, double.NaN, 4, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, double.NaN, 5, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, double.NaN, 6, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, double.NaN, 7), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, double.NaN), false }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), false }; + } + + [Theory] + [MemberData(nameof(EqualityOperator_TestData))] + public void EqualityOperator_Invoke_ReturnsExpected(Matrix matrix1, Matrix matrix2, bool expected) + { + Assert.Equal(expected, matrix1 == matrix2); + Assert.Equal(expected, matrix2 == matrix1); + Assert.Equal(expected, matrix2 == matrix1); + Assert.Equal(!expected, matrix2 != matrix1); + } + + public static IEnumerable GetHashCode_Identity_TestData() + { + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { identity }; + } + } + + [Theory] + [MemberData(nameof(GetHashCode_Identity_TestData))] + public void GetHashCode_InvokeIdentity_ReturnsExpected(Matrix matrix) + { + Assert.Equal(0, matrix.GetHashCode()); + Assert.Equal(matrix.GetHashCode(), matrix.GetHashCode()); + } + + public static IEnumerable GetHashCode_NotIdentity_TestData() + { + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7) }; + } + + [Theory] + [MemberData(nameof(GetHashCode_NotIdentity_TestData))] + public void GetHashCode_InvokeNotIdentity_ReturnsExpected(Matrix matrix) + { + Assert.NotEqual(0, matrix.GetHashCode()); + Assert.Equal(matrix.GetHashCode(), matrix.GetHashCode()); + } + + [Fact] + public void GetHashCode_InvokeZero_ReturnsExpected() + { + var matrix = new Matrix(0, 0, 0, 0, 0, 0); + Assert.Equal(0, matrix.GetHashCode()); + Assert.Equal(matrix.GetHashCode(), matrix.GetHashCode()); + } + + public static IEnumerable Invert_TestData() + { + // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, new Matrix(1, 0, 0, 1, 0, 0), 1 }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), new Matrix(0.5, 0, 0, 0.33333, 0, 0), 0.16667 }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), new Matrix(1.6, -0.8, -1.2, 1.6, 0, 0), 1.6 }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), new Matrix(1, 0, 0, 1, -2, -3), 1 }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), new Matrix(0.5, 0, 0, 0.33333, -0.5, -0.66667), 0.16667 }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), new Matrix(1.6, -0.8, -1.2, 1.6, 0.4, -3.2), 1.6 }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), new Matrix(0.53333, -0.08889, -0.13333, 0.35556, 0, 0), 0.17778 }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(-2.5, 1.5, 2, -1, 1, -2), -0.5 }; + } + + [Theory] + [MemberData(nameof(Invert_TestData))] + public void Invert_Invoke_Success(Matrix matrix, Matrix expected, double expectedDeterminant) + { + matrix.Invert(); + Helpers.AssertEqualRounded(expected, matrix); + Assert.Equal(expected.IsIdentity, matrix.IsIdentity); + Assert.Equal(expected.HasInverse, matrix.HasInverse); + Assert.Equal(expectedDeterminant, matrix.Determinant, precision: 5); + } + + [Fact] + public void Invert_NoInverse_ThrowsInvalidOperationException() + { + var matrix = new Matrix(0, 0, 0, 0, 0, 0); + Assert.Throws(() => matrix.Invert()); + } + + public static IEnumerable Parse_TestData() + { + yield return new object[] { "Identity", Matrix.Identity }; + yield return new object[] { " Identity ", Matrix.Identity }; + yield return new object[] { "0,0,0,0,0,0", new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { "2,3,4,5,6,7", new Matrix(2, 3, 4, 5, 6, 7) }; + yield return new object[] { "2.1,3.2,4.3,5.4,6.5,7.6", new Matrix(2.1, 3.2, 4.3, 5.4, 6.5, 7.6) }; + yield return new object[] { " 2 , 3 , 4, 5 , 6 , 7 ", new Matrix(2, 3, 4, 5, 6, 7) }; + yield return new object[] { "-1,-2,-3,-4,-5,-6", new Matrix(-1, -2, -3, -4, -5, -6) }; + } + + [Theory] + [MemberData(nameof(Parse_TestData))] + public void Parse_Invoke_Success(string source, Matrix expected) + { + Matrix result = Matrix.Parse(source); + Assert.Equal(expected.M11, result.M11, precision: 5); + Assert.Equal(expected.M12, result.M12, precision: 5); + Assert.Equal(expected.M21, result.M21, precision: 5); + Assert.Equal(expected.M22, result.M22, precision: 5); + Assert.Equal(expected.OffsetX, result.OffsetX, precision: 5); + Assert.Equal(expected.OffsetY, result.OffsetY, precision: 5); + } + + [Fact] + public void Parse_NullSource_ThrowsNotSupportedException() + { + Assert.Throws(() => Matrix.Parse(null)); + } + + public static IEnumerable Parse_InvalidSource_TestData() + { + yield return new object?[] { "" }; + yield return new object?[] { " " }; + yield return new object?[] { "2" }; + yield return new object?[] { "2," }; + yield return new object?[] { "2,3" }; + yield return new object?[] { "2,3," }; + yield return new object?[] { "2,3,4" }; + yield return new object?[] { "2,3,4," }; + yield return new object?[] { "2,3,4,5," }; + yield return new object?[] { "2,3,4,5,6" }; + yield return new object?[] { "2,3,4,5,6,7,8" }; + yield return new object?[] { "2,3,4,5,6,7,test" }; + yield return new object?[] { "Empty," }; + yield return new object?[] { "Identity," }; + yield return new object?[] { "Identity,3,4,5,6,7" }; + } + + [Theory] + [MemberData(nameof(Parse_InvalidSource_TestData))] + public void Parse_InvalidSource_ThrowsInvalidOperationException(string source) + { + Assert.Throws(() => Matrix.Parse(source)); + } + + public static IEnumerable Parse_NotDouble_TestData() + { + yield return new object[] { "Empty" }; + yield return new object[] { " Empty " }; + yield return new object[] { "Empty,2,3,4,5,6" }; + yield return new object[] { "test" }; + yield return new object[] { "test,2,3,4,5,6" }; + yield return new object[] { "1,test,3,4,5,6" }; + yield return new object[] { "1,2,test,4,5,6" }; + yield return new object[] { "1,2,3,test,5,6" }; + yield return new object[] { "1,2,3,4,test,6" }; + yield return new object[] { "1,2,3,4,5,test" }; + yield return new object[] { "1;2;3;4;5;6" }; + yield return new object[] { """1"",""2"",""3"",""4"",""5"",""6""" }; + } + + [Theory] + [MemberData(nameof(Parse_NotDouble_TestData))] + public void Parse_NotDouble_ThrowsFormatException(string source) + { + Assert.Throws(() => Matrix.Parse(source)); + } + + public static IEnumerable IdentityMatrices() + { + yield return Matrix.Identity; + yield return new Matrix(); + yield return new Matrix(1, 0, 0, 1, 0, 0); + + var identitySetIdentity = Matrix.Identity; + identitySetIdentity.SetIdentity(); + yield return identitySetIdentity; + + var defaultSetIdentity = new Matrix(); + defaultSetIdentity.SetIdentity(); + yield return defaultSetIdentity; + + var constructedSetIdentity = new Matrix(1, 0, 0, 1, 0, 0); + constructedSetIdentity.SetIdentity(); + yield return constructedSetIdentity; + + var scaleSetIdentity = new Matrix(2, 0, 0, 3, 0, 0); + scaleSetIdentity.SetIdentity(); + yield return scaleSetIdentity; + + var skewSetIdentity = new Matrix(1, 2, 3, 1, 0, 0); + skewSetIdentity.SetIdentity(); + yield return skewSetIdentity; + + var translateSetIdentity = new Matrix(1, 0, 0, 1, 1, 2); + translateSetIdentity.SetIdentity(); + yield return translateSetIdentity; + + var translateScaleSetIdentity = new Matrix(2, 0, 0, 3, 1, 2); + translateScaleSetIdentity.SetIdentity(); + yield return translateScaleSetIdentity; + + var translateSkewSetIdentity = new Matrix(1, 2, 3, 1, 1, 2); + translateSkewSetIdentity.SetIdentity(); + yield return translateSkewSetIdentity; + + var skewScaleSetIdentity = new Matrix(1, 2, 3, 1, 1, 2); + skewScaleSetIdentity.SetIdentity(); + yield return skewScaleSetIdentity; + + var complexSetIdentity = new Matrix(2, 3, 4, 5, 6, 7); + complexSetIdentity.SetIdentity(); + yield return complexSetIdentity; + + var noInverseSetIdentity = new Matrix(0, 0, 0, 0, 0, 0); + noInverseSetIdentity.SetIdentity(); + yield return noInverseSetIdentity; + } + + public static IEnumerable Multiply_TestData() + { + // Identity. + foreach (Matrix identity1 in IdentityMatrices()) + { + // Identity + identity. + foreach (Matrix identity2 in IdentityMatrices()) + { + yield return new object[] { identity1, identity2, identity2 }; + } + + // Identity + scaled. + yield return new object[] { identity1, new Matrix(2, 0, 0, 1, 0, 0), new Matrix(2, 0, 0, 1, 0, 0) }; + yield return new object[] { identity1, new Matrix(1, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 5, 0, 0) }; + yield return new object[] { identity1, new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 5, 0, 0) }; + + // Identity + skewed. + yield return new object[] { identity1, new Matrix(1, 3, 0, 1, 0, 0), new Matrix(1, 3, 0, 1, 0, 0) }; + yield return new object[] { identity1, new Matrix(1, 0, 4, 1, 0, 0), new Matrix(1, 0, 4, 1, 0, 0) }; + yield return new object[] { identity1, new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 1, 0, 0) }; + + // Identity + translated. + yield return new object[] { identity1, new Matrix(1, 0, 0, 1, 6, 0), new Matrix(1, 0, 0, 1, 6, 0) }; + yield return new object[] { identity1, new Matrix(1, 0, 0, 1, 0, 7), new Matrix(1, 0, 0, 1, 0, 7) }; + yield return new object[] { identity1, new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 1, 6, 7) }; + + // Identity + translated and scaled. + yield return new object[] { identity1, new Matrix(2, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 1, 6, 7) }; + yield return new object[] { identity1, new Matrix(1, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 5, 6, 7) }; + yield return new object[] { identity1, new Matrix(2, 0, 0, 5, 6, 0), new Matrix(2, 0, 0, 5, 6, 0) }; + yield return new object[] { identity1, new Matrix(2, 0, 0, 5, 0, 7), new Matrix(2, 0, 0, 5, 0, 7) }; + yield return new object[] { identity1, new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 5, 6, 7) }; + + // Identity + translate and skewed. + yield return new object[] { identity1, new Matrix(1, 3, 0, 1, 6, 7), new Matrix(1, 3, 0, 1, 6, 7) }; + yield return new object[] { identity1, new Matrix(1, 0, 4, 1, 6, 7), new Matrix(1, 0, 4, 1, 6, 7) }; + yield return new object[] { identity1, new Matrix(1, 3, 4, 1, 6, 0), new Matrix(1, 3, 4, 1, 6, 0) }; + yield return new object[] { identity1, new Matrix(1, 3, 4, 1, 0, 7), new Matrix(1, 3, 4, 1, 0, 7) }; + yield return new object[] { identity1, new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 1, 6, 7) }; + + // Identity + skewed and scaled. + yield return new object[] { identity1, new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 3, 4, 5, 0, 0) }; + yield return new object[] { identity1, new Matrix(1, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 5, 0, 0) }; + yield return new object[] { identity1, new Matrix(2, 3, 0, 5, 0, 0), new Matrix(2, 3, 0, 5, 0, 0) }; + yield return new object[] { identity1, new Matrix(2, 0, 4, 5, 0, 0), new Matrix(2, 0, 4, 5, 0, 0) }; + yield return new object[] { identity1, new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 3, 4, 5, 0, 0) }; + + // Identity + complex. + yield return new object[] { identity1, new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7) }; + yield return new object[] { identity1, new Matrix(0, 0, 0, 0, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + } + + + // Scaled + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), identity, new Matrix(2, 0, 0, 5, 0, 0) }; + } + + // Scaled + scaled. + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 1, 0, 0), new Matrix(4, 0, 0, 5, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 25, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 5, 0, 0), new Matrix(4, 0, 0, 25, 0, 0) }; + + // Scaled + skewed. + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 0, 1, 0, 0), new Matrix(2, 6, 0, 5, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 4, 1, 0, 0), new Matrix(2, 0, 20, 5, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 6, 20, 5, 0, 0) }; + + // Scaled + translated. + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 1, 6, 0), new Matrix(2, 0, 0, 5, 6, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 1, 0, 7), new Matrix(2, 0, 0, 5, 0, 7) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 5, 6, 7) }; + + // Scaled + translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 1, 6, 7), new Matrix(4, 0, 0, 5, 6, 7) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 25, 6, 7) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 5, 0, 7), new Matrix(4, 0, 0, 25, 0, 7) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 5, 6, 0), new Matrix(4, 0, 0, 25, 6, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 5, 6, 7), new Matrix(4, 0, 0, 25, 6, 7) }; + + // Scaled + translated and skewed. + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 0, 1, 6, 7), new Matrix(2, 6, 0, 5, 6, 7) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 4, 0, 1, 6, 7), new Matrix(2, 8, 0, 5, 6, 7) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 4, 1, 0, 7), new Matrix(2, 6, 20, 5, 0, 7) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 4, 1, 6, 0), new Matrix(2, 6, 20, 5, 6, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 6, 20, 5, 6, 7) }; + + // Scaled + skewed and scaled. + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 3, 4, 1, 0, 0), new Matrix(4, 6, 20, 5, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(1, 3, 4, 5, 0, 0), new Matrix(2, 6, 20, 25, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 3, 0, 5, 0, 0), new Matrix(4, 6, 0, 25, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 4, 5, 0, 0), new Matrix(4, 0, 20, 25, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 3, 4, 5, 0, 0), new Matrix(4, 6, 20, 25, 0, 0) }; + + // Scaled + complex. + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 3, 4, 5, 6, 7), new Matrix(4, 6, 20, 25, 6, 7) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 0, 0), new Matrix(0, 0, 0, 0, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + + // Skewed + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), identity, new Matrix(1, 3, 4, 1, 0, 0) }; + } + + // Skewed + scaled. + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 1, 0, 0), new Matrix(2, 3, 8, 1, 0, 0) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 0, 5, 0, 0), new Matrix(1, 15, 4, 5, 0, 0) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 15, 8, 5, 0, 0) }; + + // Skewed + skewed. + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 0, 1, 0, 0), new Matrix(1, 6, 4, 13, 0, 0) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 4, 1, 0, 0), new Matrix(13, 3, 8, 1, 0, 0) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 1, 0, 0), new Matrix(13, 6, 8, 13, 0, 0) }; + + // Skewed + translated. + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 0, 1, 6, 0), new Matrix(1, 3, 4, 1, 6, 0) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 0, 1, 0, 7), new Matrix(1, 3, 4, 1, 0, 7) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 1, 6, 7) }; + + // Skewed + translated and scaled. + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 1, 6, 7), new Matrix(2, 3, 8, 1, 6, 7) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 0, 5, 6, 7), new Matrix(1, 15, 4, 5, 6, 7) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 5, 6, 0), new Matrix(2, 15, 8, 5, 6, 0) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 5, 0, 7), new Matrix(2, 15, 8, 5, 0, 7) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 15, 8, 5, 6, 7) }; + + // Skewed + translated and skewed. + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 0, 1, 6, 7), new Matrix(1, 6, 4, 13, 6, 7) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 0, 4, 1, 6, 7), new Matrix(13, 3, 8, 1, 6, 7) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 1, 6, 0), new Matrix(13, 6, 8, 13, 6, 0) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 1, 0, 7), new Matrix(13, 6, 8, 13, 0, 7) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 1, 6, 7), new Matrix(13, 6, 8, 13, 6, 7) }; + + // Skewed + skewed and scaled. + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 3, 4, 1, 0, 0), new Matrix(14, 6, 12, 13, 0, 0) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 5, 0, 0), new Matrix(13, 18, 8, 17, 0, 0) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 3, 0, 5, 0, 0), new Matrix(2, 18, 8, 17, 0, 0) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 0, 4, 5, 0, 0), new Matrix(14, 15, 12, 5, 0, 0) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 3, 4, 5, 0, 0), new Matrix(14, 18, 12, 17, 0, 0) }; + + // Skewed + complex. + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 3, 4, 5, 6, 7), new Matrix(14, 18, 12, 17, 6, 7) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 0, 0), new Matrix(0, 0, 0, 0, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + + // Translated + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), identity, new Matrix(1, 0, 0, 1, 6, 7) }; + } + + // Translated + scaled. + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 1, 0, 0), new Matrix(2, 0, 0, 1, 12, 7) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 5, 0, 0), new Matrix(1, 0, 0, 5, 6, 35) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 5, 12, 35) }; + + // Translated + skewed. + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 0, 1, 0, 0), new Matrix(1, 3, 0, 1, 6, 25) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 4, 1, 0, 0), new Matrix(1, 0, 4, 1, 34, 7) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 1, 0, 0), new Matrix(1, 3, 4, 1, 34, 25) }; + + // Translated + translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 1, 6, 0), new Matrix(1, 0, 0, 1, 12, 7) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 1, 0, 7), new Matrix(1, 0, 0, 1, 6, 14) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 1, 12, 14) }; + + // Translated + translated and scaled. + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 1, 18, 14) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 5, 12, 42) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 5, 6, 0), new Matrix(2, 0, 0, 5, 18, 35) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 5, 0, 7), new Matrix(2, 0, 0, 5, 12, 42) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 5, 18, 42) }; + + // Translated + translated and skewed. + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 0, 1, 6, 7), new Matrix(1, 3, 0, 1, 12, 32) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 0, 4, 1, 6, 7), new Matrix(1, 0, 4, 1, 40, 14) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 1, 6, 0), new Matrix(1, 3, 4, 1, 40, 25) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 1, 0, 7), new Matrix(1, 3, 4, 1, 34, 32) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 1, 40, 32) }; + + // Translated + skewed and scaled. + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 3, 4, 1, 0, 0), new Matrix(2, 3, 4, 1, 40, 25) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 5, 34, 53) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 3, 0, 5, 0, 0), new Matrix(2, 3, 0, 5, 12, 53) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 4, 5, 0, 0), new Matrix(2, 0, 4, 5, 40, 35) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 3, 4, 5, 40, 53) }; + + // Translated + complex. + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 46, 60) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 6, 7), new Matrix(0, 0, 0, 0, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + + // Translated and scaled + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), identity, new Matrix(2, 0, 0, 5, 6, 7) }; + } + + // Translated and scaled + scaled. + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 1, 0, 0), new Matrix(4, 0, 0, 5, 12, 7) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 5, 0, 0), new Matrix(2, 0, 0, 25, 6, 35) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 5, 0, 0), new Matrix(4, 0, 0, 25, 12, 35) }; + + // Translated and scaled + skewed. + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 0, 1, 0, 0), new Matrix(2, 6, 0, 5, 6, 25) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 4, 1, 0, 0), new Matrix(2, 0, 20, 5, 34, 7) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 4, 1, 0, 0), new Matrix(2, 6, 20, 5, 34, 25) }; + + // Translated and scaled + translated. + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 1, 6, 0), new Matrix(2, 0, 0, 5, 12, 7) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 1, 0, 7), new Matrix(2, 0, 0, 5, 6, 14) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 0, 0, 5, 12, 14) }; + + // Translated and scaled + translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 1, 6, 7), new Matrix(4, 0, 0, 5, 18, 14) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 25, 12, 42) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 5, 6, 0), new Matrix(4, 0, 0, 25, 18, 35) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 5, 0, 7), new Matrix(4, 0, 0, 25, 12, 42) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 0, 5, 6, 7), new Matrix(4, 0, 0, 25, 18, 42) }; + + // Translated and scaled + translated and skewed. + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 0, 1, 6, 7), new Matrix(2, 6, 0, 5, 12, 32) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 0, 4, 1, 6, 7), new Matrix(2, 0, 20, 5, 40, 14) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 4, 1, 6, 0), new Matrix(2, 6, 20, 5, 40, 25) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 4, 1, 0, 7), new Matrix(2, 6, 20, 5, 34, 32) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 6, 20, 5, 40, 32) }; + + // Translated and scaled + skewed and scaled. + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 3, 4, 1, 0, 0), new Matrix(4, 6, 20, 5, 40, 25) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(1, 3, 4, 5, 0, 0), new Matrix(2, 6, 20, 25, 34, 53) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 3, 0, 5, 0, 0), new Matrix(4, 6, 0, 25, 12, 53) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 0, 4, 5, 0, 0), new Matrix(4, 0, 20, 25, 40, 35) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 3, 4, 5, 0, 0), new Matrix(4, 6, 20, 25, 40, 53) }; + + // Translated and scaled + complex. + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), new Matrix(4, 6, 20, 25, 46, 60) }; + yield return new object[] { new Matrix(2, 0, 0, 5, 6, 7), new Matrix(0, 0, 0, 0, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + + // Translated and skewed + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), identity, new Matrix(1, 3, 4, 1, 6, 7) }; + } + + // Translated and skewed + scaled. + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 1, 0, 0), new Matrix(2, 3, 8, 1, 12, 7) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 0, 5, 0, 0), new Matrix(1, 15, 4, 5, 6, 35) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 5, 0, 0), new Matrix(2, 15, 8, 5, 12, 35) }; + + // Translated and skewed + skewed. + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 0, 1, 0, 0), new Matrix(1, 6, 4, 13, 6, 25) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 4, 1, 0, 0), new Matrix(13, 3, 8, 1, 34, 7) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 1, 0, 0), new Matrix(13, 6, 8, 13, 34, 25) }; + + // Translated and skewed + translated. + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 0, 1, 6, 0), new Matrix(1, 3, 4, 1, 12, 7) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 0, 1, 0, 7), new Matrix(1, 3, 4, 1, 6, 14) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 0, 1, 6, 7), new Matrix(1, 3, 4, 1, 12, 14) }; + + // Translated and skewed + translated and scaled. + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 1, 6, 7), new Matrix(2, 3, 8, 1, 18, 14) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 0, 5, 6, 7), new Matrix(1, 15, 4, 5, 12, 42) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 5, 6, 0), new Matrix(2, 15, 8, 5, 18, 35) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 5, 0, 7), new Matrix(2, 15, 8, 5, 12, 42) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 0, 5, 6, 7), new Matrix(2, 15, 8, 5, 18, 42) }; + + // Translated and skewed + translated and skewed. + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 0, 1, 6, 7), new Matrix(1, 6, 4, 13, 12, 32) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 0, 4, 1, 6, 7), new Matrix(13, 3, 8, 1, 40, 14) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 1, 6, 0), new Matrix(13, 6, 8, 13, 40, 25) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 1, 0, 7), new Matrix(13, 6, 8, 13, 34, 32) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 1, 6, 7), new Matrix(13, 6, 8, 13, 40, 32) }; + + // Translated and skewed + skewed and scaled. + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 3, 4, 1, 0, 0), new Matrix(14, 6, 12, 13, 40, 25) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(1, 3, 4, 5, 0, 0), new Matrix(13, 18, 8, 17, 34, 53) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 3, 0, 5, 0, 0), new Matrix(2, 18, 8, 17, 12, 53) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 0, 4, 5, 0, 0), new Matrix(14, 15, 12, 5, 40, 35) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 3, 4, 5, 0, 0), new Matrix(14, 18, 12, 17, 40, 53) }; + + // Translated and skewed + complex. + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), new Matrix(14, 18, 12, 17, 46, 60) }; + yield return new object[] { new Matrix(1, 3, 4, 1, 6, 7), new Matrix(0, 0, 0, 0, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + + // Skewed and scaled + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), identity, new Matrix(2, 3, 4, 5, 0, 0) }; + } + + // Skewed and scaled + scaled. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 1, 0, 0), new Matrix(4, 3, 8, 5, 0, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 0, 5, 0, 0), new Matrix(2, 15, 4, 25, 0, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 5, 0, 0), new Matrix(4, 15, 8, 25, 0, 0) }; + + // Skewed and scaled + skewed. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 0, 1, 0, 0), new Matrix(2, 9, 4, 17, 0, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 4, 1, 0, 0), new Matrix(14, 3, 24, 5, 0, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 1, 0, 0), new Matrix(14, 9, 24, 17, 0, 0) }; + + // Skewed and scaled + translated. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 0, 1, 6, 0), new Matrix(2, 3, 4, 5, 6, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 0, 1, 0, 7), new Matrix(2, 3, 4, 5, 0, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 3, 4, 5, 6, 7) }; + + // Skewed and scaled + translated and scaled. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 1, 6, 7), new Matrix(4, 3, 8, 5, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 0, 5, 6, 7), new Matrix(2, 15, 4, 25, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 5, 6, 0), new Matrix(4, 15, 8, 25, 6, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 5, 0, 7), new Matrix(4, 15, 8, 25, 0, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 0, 5, 6, 7), new Matrix(4, 15, 8, 25, 6, 7) }; + + // Skewed and scaled + translated and skewed. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 0, 1, 6, 7), new Matrix(2, 9, 4, 17, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 0, 4, 1, 6, 7), new Matrix(14, 3, 24, 5, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 1, 6, 0), new Matrix(14, 9, 24, 17, 6, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 1, 0, 7), new Matrix(14, 9, 24, 17, 0, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 1, 6, 7), new Matrix(14, 9, 24, 17, 6, 7) }; + + // Skewed and scaled + skewed and scaled. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 3, 4, 1, 0, 0), new Matrix(16, 9, 28, 17, 0, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(1, 3, 4, 5, 0, 0), new Matrix(14, 21, 24, 37, 0, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 3, 0, 5, 0, 0), new Matrix(4, 21, 8, 37, 0, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 0, 4, 5, 0, 0), new Matrix(16, 15, 28, 25, 0, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 3, 4, 5, 0, 0), new Matrix(16, 21, 28, 37, 0, 0) }; + + // Skewed and scaled + complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(2, 3, 4, 5, 6, 7), new Matrix(16, 21, 28, 37, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Matrix(0, 0, 0, 0, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + + // Complex + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), identity, new Matrix(2, 3, 4, 5, 6, 7) }; + } + + // Complex + scaled. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 1, 0, 0), new Matrix(4, 3, 8, 5, 12, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 0, 5, 0, 0), new Matrix(2, 15, 4, 25, 6, 35) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 5, 0, 0), new Matrix(4, 15, 8, 25, 12, 35) }; + + // Complex + skewed. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 0, 1, 0, 0), new Matrix(2, 9, 4, 17, 6, 25) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 4, 1, 0, 0), new Matrix(14, 3, 24, 5, 34, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 4, 1, 0, 0), new Matrix(14, 9, 24, 17, 34, 25) }; + + // Complex + translated. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 0, 1, 6, 0), new Matrix(2, 3, 4, 5, 12, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 0, 1, 0, 7), new Matrix(2, 3, 4, 5, 6, 14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 0, 1, 6, 7), new Matrix(2, 3, 4, 5, 12, 14) }; + + // Complex + translated and scaled. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 1, 6, 7), new Matrix(4, 3, 8, 5, 18, 14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 0, 5, 6, 7), new Matrix(2, 15, 4, 25, 12, 42) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 5, 6, 0), new Matrix(4, 15, 8, 25, 18, 35) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 5, 0, 7), new Matrix(4, 15, 8, 25, 12, 42) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 0, 5, 6, 7), new Matrix(4, 15, 8, 25, 18, 42) }; + + // Complex + translated and skewed. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 0, 1, 6, 7), new Matrix(2, 9, 4, 17, 12, 32) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 0, 4, 1, 6, 7), new Matrix(14, 3, 24, 5, 40, 14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 4, 1, 6, 0), new Matrix(14, 9, 24, 17, 40, 25) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 4, 1, 0, 7), new Matrix(14, 9, 24, 17, 34, 32) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 4, 1, 6, 7), new Matrix(14, 9, 24, 17, 40, 32) }; + + // Complex + skewed and scaled. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 1, 0, 0), new Matrix(16, 9, 28, 17, 40, 25) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(1, 3, 4, 5, 0, 0), new Matrix(14, 21, 24, 37, 34, 53) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 0, 5, 0, 0), new Matrix(4, 21, 8, 37, 12, 53) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 0, 4, 5, 0, 0), new Matrix(16, 15, 28, 25, 40, 35) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 0, 0), new Matrix(16, 21, 28, 37, 40, 53) }; + + // Complex + complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), new Matrix(16, 21, 28, 37, 46, 60) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(0, 0, 0, 0, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + + // Zero + identity. + foreach (Matrix identity in IdentityMatrices()) + { + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), identity, new Matrix(0, 0, 0, 0, 0, 0) }; + } + + // Zero + scaled. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 1, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 0, 5, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 5, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + + // Zero + skewed. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 0, 1, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 4, 1, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 4, 1, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + + // Zero + translated. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 0, 1, 6, 0), new Matrix(0, 0, 0, 0, 6, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 0, 1, 0, 7), new Matrix(0, 0, 0, 0, 0, 7) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 0, 1, 6, 7), new Matrix(0, 0, 0, 0, 6, 7) }; + + // Zero + translated and scaled. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 1, 6, 7), new Matrix(0, 0, 0, 0, 6, 7) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 0, 5, 6, 7), new Matrix(0, 0, 0, 0, 6, 7) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 5, 6, 0), new Matrix(0, 0, 0, 0, 6, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 5, 0, 7), new Matrix(0, 0, 0, 0, 0, 7) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 0, 5, 6, 7), new Matrix(0, 0, 0, 0, 6, 7) }; + + // Zero + translated and skewed. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 0, 1, 6, 7), new Matrix(0, 0, 0, 0, 6, 7) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 0, 4, 1, 6, 7), new Matrix(0, 0, 0, 0, 6, 7) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 4, 1, 6, 0), new Matrix(0, 0, 0, 0, 6, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 4, 1, 0, 7), new Matrix(0, 0, 0, 0, 0, 7) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 4, 1, 6, 7), new Matrix(0, 0, 0, 0, 6, 7) }; + + // Zero + skewed and scaled. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 3, 4, 1, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(1, 3, 4, 5, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 3, 0, 5, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 0, 4, 5, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 3, 4, 5, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + + // Zero + complex. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(2, 3, 4, 5, 6, 7), new Matrix(0, 0, 0, 0, 6, 7) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Matrix(0, 0, 0, 0, 0, 0), new Matrix(0, 0, 0, 0, 0, 0) }; + + // NaN. + yield return new object[] { new Matrix(double.NaN, 3, 4, 5, 6, 7), new Matrix(double.NaN, 3, 4, 5, 6, 7), new Matrix(double.NaN, double.NaN, double.NaN, 37, double.NaN, 60) }; + yield return new object[] { new Matrix(double.NaN, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), new Matrix(double.NaN, double.NaN, 28, 37, 46, 60) }; + yield return new object[] { new Matrix(2, double.NaN, 4, 5, 6, 7), new Matrix(2, double.NaN, 4, 5, 6, 7), new Matrix(double.NaN, double.NaN, 28, double.NaN, 46, double.NaN) }; + yield return new object[] { new Matrix(2, double.NaN, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), new Matrix(double.NaN, double.NaN, 28, 37, 46, 60) }; + yield return new object[] { new Matrix(2, 3, double.NaN, 5, 6, 7), new Matrix(2, 3, double.NaN, 5, 6, 7), new Matrix(double.NaN, 21, double.NaN, double.NaN, double.NaN, 60) }; + yield return new object[] { new Matrix(2, 3, double.NaN, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), new Matrix(16, 21, double.NaN, double.NaN, 46, 60) }; + yield return new object[] { new Matrix(2, 3, 4, double.NaN, 6, 7), new Matrix(2, 3, 4, double.NaN, 6, 7), new Matrix(16, double.NaN, double.NaN, double.NaN, 46, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, double.NaN, 6, 7), new Matrix(2, 3, 4, 5, 6, 7), new Matrix(16, 21, double.NaN, double.NaN, 46, 60) }; + yield return new object[] { new Matrix(2, 3, 4, 5, double.NaN, 7), new Matrix(2, 3, 4, 5, double.NaN, 7), new Matrix(16, 21, 28, 37, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, double.NaN, 7), new Matrix(2, 3, 4, 5, 6, 7), new Matrix(16, 21, 28, 37, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, double.NaN), new Matrix(2, 3, 4, 5, 6, double.NaN), new Matrix(16, 21, 28, 37, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, double.NaN), new Matrix(2, 3, 4, 5, 6, 7), new Matrix(16, 21, 28, 37, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), new Matrix(2, 3, 4, 5, 6, 7), new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(double.NaN, 3, 4, 5, 6, 7), new Matrix(double.NaN, 21, double.NaN, 37, double.NaN, 60) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, double.NaN, 4, 5, 6, 7), new Matrix(16, double.NaN, 28, double.NaN, 46, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, double.NaN, 5, 6, 7), new Matrix(double.NaN, 21, double.NaN, 37, double.NaN, 60) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, double.NaN, 6, 7), new Matrix(16, double.NaN, 28, double.NaN, 46, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, double.NaN, 7), new Matrix(16, 21, 28, 37, double.NaN, 60) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(2, 3, 4, 5, 6, double.NaN), new Matrix(16, 21, 28, 37, 46, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + } + + [Theory] + [MemberData(nameof(Multiply_TestData))] + public void Multiply_Invoke_ReturnsExpected(Matrix matrix1, Matrix matrix2, Matrix expected) + { + Matrix copy1 = matrix1; + Matrix copy2 = matrix2; + + Matrix result = Matrix.Multiply(matrix1, matrix2); + Assert.Equal(expected, result); + Assert.Equal(copy1, matrix1); + Assert.Equal(copy2, matrix2); + } + + [Theory] + [MemberData(nameof(Multiply_TestData))] + public void OperatorMultiply_Invoke_ReturnsExpected(Matrix matrix1, Matrix matrix2, Matrix expected) + { + Matrix copy1 = matrix1; + Matrix copy2 = matrix2; + + Matrix result = matrix1 * matrix2; + Assert.Equal(expected, result); + Assert.Equal(copy1, matrix1); + Assert.Equal(copy2, matrix2); + } + + [Theory] + [MemberData(nameof(Multiply_TestData))] + public void Prepend_Invoke_ReturnsExpected(Matrix matrix1, Matrix matrix2, Matrix expected) + { + Matrix copy1 = matrix1; + Matrix copy2 = matrix2; + + matrix2.Prepend(matrix1); + Assert.Equal(expected, matrix2); + Assert.Equal(copy1, matrix1); + } + + public static IEnumerable Rotate_TestData() + { + // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, -360, new Matrix(1, -0, 0, 1, 0, 0), 1 }; + yield return new object[] { matrix, -270, new Matrix(-0, 1, -1, -0, 0, 0), 1 }; + yield return new object[] { matrix, -180, new Matrix(-1, -0, 0, -1, 0, 0), 1 }; + yield return new object[] { matrix, -90, new Matrix(0, -1, 1, 0, 0, 0), 1 }; + yield return new object[] { matrix, -45, new Matrix(0.70711, -0.70711, 0.70711, 0.70711, 0, 0), 1 }; + yield return new object[] { matrix, 45, new Matrix(0.70711, 0.70711, -0.70711, 0.70711, 0, 0), 1 }; + yield return new object[] { matrix, 0, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, 90, new Matrix(0, 1, -1, 0, 0, 0), 1 }; + yield return new object[] { matrix, 30, new Matrix(0.86603, 0.5, -0.5, 0.86603, 0, 0), 1 }; + yield return new object[] { matrix, 180, new Matrix(-1, 0, -0, -1, 0, 0), 1 }; + yield return new object[] { matrix, 270, new Matrix(-0, -1, 1, -0, 0, 0), 1 }; + yield return new object[] { matrix, 360, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, 720, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -360, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -270, new Matrix(-0, 2, -3, -0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -180, new Matrix(-2, -0, 0, -3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -90, new Matrix(0, -2, 3, 0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -45, new Matrix(1.41421, -1.41421, 2.12132, 2.12132, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 45, new Matrix(1.41421, 1.41421, -2.12132, 2.12132, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 90, new Matrix(0, 2, -3, 0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 30, new Matrix(1.73205, 1, -1.5, 2.59808, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 180, new Matrix(-2, 0, -0, -3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 270, new Matrix(-0, -2, 3, -0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 360, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 720, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -360, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -270, new Matrix(-0.5, 1, -1, 0.75, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -180, new Matrix(-1, -0.5, -0.75, -1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -90, new Matrix(0.5, -1, 1, -0.75, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -45, new Matrix(1.06066, -0.35355, 1.23744, 0.17678, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 45, new Matrix(0.35355, 1.06066, -0.17678, 1.23744, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 90, new Matrix(-0.5, 1, -1, 0.75, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 30, new Matrix(0.61603, 0.93301, 0.14951, 1.24103, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 180, new Matrix(-1, -0.5, -0.75, -1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 270, new Matrix(0.5, -1, 1, -0.75, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 360, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 720, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -360, new Matrix(1, -0, 0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -270, new Matrix(-0, 1, -1, -0, -3, 2), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -180, new Matrix(-1, -0, 0, -1, -2, -3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -90, new Matrix(0, -1, 1, 0, 3, -2), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -45, new Matrix(0.70711, -0.70711, 0.70711, 0.70711, 3.53553, 0.70711), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 45, new Matrix(0.70711, 0.70711, -0.70711, 0.70711, -0.70711, 3.53553), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 90, new Matrix(0, 1, -1, 0, -3, 2), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 30, new Matrix(0.86603, 0.5, -0.5, 0.86603, 0.23205, 3.59808), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 180, new Matrix(-1, 0, -0, -1, -2, -3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 270, new Matrix(-0, -1, 1, -0, 3, -2), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 360, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 720, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -360, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -270, new Matrix(-0, 2, -3, -0, -2, 1), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -180, new Matrix(-2, -0, 0, -3, -1, -2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -90, new Matrix(0, -2, 3, 0, 2, -1), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -45, new Matrix(1.41421, -1.41421, 2.12132, 2.12132, 2.12132, 0.70711), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 45, new Matrix(1.41421, 1.41421, -2.12132, 2.12132, -0.70711, 2.12132), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 90, new Matrix(0, 2, -3, 0, -2, 1), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 30, new Matrix(1.73205, 1, -1.5, 2.59808, -0.13397, 2.23205), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 180, new Matrix(-2, 0, -0, -3, -1, -2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 270, new Matrix(-0, -2, 3, -0, 2, -1), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 360, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 720, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -360, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -270, new Matrix(-0.5, 1, -1, 0.75, -3, 2), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -180, new Matrix(-1, -0.5, -0.75, -1, -2, -3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -90, new Matrix(0.5, -1, 1, -0.75, 3, -2), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -45, new Matrix(1.06066, -0.35355, 1.23744, 0.17678, 3.53553, 0.70711), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 45, new Matrix(0.35355, 1.06066, -0.17678, 1.23744, -0.70711, 3.53553), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 90, new Matrix(-0.5, 1, -1, 0.75, -3, 2), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 30, new Matrix(0.61603, 0.93301, 0.14951, 1.24103, 0.23205, 3.59808), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 180, new Matrix(-1, -0.5, -0.75, -1, -2, -3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 270, new Matrix(0.5, -1, 1, -0.75, 3, -2), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 360, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 720, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -360, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -270, new Matrix(-0.5, 2, -3, 0.75, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -180, new Matrix(-2, -0.5, -0.75, -3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -90, new Matrix(0.5, -2, 3, -0.75, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -45, new Matrix(1.76777, -1.06066, 2.65165, 1.59099, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 45, new Matrix(1.06066, 1.76777, -1.59099, 2.65165, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 90, new Matrix(-0.5, 2, -3, 0.75, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 30, new Matrix(1.48205, 1.43301, -0.85048, 2.97308, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 180, new Matrix(-2, -0.5, -0.75, -3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 270, new Matrix(0.5, -2, 3, -0.75, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 360, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 720, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -360, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -270, new Matrix(-3, 2, -5, 4, -7, 6), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -180, new Matrix(-2, -3, -4, -5, -6, -7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -90, new Matrix(3, -2, 5, -4, 7, -6), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -45, new Matrix(3.53553, 0.70711, 6.36396, 0.70711, 9.19239, 0.70711), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 45, new Matrix(-0.70711, 3.53553, -0.70711, 6.36396, -0.70711, 9.19239), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 90, new Matrix(-3, 2, -5, 4, -7, 6), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 30, new Matrix(0.23205, 3.59808, 0.9641, 6.33013, 1.69615, 9.06218), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 180, new Matrix(-2, -3, -4, -5, -6, -7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 270, new Matrix(3, -2, 5, -4, 7, -6), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 360, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 720, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + + // No inverse. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -360, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -270, new Matrix(-0, 0, -0, 0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -180, new Matrix(0, -0, 0, -0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -90, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -45, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 45, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 90, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 30, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 180, new Matrix(-0, 0, -0, 0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 270, new Matrix(0, -0, 0, -0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 360, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 720, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + } + + [Theory] + [MemberData(nameof(Rotate_TestData))] + + public void Rotate_Invoke_Success(Matrix matrix, double angle, Matrix expected, double expectedDeterminant) + { + matrix.Rotate(angle); + Helpers.AssertEqualRounded(expected, matrix); + Assert.Equal(expected.IsIdentity, matrix.IsIdentity); + Assert.Equal(expected.HasInverse, matrix.HasInverse); + Assert.Equal(expectedDeterminant, matrix.Determinant, precision: 5); + } + + public static IEnumerable RotatePrepend_TestData() + { + // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, -360, new Matrix(1, -0, 0, 1, 0, 0), 1 }; + yield return new object[] { matrix, -270, new Matrix(-0, 1, -1, -0, 0, 0), 1 }; + yield return new object[] { matrix, -180, new Matrix(-1, -0, 0, -1, 0, 0), 1 }; + yield return new object[] { matrix, -90, new Matrix(0, -1, 1, 0, 0, 0), 1 }; + yield return new object[] { matrix, -45, new Matrix(0.70711, -0.70711, 0.70711, 0.70711, 0, 0), 1 }; + yield return new object[] { matrix, 45, new Matrix(0.70711, 0.70711, -0.70711, 0.70711, 0, 0), 1 }; + yield return new object[] { matrix, 0, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, 90, new Matrix(0, 1, -1, 0, 0, 0), 1 }; + yield return new object[] { matrix, 30, new Matrix(0.86603, 0.5, -0.5, 0.86603, 0, 0), 1 }; + yield return new object[] { matrix, 180, new Matrix(-1, 0, -0, -1, 0, 0), 1 }; + yield return new object[] { matrix, 270, new Matrix(-0, -1, 1, -0, 0, 0), 1 }; + yield return new object[] { matrix, 360, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, 720, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -360, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -270, new Matrix(-0, 3, -2, -0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -180, new Matrix(-2, -0, 0, -3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -90, new Matrix(0, -3, 2, 0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -45, new Matrix(1.41421, -2.12132, 1.41421, 2.12132, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 45, new Matrix(1.41421, 2.12132, -1.41421, 2.12132, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 90, new Matrix(0, 3, -2, 0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 30, new Matrix(1.73205, 1.5, -1, 2.59808, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 180, new Matrix(-2, 0, -0, -3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 270, new Matrix(-0, -3, 2, -0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 360, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 720, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -360, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -270, new Matrix(0.75, 1, -1, -0.5, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -180, new Matrix(-1, -0.5, -0.75, -1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -90, new Matrix(-0.75, -1, 1, 0.5, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -45, new Matrix(0.17678, -0.35355, 1.23744, 1.06066, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 45, new Matrix(1.23744, 1.06066, -0.17678, 0.35355, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 90, new Matrix(0.75, 1, -1, -0.5, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 30, new Matrix(1.24103, 0.93301, 0.14951, 0.61603, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 180, new Matrix(-1, -0.5, -0.75, -1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 270, new Matrix(-0.75, -1, 1, 0.5, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 360, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 720, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -360, new Matrix(1, -0, 0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -270, new Matrix(-0, 1, -1, -0, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -180, new Matrix(-1, -0, 0, -1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -90, new Matrix(0, -1, 1, 0, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -45, new Matrix(0.70711, -0.70711, 0.70711, 0.70711, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 45, new Matrix(0.70711, 0.70711, -0.70711, 0.70711, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 90, new Matrix(0, 1, -1, 0, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 30, new Matrix(0.86603, 0.5, -0.5, 0.86603, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 180, new Matrix(-1, 0, -0, -1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 270, new Matrix(-0, -1, 1, -0, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 360, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 720, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -360, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -270, new Matrix(-0, 3, -2, -0, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -180, new Matrix(-2, -0, 0, -3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -90, new Matrix(0, -3, 2, 0, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -45, new Matrix(1.41421, -2.12132, 1.41421, 2.12132, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 45, new Matrix(1.41421, 2.12132, -1.41421, 2.12132, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 90, new Matrix(0, 3, -2, 0, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 30, new Matrix(1.73205, 1.5, -1, 2.59808, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 180, new Matrix(-2, 0, -0, -3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 270, new Matrix(-0, -3, 2, -0, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 360, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 720, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -360, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -270, new Matrix(0.75, 1, -1, -0.5, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -180, new Matrix(-1, -0.5, -0.75, -1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -90, new Matrix(-0.75, -1, 1, 0.5, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -45, new Matrix(0.17678, -0.35355, 1.23744, 1.06066, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 45, new Matrix(1.23744, 1.06066, -0.17678, 0.35355, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 90, new Matrix(0.75, 1, -1, -0.5, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 30, new Matrix(1.24103, 0.93301, 0.14951, 0.61603, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 180, new Matrix(-1, -0.5, -0.75, -1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 270, new Matrix(-0.75, -1, 1, 0.5, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 360, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 720, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -360, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -270, new Matrix(0.75, 3, -2, -0.5, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -180, new Matrix(-2, -0.5, -0.75, -3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -90, new Matrix(-0.75, -3, 2, 0.5, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -45, new Matrix(0.88388, -1.76777, 1.94454, 2.47487, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 45, new Matrix(1.94454, 2.47487, -0.88388, 1.76777, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 90, new Matrix(0.75, 3, -2, -0.5, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 30, new Matrix(2.10705, 1.93301, -0.35048, 2.34808, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 180, new Matrix(-2, -0.5, -0.75, -3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 270, new Matrix(-0.75, -3, 2, 0.5, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 360, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 720, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -360, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -270, new Matrix(4, 5, -2, -3, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -180, new Matrix(-2, -3, -4, -5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -90, new Matrix(-4, -5, 2, 3, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -45, new Matrix(-1.41421, -1.41421, 4.24264, 5.65685, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 45, new Matrix(4.24264, 5.65685, 1.41421, 1.41421, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 90, new Matrix(4, 5, -2, -3, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 30, new Matrix(3.73205, 5.09808, 2.4641, 2.83013, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 180, new Matrix(-2, -3, -4, -5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 270, new Matrix(-4, -5, 2, 3, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 360, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 720, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + + // No inverse. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -360, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -270, new Matrix(0, 0, -0, -0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -180, new Matrix(-0, -0, 0, 0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -90, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -45, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 45, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 90, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 30, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 180, new Matrix(0, 0, -0, -0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 270, new Matrix(-0, -0, 0, 0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 360, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 720, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + } + + [Theory] + [MemberData(nameof(RotatePrepend_TestData))] + public void RotatePrepend_Invoke_Success(Matrix matrix, double angle, Matrix expected, double expectedDeterminant) + { + matrix.RotatePrepend(angle); + Helpers.AssertEqualRounded(expected, matrix); + Assert.Equal(expected.IsIdentity, matrix.IsIdentity); + Assert.Equal(expected.HasInverse, matrix.HasInverse); + Assert.Equal(expectedDeterminant, matrix.Determinant, precision: 5); + } + + public static IEnumerable RotateAt_TestData() + { + // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, -360, 0, 0, new Matrix(1, -0, 0, 1, 0, 0), 1 }; + yield return new object[] { matrix, -270, 0, 0, new Matrix(-0, 1, -1, -0, 0, 0), 1 }; + yield return new object[] { matrix, -180, 0, 0, new Matrix(-1, -0, 0, -1, 0, 0), 1 }; + yield return new object[] { matrix, -90, 0, 0, new Matrix(0, -1, 1, 0, 0, 0), 1 }; + yield return new object[] { matrix, -45, 0, 0, new Matrix(0.70711, -0.70711, 0.70711, 0.70711, 0, 0), 1 }; + yield return new object[] { matrix, 45, 0, 0, new Matrix(0.70711, 0.70711, -0.70711, 0.70711, 0, 0), 1 }; + yield return new object[] { matrix, 0, 0, 0, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, 90, 0, 0, new Matrix(0, 1, -1, 0, 0, 0), 1 }; + yield return new object[] { matrix, 30, 0, 0, new Matrix(0.86603, 0.5, -0.5, 0.86603, 0, 0), 1 }; + yield return new object[] { matrix, 180, 0, 0, new Matrix(-1, 0, -0, -1, 0, 0), 1 }; + yield return new object[] { matrix, 270, 0, 0, new Matrix(-0, -1, 1, -0, 0, 0), 1 }; + yield return new object[] { matrix, 360, 0, 0, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, 720, 0, 0, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { matrix, -360, 1, 2, new Matrix(1, -0, 0, 1, 0, 0), 1 }; + yield return new object[] { matrix, -270, 1, 2, new Matrix(-0, 1, -1, -0, 3, 1), 1 }; + yield return new object[] { matrix, -180, 1, 2, new Matrix(-1, -0, 0, -1, 2, 4), 1 }; + yield return new object[] { matrix, -90, 1, 2, new Matrix(0, -1, 1, 0, -1, 3), 1 }; + yield return new object[] { matrix, -45, 1, 2, new Matrix(0.70711, -0.70711, 0.70711, 0.70711, -1.12132, 1.29289), 1 }; + yield return new object[] { matrix, 45, 1, 2, new Matrix(0.70711, 0.70711, -0.70711, 0.70711, 1.70711, -0.12132), 1 }; + yield return new object[] { matrix, 0, 1, 2, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, 90, 1, 2, new Matrix(0, 1, -1, 0, 3, 1), 1 }; + yield return new object[] { matrix, 30, 1, 2, new Matrix(0.86603, 0.5, -0.5, 0.86603, 1.13396, -0.23205), 1 }; + yield return new object[] { matrix, 180, 1, 2, new Matrix(-1, 0, -0, -1, 2, 4), 1 }; + yield return new object[] { matrix, 270, 1, 2, new Matrix(-0, -1, 1, -0, -1, 3), 1 }; + yield return new object[] { matrix, 360, 1, 2, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, 720, 1, 2, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { matrix, 20, double.NaN, double.NaN, new Matrix(0.93969, 0.34202, -0.34202, 0.93969, double.NaN, double.NaN), 1 }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -360, 0, 0, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -270, 0, 0, new Matrix(-0, 2, -3, -0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -180, 0, 0, new Matrix(-2, -0, 0, -3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -90, 0, 0, new Matrix(0, -2, 3, 0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -45, 0, 0, new Matrix(1.41421, -1.41421, 2.12132, 2.12132, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 45, 0, 0, new Matrix(1.41421, 1.41421, -2.12132, 2.12132, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 0, 0, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 90, 0, 0, new Matrix(0, 2, -3, 0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 30, 0, 0, new Matrix(1.73205, 1, -1.5, 2.59808, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 180, 0, 0, new Matrix(-2, 0, -0, -3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 270, 0, 0, new Matrix(-0, -2, 3, -0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 360, 0, 0, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 720, 0, 0, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -360, 1, 2, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -270, 1, 2, new Matrix(-0, 2, -3, -0, 3, 1), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -180, 1, 2, new Matrix(-2, -0, 0, -3, 2, 4), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -90, 1, 2, new Matrix(0, -2, 3, 0, -1, 3), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -45, 1, 2, new Matrix(1.41421, -1.41421, 2.12132, 2.12132, -1.12132, 1.29289), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 45, 1, 2, new Matrix(1.41421, 1.41421, -2.12132, 2.12132, 1.70711, -0.12132), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 1, 2, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 90, 1, 2, new Matrix(0, 2, -3, 0, 3, 1), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 30, 1, 2, new Matrix(1.73205, 1, -1.5, 2.59808, 1.13396, -0.23205), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 180, 1, 2, new Matrix(-2, 0, -0, -3, 2, 4), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 270, 1, 2, new Matrix(-0, -2, 3, -0, -1, 3), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 360, 1, 2, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 720, 1, 2, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 20, double.NaN, double.NaN, new Matrix(1.87939, 0.68404, -1.02606, 2.81908, double.NaN, double.NaN), 6 }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -360, 0, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -270, 0, 0, new Matrix(-0.5, 1, -1, 0.75, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -180, 0, 0, new Matrix(-1, -0.5, -0.75, -1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -90, 0, 0, new Matrix(0.5, -1, 1, -0.75, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -45, 0, 0, new Matrix(1.06066, -0.35355, 1.23744, 0.17678, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 45, 0, 0, new Matrix(0.35355, 1.06066, -0.17678, 1.23744, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 0, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 90, 0, 0, new Matrix(-0.5, 1, -1, 0.75, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 30, 0, 0, new Matrix(0.61603, 0.93301, 0.14951, 1.24103, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 180, 0, 0, new Matrix(-1, -0.5, -0.75, -1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 270, 0, 0, new Matrix(0.5, -1, 1, -0.75, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 360, 0, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 720, 0, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -360, 1, 2, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -270, 1, 2, new Matrix(-0.5, 1, -1, 0.75, 3, 1), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -180, 1, 2, new Matrix(-1, -0.5, -0.75, -1, 2, 4), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -90, 1, 2, new Matrix(0.5, -1, 1, -0.75, -1, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -45, 1, 2, new Matrix(1.06066, -0.35355, 1.23744, 0.17678, -1.12132, 1.29289), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 45, 1, 2, new Matrix(0.35355, 1.06066, -0.17678, 1.23744, 1.70711, -0.12132), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 1, 2, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 90, 1, 2, new Matrix(-0.5, 1, -1, 0.75, 3, 1), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 30, 1, 2, new Matrix(0.61603, 0.93301, 0.14951, 1.24103, 1.13396, -0.23205), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 180, 1, 2, new Matrix(-1, -0.5, -0.75, -1, 2, 4), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 270, 1, 2, new Matrix(0.5, -1, 1, -0.75, -1, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 360, 1, 2, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 720, 1, 2, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 20, double.NaN, double.NaN, new Matrix(0.76868, 0.81187, 0.36275, 1.19621, double.NaN, double.NaN), 0.625 }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -360, 0, 0, new Matrix(1, -0, 0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -270, 0, 0, new Matrix(-0, 1, -1, -0, -3, 2), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -180, 0, 0, new Matrix(-1, -0, 0, -1, -2, -3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -90, 0, 0, new Matrix(0, -1, 1, 0, 3, -2), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -45, 0, 0, new Matrix(0.70711, -0.70711, 0.70711, 0.70711, 3.53553, 0.70711), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 45, 0, 0, new Matrix(0.70711, 0.70711, -0.70711, 0.70711, -0.70711, 3.53553), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 0, 0, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 90, 0, 0, new Matrix(0, 1, -1, 0, -3, 2), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 30, 0, 0, new Matrix(0.86603, 0.5, -0.5, 0.86603, 0.23205, 3.59808), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 180, 0, 0, new Matrix(-1, 0, -0, -1, -2, -3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 270, 0, 0, new Matrix(-0, -1, 1, -0, 3, -2), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 360, 0, 0, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 720, 0, 0, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -360, 1, 2, new Matrix(1, -0, 0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -270, 1, 2, new Matrix(-0, 1, -1, -0, -0, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -180, 1, 2, new Matrix(-1, -0, 0, -1, 0, 1), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -90, 1, 2, new Matrix(0, -1, 1, 0, 2, 1), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -45, 1, 2, new Matrix(0.70711, -0.70711, 0.70711, 0.70711, 2.41421, 2), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 45, 1, 2, new Matrix(0.70711, 0.70711, -0.70711, 0.70711, 1, 3.41421), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 1, 2, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 90, 1, 2, new Matrix(0, 1, -1, 0, 0, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 30, 1, 2, new Matrix(0.86603, 0.5, -0.5, 0.86603, 1.36603, 3.36603), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 180, 1, 2, new Matrix(-1, 0, -0, -1, 0, 1), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 270, 1, 2, new Matrix(-0, -1, 1, -0, 2, 1), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 360, 1, 2, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 720, 1, 2, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 20, double.NaN, double.NaN, new Matrix(0.93969, 0.34202, -0.34202, 0.93969, double.NaN, double.NaN), 1 }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -360, 0, 0, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -270, 0, 0, new Matrix(-0, 2, -3, -0, -2, 1), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -180, 0, 0, new Matrix(-2, -0, 0, -3, -1, -2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -90, 0, 0, new Matrix(0, -2, 3, 0, 2, -1), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -45, 0, 0, new Matrix(1.41421, -1.41421, 2.12132, 2.12132, 2.12132, 0.70711), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 45, 0, 0, new Matrix(1.41421, 1.41421, -2.12132, 2.12132, -0.70711, 2.12132), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 0, 0, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 90, 0, 0, new Matrix(0, 2, -3, 0, -2, 1), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 30, 0, 0, new Matrix(1.73205, 1, -1.5, 2.59808, -0.13397, 2.23205), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 180, 0, 0, new Matrix(-2, 0, -0, -3, -1, -2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 270, 0, 0, new Matrix(-0, -2, 3, -0, 2, -1), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 360, 0, 0, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 720, 0, 0, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -360, 1, 2, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -270, 1, 2, new Matrix(-0, 2, -3, -0, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -180, 1, 2, new Matrix(-2, -0, 0, -3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -90, 1, 2, new Matrix(0, -2, 3, 0, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -45, 1, 2, new Matrix(1.41421, -1.41421, 2.12132, 2.12132, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 45, 1, 2, new Matrix(1.41421, 1.41421, -2.12132, 2.12132, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 1, 2, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 90, 1, 2, new Matrix(0, 2, -3, 0, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 30, 1, 2, new Matrix(1.73205, 1, -1.5, 2.59808, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 180, 1, 2, new Matrix(-2, 0, -0, -3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 270, 1, 2, new Matrix(-0, -2, 3, -0, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 360, 1, 2, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 720, 1, 2, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 20, double.NaN, double.NaN, new Matrix(1.87939, 0.68404, -1.02606, 2.81908, double.NaN, double.NaN), 6 }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -360, 0, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -270, 0, 0, new Matrix(-0.5, 1, -1, 0.75, -3, 2), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -180, 0, 0, new Matrix(-1, -0.5, -0.75, -1, -2, -3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -90, 0, 0, new Matrix(0.5, -1, 1, -0.75, 3, -2), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -45, 0, 0, new Matrix(1.06066, -0.35355, 1.23744, 0.17678, 3.53553, 0.70711), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 45, 0, 0, new Matrix(0.35355, 1.06066, -0.17678, 1.23744, -0.70711, 3.53553), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 0, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 90, 0, 0, new Matrix(-0.5, 1, -1, 0.75, -3, 2), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 30, 0, 0, new Matrix(0.61603, 0.93301, 0.14951, 1.24103, 0.23205, 3.59808), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 180, 0, 0, new Matrix(-1, -0.5, -0.75, -1, -2, -3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 270, 0, 0, new Matrix(0.5, -1, 1, -0.75, 3, -2), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 360, 0, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 720, 0, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -360, 1, 2, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -270, 1, 2, new Matrix(-0.5, 1, -1, 0.75, -0, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -180, 1, 2, new Matrix(-1, -0.5, -0.75, -1, 0, 1), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -90, 1, 2, new Matrix(0.5, -1, 1, -0.75, 2, 1), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -45, 1, 2, new Matrix(1.06066, -0.35355, 1.23744, 0.17678, 2.41421, 2), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 45, 1, 2, new Matrix(0.35355, 1.06066, -0.17678, 1.23744, 1, 3.41421), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 1, 2, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 90, 1, 2, new Matrix(-0.5, 1, -1, 0.75, 0, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 30, 1, 2, new Matrix(0.61603, 0.93301, 0.14951, 1.24103, 1.36603, 3.36603), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 180, 1, 2, new Matrix(-1, -0.5, -0.75, -1, 0, 1), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 270, 1, 2, new Matrix(0.5, -1, 1, -0.75, 2, 1), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 360, 1, 2, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 720, 1, 2, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 20, double.NaN, double.NaN, new Matrix(0.76868, 0.81187, 0.36275, 1.19621, double.NaN, double.NaN), 0.625 }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -360, 0, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -270, 0, 0, new Matrix(-0.5, 2, -3, 0.75, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -180, 0, 0, new Matrix(-2, -0.5, -0.75, -3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -90, 0, 0, new Matrix(0.5, -2, 3, -0.75, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -45, 0, 0, new Matrix(1.76777, -1.06066, 2.65165, 1.59099, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 45, 0, 0, new Matrix(1.06066, 1.76777, -1.59099, 2.65165, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 0, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 90, 0, 0, new Matrix(-0.5, 2, -3, 0.75, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 30, 0, 0, new Matrix(1.48205, 1.43301, -0.85048, 2.97308, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 180, 0, 0, new Matrix(-2, -0.5, -0.75, -3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 270, 0, 0, new Matrix(0.5, -2, 3, -0.75, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 360, 0, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 720, 0, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -360, 1, 2, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -270, 1, 2, new Matrix(-0.5, 2, -3, 0.75, 3, 1), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -180, 1, 2, new Matrix(-2, -0.5, -0.75, -3, 2, 4), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -90, 1, 2, new Matrix(0.5, -2, 3, -0.75, -1, 3), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -45, 1, 2, new Matrix(1.76777, -1.06066, 2.65165, 1.59099, -1.12132, 1.29289), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 45, 1, 2, new Matrix(1.06066, 1.76777, -1.59099, 2.65165, 1.70711, -0.12132), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 1, 2, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 90, 1, 2, new Matrix(-0.5, 2, -3, 0.75, 3, 1), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 30, 1, 2, new Matrix(1.48205, 1.43301, -0.85048, 2.97308, 1.13396, -0.23205), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 180, 1, 2, new Matrix(-2, -0.5, -0.75, -3, 2, 4), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 270, 1, 2, new Matrix(0.5, -2, 3, -0.75, -1, 3), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 360, 1, 2, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 720, 1, 2, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 20, double.NaN, double.NaN, new Matrix(1.70838, 1.15389, -0.32129, 3.07559, double.NaN, double.NaN), 5.625 }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -360, 0, 0, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -270, 0, 0, new Matrix(-3, 2, -5, 4, -7, 6), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -180, 0, 0, new Matrix(-2, -3, -4, -5, -6, -7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -90, 0, 0, new Matrix(3, -2, 5, -4, 7, -6), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -45, 0, 0, new Matrix(3.53553, 0.70711, 6.36396, 0.70711, 9.19239, 0.70711), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 45, 0, 0, new Matrix(-0.70711, 3.53553, -0.70711, 6.36396, -0.70711, 9.19239), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 0, 0, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 90, 0, 0, new Matrix(-3, 2, -5, 4, -7, 6), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 30, 0, 0, new Matrix(0.23205, 3.59808, 0.9641, 6.33013, 1.69615, 9.06218), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 180, 0, 0, new Matrix(-2, -3, -4, -5, -6, -7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 270, 0, 0, new Matrix(3, -2, 5, -4, 7, -6), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 360, 0, 0, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 720, 0, 0, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -360, 1, 2, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -270, 1, 2, new Matrix(-3, 2, -5, 4, -4, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -180, 1, 2, new Matrix(-2, -3, -4, -5, -4, -3), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -90, 1, 2, new Matrix(3, -2, 5, -4, 6, -3), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -45, 1, 2, new Matrix(3.53553, 0.70711, 6.36396, 0.70711, 8.07107, 2), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 45, 1, 2, new Matrix(-0.70711, 3.53553, -0.70711, 6.36396, 1, 9.07107), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 1, 2, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 90, 1, 2, new Matrix(-3, 2, -5, 4, -4, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 30, 1, 2, new Matrix(0.23205, 3.59808, 0.9641, 6.33013, 2.83013, 8.83013), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 180, 1, 2, new Matrix(-2, -3, -4, -5, -4, -3), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 270, 1, 2, new Matrix(3, -2, 5, -4, 6, -3), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 360, 1, 2, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 720, 1, 2, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 20, double.NaN, double.NaN, new Matrix(0.85332, 3.50312, 2.04867, 6.06654, double.NaN, double.NaN), -2 }; + + // No inverse. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -360, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -270, 0, 0, new Matrix(-0, 0, -0, 0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -180, 0, 0, new Matrix(0, -0, 0, -0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -90, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -45, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 45, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 90, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 30, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 180, 0, 0, new Matrix(-0, 0, -0, 0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 270, 0, 0, new Matrix(0, -0, 0, -0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 360, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 720, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -360, 1, 2, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -270, 1, 2, new Matrix(-0, 0, -0, 0, 3, 1), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -180, 1, 2, new Matrix(0, -0, 0, -0, 2, 4), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -90, 1, 2, new Matrix(0, 0, 0, 0, -1, 3), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -45, 1, 2, new Matrix(0, 0, 0, 0, -1.12132, 1.29289), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 45, 1, 2, new Matrix(0, 0, 0, 0, 1.70711, -0.12132), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 1, 2, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 90, 1, 2, new Matrix(0, 0, 0, 0, 3, 1), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 30, 1, 2, new Matrix(0, 0, 0, 0, 1.13396, -0.23205), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 180, 1, 2, new Matrix(-0, 0, -0, 0, 2, 4), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 270, 1, 2, new Matrix(0, -0, 0, -0, -1, 3), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 360, 1, 2, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 720, 1, 2, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 20, double.NaN, double.NaN, new Matrix(0, 0, 0, 0, double.NaN, double.NaN), 0 }; + } + + [Theory] + [MemberData(nameof(RotateAt_TestData))] + public void RotateAt_Invoke_Success(Matrix matrix, double angle, double centerX, double centerY, Matrix expected, double expectedDeterminant) + { + matrix.RotateAt(angle, centerX, centerY); + Helpers.AssertEqualRounded(expected, matrix); + Assert.Equal(expected.IsIdentity, matrix.IsIdentity); + Assert.Equal(expected.HasInverse, matrix.HasInverse); + Assert.Equal(expectedDeterminant, matrix.Determinant, precision: 5); + } + + public static IEnumerable RotateAtPrepend_TestData() + { + // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, -360, 0, 0, new Matrix(1, -0, 0, 1, 0, 0), 1 }; + yield return new object[] { matrix, -270, 0, 0, new Matrix(-0, 1, -1, -0, 0, 0), 1 }; + yield return new object[] { matrix, -180, 0, 0, new Matrix(-1, -0, 0, -1, 0, 0), 1 }; + yield return new object[] { matrix, -90, 0, 0, new Matrix(0, -1, 1, 0, 0, 0), 1 }; + yield return new object[] { matrix, -45, 0, 0, new Matrix(0.70711, -0.70711, 0.70711, 0.70711, 0, 0), 1 }; + yield return new object[] { matrix, 45, 0, 0, new Matrix(0.70711, 0.70711, -0.70711, 0.70711, 0, 0), 1 }; + yield return new object[] { matrix, 0, 0, 0, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, 90, 0, 0, new Matrix(0, 1, -1, 0, 0, 0), 1 }; + yield return new object[] { matrix, 30, 0, 0, new Matrix(0.86603, 0.5, -0.5, 0.86603, 0, 0), 1 }; + yield return new object[] { matrix, 180, 0, 0, new Matrix(-1, 0, -0, -1, 0, 0), 1 }; + yield return new object[] { matrix, 270, 0, 0, new Matrix(-0, -1, 1, -0, 0, 0), 1 }; + yield return new object[] { matrix, 360, 0, 0, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, 720, 0, 0, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { matrix, -360, 1, 2, new Matrix(1, -0, 0, 1, 0, 0), 1 }; + yield return new object[] { matrix, -270, 1, 2, new Matrix(-0, 1, -1, -0, 3, 1), 1 }; + yield return new object[] { matrix, -180, 1, 2, new Matrix(-1, -0, 0, -1, 2, 4), 1 }; + yield return new object[] { matrix, -90, 1, 2, new Matrix(0, -1, 1, 0, -1, 3), 1 }; + yield return new object[] { matrix, -45, 1, 2, new Matrix(0.70711, -0.70711, 0.70711, 0.70711, -1.12132, 1.29289), 1 }; + yield return new object[] { matrix, 45, 1, 2, new Matrix(0.70711, 0.70711, -0.70711, 0.70711, 1.70711, -0.12132), 1 }; + yield return new object[] { matrix, 0, 1, 2, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, 90, 1, 2, new Matrix(0, 1, -1, 0, 3, 1), 1 }; + yield return new object[] { matrix, 30, 1, 2, new Matrix(0.86603, 0.5, -0.5, 0.86603, 1.13396, -0.23205), 1 }; + yield return new object[] { matrix, 180, 1, 2, new Matrix(-1, 0, -0, -1, 2, 4), 1 }; + yield return new object[] { matrix, 270, 1, 2, new Matrix(-0, -1, 1, -0, -1, 3), 1 }; + yield return new object[] { matrix, 360, 1, 2, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, 720, 1, 2, new Matrix(1, 0, -0, 1, 0, 0), 1 }; + yield return new object[] { matrix, double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { matrix, 20, double.NaN, double.NaN, new Matrix(0.93969, 0.34202, -0.34202, 0.93969, double.NaN, double.NaN), 1 }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -360, 0, 0, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -270, 0, 0, new Matrix(-0, 3, -2, -0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -180, 0, 0, new Matrix(-2, -0, 0, -3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -90, 0, 0, new Matrix(0, -3, 2, 0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -45, 0, 0, new Matrix(1.41421, -2.12132, 1.41421, 2.12132, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 45, 0, 0, new Matrix(1.41421, 2.12132, -1.41421, 2.12132, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 0, 0, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 90, 0, 0, new Matrix(0, 3, -2, 0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 30, 0, 0, new Matrix(1.73205, 1.5, -1, 2.59808, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 180, 0, 0, new Matrix(-2, 0, -0, -3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 270, 0, 0, new Matrix(-0, -3, 2, -0, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 360, 0, 0, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 720, 0, 0, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -360, 1, 2, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -270, 1, 2, new Matrix(-0, 3, -2, -0, 6, 3), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -180, 1, 2, new Matrix(-2, -0, 0, -3, 4, 12), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -90, 1, 2, new Matrix(0, -3, 2, 0, -2, 9), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -45, 1, 2, new Matrix(1.41421, -2.12132, 1.41421, 2.12132, -2.24264, 3.87868), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 45, 1, 2, new Matrix(1.41421, 2.12132, -1.41421, 2.12132, 3.41421, -0.36396), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 1, 2, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 90, 1, 2, new Matrix(0, 3, -2, 0, 6, 3), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 30, 1, 2, new Matrix(1.73205, 1.5, -1, 2.59808, 2.26795, -0.69615), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 180, 1, 2, new Matrix(-2, 0, -0, -3, 4, 12), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 270, 1, 2, new Matrix(-0, -3, 2, -0, -2, 9), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 360, 1, 2, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 720, 1, 2, new Matrix(2, 0, 0, 3, 0, 0), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 20, double.NaN, double.NaN, new Matrix(1.87939, 1.02606, -0.68404, 2.81908, double.NaN, double.NaN), 6 }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -360, 0, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -270, 0, 0, new Matrix(0.75, 1, -1, -0.5, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -180, 0, 0, new Matrix(-1, -0.5, -0.75, -1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -90, 0, 0, new Matrix(-0.75, -1, 1, 0.5, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -45, 0, 0, new Matrix(0.17678, -0.35355, 1.23744, 1.06066, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 45, 0, 0, new Matrix(1.23744, 1.06066, -0.17678, 0.35355, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 0, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 90, 0, 0, new Matrix(0.75, 1, -1, -0.5, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 30, 0, 0, new Matrix(1.24103, 0.93301, 0.14951, 0.61603, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 180, 0, 0, new Matrix(-1, -0.5, -0.75, -1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 270, 0, 0, new Matrix(-0.75, -1, 1, 0.5, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 360, 0, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 720, 0, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -360, 1, 2, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -270, 1, 2, new Matrix(0.75, 1, -1, -0.5, 3.75, 2.5), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -180, 1, 2, new Matrix(-1, -0.5, -0.75, -1, 5, 5), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -90, 1, 2, new Matrix(-0.75, -1, 1, 0.5, 1.25, 2.5), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -45, 1, 2, new Matrix(0.17678, -0.35355, 1.23744, 1.06066, -0.15165, 0.73223), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 45, 1, 2, new Matrix(1.23744, 1.06066, -0.17678, 0.35355, 1.61612, 0.73223), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 1, 2, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 90, 1, 2, new Matrix(0.75, 1, -1, -0.5, 3.75, 2.5), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 30, 1, 2, new Matrix(1.24103, 0.93301, 0.14951, 0.61603, 0.95994, 0.33494), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 180, 1, 2, new Matrix(-1, -0.5, -0.75, -1, 5, 5), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 270, 1, 2, new Matrix(-0.75, -1, 1, 0.5, 1.25, 2.5), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 360, 1, 2, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 720, 1, 2, new Matrix(1, 0.5, 0.75, 1, 0, 0), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 20, double.NaN, double.NaN, new Matrix(1.19621, 0.81187, 0.36275, 0.76868, double.NaN, double.NaN), 0.625 }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -360, 0, 0, new Matrix(1, -0, 0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -270, 0, 0, new Matrix(-0, 1, -1, -0, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -180, 0, 0, new Matrix(-1, -0, 0, -1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -90, 0, 0, new Matrix(0, -1, 1, 0, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -45, 0, 0, new Matrix(0.70711, -0.70711, 0.70711, 0.70711, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 45, 0, 0, new Matrix(0.70711, 0.70711, -0.70711, 0.70711, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 0, 0, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 90, 0, 0, new Matrix(0, 1, -1, 0, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 30, 0, 0, new Matrix(0.86603, 0.5, -0.5, 0.86603, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 180, 0, 0, new Matrix(-1, 0, -0, -1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 270, 0, 0, new Matrix(-0, -1, 1, -0, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 360, 0, 0, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 720, 0, 0, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -360, 1, 2, new Matrix(1, -0, 0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -270, 1, 2, new Matrix(-0, 1, -1, -0, 5, 4), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -180, 1, 2, new Matrix(-1, -0, 0, -1, 4, 7), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -90, 1, 2, new Matrix(0, -1, 1, 0, 1, 6), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -45, 1, 2, new Matrix(0.70711, -0.70711, 0.70711, 0.70711, 0.87868, 4.29289), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 45, 1, 2, new Matrix(0.70711, 0.70711, -0.70711, 0.70711, 3.70711, 2.87868), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 1, 2, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 90, 1, 2, new Matrix(0, 1, -1, 0, 5, 4), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 30, 1, 2, new Matrix(0.86603, 0.5, -0.5, 0.86603, 3.13397, 2.76795), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 180, 1, 2, new Matrix(-1, 0, -0, -1, 4, 7), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 270, 1, 2, new Matrix(-0, -1, 1, -0, 1, 6), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 360, 1, 2, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 720, 1, 2, new Matrix(1, 0, -0, 1, 2, 3), 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 20, double.NaN, double.NaN, new Matrix(0.93969, 0.34202, -0.34202, 0.93969, double.NaN, double.NaN), 1 }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -360, 0, 0, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -270, 0, 0, new Matrix(-0, 3, -2, -0, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -180, 0, 0, new Matrix(-2, -0, 0, -3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -90, 0, 0, new Matrix(0, -3, 2, 0, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -45, 0, 0, new Matrix(1.41421, -2.12132, 1.41421, 2.12132, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 45, 0, 0, new Matrix(1.41421, 2.12132, -1.41421, 2.12132, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 0, 0, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 90, 0, 0, new Matrix(0, 3, -2, 0, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 30, 0, 0, new Matrix(1.73205, 1.5, -1, 2.59808, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 180, 0, 0, new Matrix(-2, 0, -0, -3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 270, 0, 0, new Matrix(-0, -3, 2, -0, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 360, 0, 0, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 720, 0, 0, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -360, 1, 2, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -270, 1, 2, new Matrix(-0, 3, -2, -0, 7, 5), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -180, 1, 2, new Matrix(-2, -0, 0, -3, 5, 14), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -90, 1, 2, new Matrix(0, -3, 2, 0, -1, 11), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -45, 1, 2, new Matrix(1.41421, -2.12132, 1.41421, 2.12132, -1.24264, 5.87868), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 45, 1, 2, new Matrix(1.41421, 2.12132, -1.41421, 2.12132, 4.41421, 1.63604), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 1, 2, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 90, 1, 2, new Matrix(0, 3, -2, 0, 7, 5), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 30, 1, 2, new Matrix(1.73205, 1.5, -1, 2.59808, 3.26795, 1.30385), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 180, 1, 2, new Matrix(-2, 0, -0, -3, 5, 14), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 270, 1, 2, new Matrix(-0, -3, 2, -0, -1, 11), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 360, 1, 2, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 720, 1, 2, new Matrix(2, 0, 0, 3, 1, 2), 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 20, double.NaN, double.NaN, new Matrix(1.87939, 1.02606, -0.68404, 2.81908, double.NaN, double.NaN), 6 }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -360, 0, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -270, 0, 0, new Matrix(0.75, 1, -1, -0.5, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -180, 0, 0, new Matrix(-1, -0.5, -0.75, -1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -90, 0, 0, new Matrix(-0.75, -1, 1, 0.5, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -45, 0, 0, new Matrix(0.17678, -0.35355, 1.23744, 1.06066, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 45, 0, 0, new Matrix(1.23744, 1.06066, -0.17678, 0.35355, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 0, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 90, 0, 0, new Matrix(0.75, 1, -1, -0.5, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 30, 0, 0, new Matrix(1.24103, 0.93301, 0.14951, 0.61603, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 180, 0, 0, new Matrix(-1, -0.5, -0.75, -1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 270, 0, 0, new Matrix(-0.75, -1, 1, 0.5, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 360, 0, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 720, 0, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -360, 1, 2, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -270, 1, 2, new Matrix(0.75, 1, -1, -0.5, 5.75, 5.5), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -180, 1, 2, new Matrix(-1, -0.5, -0.75, -1, 7, 8), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -90, 1, 2, new Matrix(-0.75, -1, 1, 0.5, 3.25, 5.5), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -45, 1, 2, new Matrix(0.17678, -0.35355, 1.23744, 1.06066, 1.84835, 3.73223), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 45, 1, 2, new Matrix(1.23744, 1.06066, -0.17678, 0.35355, 3.61612, 3.73223), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 1, 2, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 90, 1, 2, new Matrix(0.75, 1, -1, -0.5, 5.75, 5.5), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 30, 1, 2, new Matrix(1.24103, 0.93301, 0.14951, 0.61603, 2.95994, 3.33494), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 180, 1, 2, new Matrix(-1, -0.5, -0.75, -1, 7, 8), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 270, 1, 2, new Matrix(-0.75, -1, 1, 0.5, 3.25, 5.5), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 360, 1, 2, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 720, 1, 2, new Matrix(1, 0.5, 0.75, 1, 2, 3), 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 20, double.NaN, double.NaN, new Matrix(1.19621, 0.81187, 0.36275, 0.76868, double.NaN, double.NaN), 0.625 }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -360, 0, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -270, 0, 0, new Matrix(0.75, 3, -2, -0.5, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -180, 0, 0, new Matrix(-2, -0.5, -0.75, -3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -90, 0, 0, new Matrix(-0.75, -3, 2, 0.5, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -45, 0, 0, new Matrix(0.88388, -1.76777, 1.94454, 2.47487, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 45, 0, 0, new Matrix(1.94454, 2.47487, -0.88388, 1.76777, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 0, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 90, 0, 0, new Matrix(0.75, 3, -2, -0.5, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 30, 0, 0, new Matrix(2.10705, 1.93301, -0.35048, 2.34808, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 180, 0, 0, new Matrix(-2, -0.5, -0.75, -3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 270, 0, 0, new Matrix(-0.75, -3, 2, 0.5, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 360, 0, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 720, 0, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -360, 1, 2, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -270, 1, 2, new Matrix(0.75, 3, -2, -0.5, 6.75, 4.5), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -180, 1, 2, new Matrix(-2, -0.5, -0.75, -3, 7, 13), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -90, 1, 2, new Matrix(-0.75, -3, 2, 0.5, 0.25, 8.5), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -45, 1, 2, new Matrix(0.88388, -1.76777, 1.94454, 2.47487, -1.27297, 3.31802), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 45, 1, 2, new Matrix(1.94454, 2.47487, -0.88388, 1.76777, 3.32322, 0.48959), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 1, 2, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 90, 1, 2, new Matrix(0.75, 3, -2, -0.5, 6.75, 4.5), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 30, 1, 2, new Matrix(2.10705, 1.93301, -0.35048, 2.34808, 2.09391, -0.12917), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 180, 1, 2, new Matrix(-2, -0.5, -0.75, -3, 7, 13), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 270, 1, 2, new Matrix(-0.75, -3, 2, 0.5, 0.25, 8.5), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 360, 1, 2, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 720, 1, 2, new Matrix(2, 0.5, 0.75, 3, 0, 0), 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 20, double.NaN, double.NaN, new Matrix(2.1359, 1.49591, 0.02073, 2.64807, double.NaN, double.NaN), 5.625 }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -360, 0, 0, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -270, 0, 0, new Matrix(4, 5, -2, -3, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -180, 0, 0, new Matrix(-2, -3, -4, -5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -90, 0, 0, new Matrix(-4, -5, 2, 3, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -45, 0, 0, new Matrix(-1.41421, -1.41421, 4.24264, 5.65685, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 45, 0, 0, new Matrix(4.24264, 5.65685, 1.41421, 1.41421, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 0, 0, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 90, 0, 0, new Matrix(4, 5, -2, -3, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 30, 0, 0, new Matrix(3.73205, 5.09808, 2.4641, 2.83013, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 180, 0, 0, new Matrix(-2, -3, -4, -5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 270, 0, 0, new Matrix(-4, -5, 2, 3, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 360, 0, 0, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 720, 0, 0, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -360, 1, 2, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -270, 1, 2, new Matrix(4, 5, -2, -3, 16, 21), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -180, 1, 2, new Matrix(-2, -3, -4, -5, 26, 33), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -90, 1, 2, new Matrix(-4, -5, 2, 3, 16, 19), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -45, 1, 2, new Matrix(-1.41421, -1.41421, 4.24264, 5.65685, 8.92892, 10.10051), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 45, 1, 2, new Matrix(4.24264, 5.65685, 1.41421, 1.41421, 8.92892, 11.51472), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 1, 2, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 90, 1, 2, new Matrix(4, 5, -2, -3, 16, 21), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 30, 1, 2, new Matrix(3.73205, 5.09808, 2.4641, 2.83013, 7.33975, 9.24166), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 180, 1, 2, new Matrix(-2, -3, -4, -5, 26, 33), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 270, 1, 2, new Matrix(-4, -5, 2, 3, 16, 19), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 360, 1, 2, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 720, 1, 2, new Matrix(2, 3, 4, 5, 6, 7), -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 20, double.NaN, double.NaN, new Matrix(3.24747, 4.52918, 3.07473, 3.6724, double.NaN, double.NaN), -2 }; + + // No inverse. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -360, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -270, 0, 0, new Matrix(0, 0, -0, -0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -180, 0, 0, new Matrix(-0, -0, 0, 0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -90, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -45, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 45, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 90, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 30, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 180, 0, 0, new Matrix(0, 0, -0, -0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 270, 0, 0, new Matrix(-0, -0, 0, 0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 360, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 720, 0, 0, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -360, 1, 2, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -270, 1, 2, new Matrix(0, 0, -0, -0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -180, 1, 2, new Matrix(-0, -0, 0, 0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -90, 1, 2, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -45, 1, 2, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 45, 1, 2, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 1, 2, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 90, 1, 2, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 30, 1, 2, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 180, 1, 2, new Matrix(0, 0, -0, -0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 270, 1, 2, new Matrix(-0, -0, 0, 0, 0, 0), -0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 360, 1, 2, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 720, 1, 2, new Matrix(0, 0, 0, 0, 0, 0), 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 20, double.NaN, double.NaN, new Matrix(0, 0, 0, 0, double.NaN, double.NaN), 0 }; + } + + [Theory] + [MemberData(nameof(RotateAt_TestData))] + public void RotateAtPrepend_Invoke_Success(Matrix matrix, double angle, double centerX, double centerY, Matrix expected, double expectedDeterminant) + { + matrix.RotateAtPrepend(angle, centerX, centerY); + Helpers.AssertEqualRounded(expected, matrix); + Assert.Equal(expected.IsIdentity, matrix.IsIdentity); + Assert.Equal(expected.HasInverse, matrix.HasInverse); + Assert.Equal(expectedDeterminant, matrix.Determinant, precision: 5); + } + + + public static IEnumerable Scale_TestData() + { + // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, -2, -3, new Matrix(-2, 0, 0, -3, 0, 0) }; + yield return new object[] { matrix, -2, 3, new Matrix(-2, 0, 0, 3, 0, 0) }; + yield return new object[] { matrix, 2, -3, new Matrix(2, 0, 0, -3, 0, 0) }; + yield return new object[] { matrix, -1, -2, new Matrix(-1, 0, 0, -2, 0, 0) }; + yield return new object[] { matrix, -1, 0, new Matrix(-1, 0, 0, 0, 0, 0) }; + yield return new object[] { matrix, 0, -2, new Matrix(0, 0, 0, -2, 0, 0) }; + yield return new object[] { matrix, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { matrix, 1, 1, new Matrix(1, 0, 0, 1, 0, 0) }; + yield return new object[] { matrix, 1, 0, new Matrix(1, 0, 0, 0, 0, 0) }; + yield return new object[] { matrix, 0, 2, new Matrix(0, 0, 0, 2, 0, 0) }; + yield return new object[] { matrix, 1, 2, new Matrix(1, 0, 0, 2, 0, 0) }; + yield return new object[] { matrix, 1.1, 2.2, new Matrix(1.1, 0, 0, 2.2, 0, 0) }; + yield return new object[] { matrix, 2, 3, new Matrix(2, 0, 0, 3, 0, 0) }; + yield return new object[] { matrix, double.NaN, 3, new Matrix(double.NaN, 0, 0, 3, 0, 0) }; + yield return new object[] { matrix, 2, double.NaN, new Matrix(2, 0, 0, double.NaN, 0, 0) }; + yield return new object[] { matrix, double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, 0, 0) }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -2, -3, new Matrix(-4, 0, 0, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -2, 3, new Matrix(-4, 0, 0, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, -3, new Matrix(4, 0, 0, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, -2, new Matrix(-2, 0, 0, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, 0, new Matrix(-2, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, -2, new Matrix(0, 0, 0, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 1, new Matrix(2, 0, 0, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 0, new Matrix(2, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 2, new Matrix(0, 0, 0, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 2, new Matrix(2, 0, 0, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1.1, 2.2, new Matrix(2.2, 0, 0, 6.6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, 3, new Matrix(4, 0, 0, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, 3, new Matrix(double.NaN, 0, 0, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, double.NaN, new Matrix(4, 0, 0, double.NaN, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, 0, 0) }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -2, -3, new Matrix(-2, -1.5, -1.5, -3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -2, 3, new Matrix(-2, 1.5, -1.5, 3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, -3, new Matrix(2, -1.5, 1.5, -3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, -2, new Matrix(-1, -1, -0.75, -2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, 0, new Matrix(-1, 0, -0.75, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, -2, new Matrix(0, -1, 0, -2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 1, new Matrix(1, 0.5, 0.75, 1, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 0, new Matrix(1, 0, 0.75, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 2, new Matrix(0, 1, 0, 2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 2, new Matrix(1, 1, 0.75, 2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1.1, 2.2, new Matrix(1.1, 1.1, 0.825, 2.2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, 3, new Matrix(2, 1.5, 1.5, 3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, 3, new Matrix(double.NaN, 1.5, double.NaN, 3, double.NaN, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, double.NaN, new Matrix(2, double.NaN, 1.5, double.NaN, 0, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -2, -3, new Matrix(-2, 0, 0, -3, -4, -9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -2, 3, new Matrix(-2, 0, 0, 3, -4, 9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, -3, new Matrix(2, 0, 0, -3, 4, -9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, -2, new Matrix(-1, 0, 0, -2, -2, -6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, 0, new Matrix(-1, 0, 0, 0, -2, 0) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, -2, new Matrix(0, 0, 0, -2, 0, -6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 1, new Matrix(1, 0, 0, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 0, new Matrix(1, 0, 0, 0, 2, 0) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 2, new Matrix(0, 0, 0, 2, 0, 6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 2, new Matrix(1, 0, 0, 2, 2, 6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1.1, 2.2, new Matrix(1.1, 0, 0, 2.2, 2.2, 6.6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, 3, new Matrix(2, 0, 0, 3, 4, 9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, 3, new Matrix(double.NaN, 0, 0, 3, double.NaN, 9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, double.NaN, new Matrix(2, 0, 0, double.NaN, 4, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -2, -3, new Matrix(-4, 0, 0, -9, -2, -6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -2, 3, new Matrix(-4, 0, 0, 9, -2, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, -3, new Matrix(4, 0, 0, -9, 2, -6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, -2, new Matrix(-2, 0, 0, -6, -1, -4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, 0, new Matrix(-2, 0, 0, 0, -1, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, -2, new Matrix(0, 0, 0, -6, 0, -4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 1, new Matrix(2, 0, 0, 3, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 0, new Matrix(2, 0, 0, 0, 1, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 2, new Matrix(0, 0, 0, 6, 0, 4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 2, new Matrix(2, 0, 0, 6, 1, 4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1.1, 2.2, new Matrix(2.2, 0, 0, 6.6, 1.1, 4.4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, 3, new Matrix(4, 0, 0, 9, 2, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, 3, new Matrix(double.NaN, 0, 0, 9, double.NaN, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, double.NaN, new Matrix(4, 0, 0, double.NaN, 2, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -2, -3, new Matrix(-2, -1.5, -1.5, -3, -4, -9) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -2, 3, new Matrix(-2, 1.5, -1.5, 3, -4, 9) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, -3, new Matrix(2, -1.5, 1.5, -3, 4, -9) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, -2, new Matrix(-1, -1, -0.75, -2, -2, -6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, 0, new Matrix(-1, 0, -0.75, 0, -2, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, -2, new Matrix(0, -1, 0, -2, 0, -6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 1, new Matrix(1, 0.5, 0.75, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 0, new Matrix(1, 0, 0.75, 0, 2, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 2, new Matrix(0, 1, 0, 2, 0, 6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 2, new Matrix(1, 1, 0.75, 2, 2, 6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1.1, 2.2, new Matrix(1.1, 1.1, 0.825, 2.2, 2.2, 6.6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, 3, new Matrix(2, 1.5, 1.5, 3, 4, 9) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, 3, new Matrix(double.NaN, 1.5, double.NaN, 3, double.NaN, 9) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, double.NaN, new Matrix(2, double.NaN, 1.5, double.NaN, 4, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -2, -3, new Matrix(-4, -1.5, -1.5, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -2, 3, new Matrix(-4, 1.5, -1.5, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, -3, new Matrix(4, -1.5, 1.5, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, -2, new Matrix(-2, -1, -0.75, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, 0, new Matrix(-2, 0, -0.75, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, -2, new Matrix(0, -1, 0, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 1, new Matrix(2, 0.5, 0.75, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 0, new Matrix(2, 0, 0.75, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 2, new Matrix(0, 1, 0, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 2, new Matrix(2, 1, 0.75, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1.1, 2.2, new Matrix(2.2, 1.1, 0.825, 6.6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, 3, new Matrix(4, 1.5, 1.5, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, 3, new Matrix(double.NaN, 1.5, double.NaN, 9, double.NaN, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, double.NaN, new Matrix(4, double.NaN, 1.5, double.NaN, 0, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -2, -3, new Matrix(-4, -9, -8, -15, -12, -21) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -2, 3, new Matrix(-4, 9, -8, 15, -12, 21) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, -3, new Matrix(4, -9, 8, -15, 12, -21) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, -2, new Matrix(-2, -6, -4, -10, -6, -14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, 0, new Matrix(-2, 0, -4, 0, -6, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, -2, new Matrix(0, -6, 0, -10, 0, -14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 1, new Matrix(2, 3, 4, 5, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 0, new Matrix(2, 0, 4, 0, 6, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 2, new Matrix(0, 6, 0, 10, 0, 14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 2, new Matrix(2, 6, 4, 10, 6, 14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1.1, 2.2, new Matrix(2.2, 6.6, 4.4, 11, 6.6, 15.4) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, 3, new Matrix(4, 9, 8, 15, 12, 21) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, 3, new Matrix(double.NaN, 9, double.NaN, 15, double.NaN, 21) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, double.NaN, new Matrix(4, double.NaN, 8, double.NaN, 12, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // No inverse. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -2, -3, new Matrix(-0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -2, 3, new Matrix(-0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, -3, new Matrix(0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, -2, new Matrix(-0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, 0, new Matrix(-0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, -2, new Matrix(0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 1, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 2, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 2, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1.1, 2.2, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, 3, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, 3, new Matrix(double.NaN, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, double.NaN, new Matrix(0, 0, 0, double.NaN, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, 0, 0) }; + } + + [Theory] + [MemberData(nameof(Scale_TestData))] + public void Scale_Invoke_Success(Matrix matrix, double scaleX, double scaleY, Matrix expected) + { + matrix.Scale(scaleX, scaleY); + Helpers.AssertEqualRounded(expected, matrix); + Assert.Equal(expected.IsIdentity, matrix.IsIdentity); + Assert.Equal(expected.HasInverse, matrix.HasInverse); + Assert.Equal(expected.Determinant, matrix.Determinant, precision: 5); + } + + public static IEnumerable ScalePrepend_TestData() + { + // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, -2, -3, new Matrix(-2, 0, 0, -3, 0, 0) }; + yield return new object[] { matrix, -2, 3, new Matrix(-2, 0, 0, 3, 0, 0) }; + yield return new object[] { matrix, 2, -3, new Matrix(2, 0, 0, -3, 0, 0) }; + yield return new object[] { matrix, -1, -2, new Matrix(-1, 0, 0, -2, 0, 0) }; + yield return new object[] { matrix, -1, 0, new Matrix(-1, 0, 0, 0, 0, 0) }; + yield return new object[] { matrix, 0, -2, new Matrix(0, 0, 0, -2, 0, 0) }; + yield return new object[] { matrix, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { matrix, 1, 1, new Matrix(1, 0, 0, 1, 0, 0) }; + yield return new object[] { matrix, 1, 0, new Matrix(1, 0, 0, 0, 0, 0) }; + yield return new object[] { matrix, 0, 2, new Matrix(0, 0, 0, 2, 0, 0) }; + yield return new object[] { matrix, 1, 2, new Matrix(1, 0, 0, 2, 0, 0) }; + yield return new object[] { matrix, 1.1, 2.2, new Matrix(1.1, 0, 0, 2.2, 0, 0) }; + yield return new object[] { matrix, 2, 3, new Matrix(2, 0, 0, 3, 0, 0) }; + yield return new object[] { matrix, double.NaN, 3, new Matrix(double.NaN, 0, 0, 3, 0, 0) }; + yield return new object[] { matrix, 2, double.NaN, new Matrix(2, 0, 0, double.NaN, 0, 0) }; + yield return new object[] { matrix, double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, 0, 0) }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -2, -3, new Matrix(-4, 0, 0, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -2, 3, new Matrix(-4, 0, 0, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, -3, new Matrix(4, 0, 0, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, -2, new Matrix(-2, 0, 0, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, 0, new Matrix(-2, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, -2, new Matrix(0, 0, 0, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 1, new Matrix(2, 0, 0, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 0, new Matrix(2, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 2, new Matrix(0, 0, 0, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 2, new Matrix(2, 0, 0, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1.1, 2.2, new Matrix(2.2, 0, 0, 6.6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, 3, new Matrix(4, 0, 0, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, 3, new Matrix(double.NaN, 0, 0, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, double.NaN, new Matrix(4, 0, 0, double.NaN, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, 0, 0) }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -2, -3, new Matrix(-2, -1.5, -1.5, -3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -2, 3, new Matrix(-2, 1.5, -1.5, 3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, -3, new Matrix(2, -1.5, 1.5, -3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, -2, new Matrix(-1, -1, -0.75, -2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, 0, new Matrix(-1, 0, -0.75, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, -2, new Matrix(0, -1, 0, -2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 1, new Matrix(1, 0.5, 0.75, 1, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 0, new Matrix(1, 0, 0.75, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 2, new Matrix(0, 1, 0, 2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 2, new Matrix(1, 1, 0.75, 2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1.1, 2.2, new Matrix(1.1, 1.1, 0.825, 2.2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, 3, new Matrix(2, 1.5, 1.5, 3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, 3, new Matrix(double.NaN, 1.5, double.NaN, 3, double.NaN, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, double.NaN, new Matrix(2, double.NaN, 1.5, double.NaN, 0, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -2, -3, new Matrix(-2, 0, 0, -3, -4, -9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -2, 3, new Matrix(-2, 0, 0, 3, -4, 9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, -3, new Matrix(2, 0, 0, -3, 4, -9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, -2, new Matrix(-1, 0, 0, -2, -2, -6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, 0, new Matrix(-1, 0, 0, 0, -2, 0) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, -2, new Matrix(0, 0, 0, -2, 0, -6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 1, new Matrix(1, 0, 0, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 0, new Matrix(1, 0, 0, 0, 2, 0) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 2, new Matrix(0, 0, 0, 2, 0, 6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 2, new Matrix(1, 0, 0, 2, 2, 6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1.1, 2.2, new Matrix(1.1, 0, 0, 2.2, 2.2, 6.6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, 3, new Matrix(2, 0, 0, 3, 4, 9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, 3, new Matrix(double.NaN, 0, 0, 3, double.NaN, 9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, double.NaN, new Matrix(2, 0, 0, double.NaN, 4, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -2, -3, new Matrix(-4, 0, 0, -9, -2, -6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -2, 3, new Matrix(-4, 0, 0, 9, -2, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, -3, new Matrix(4, 0, 0, -9, 2, -6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, -2, new Matrix(-2, 0, 0, -6, -1, -4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, 0, new Matrix(-2, 0, 0, 0, -1, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, -2, new Matrix(0, 0, 0, -6, 0, -4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 1, new Matrix(2, 0, 0, 3, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 0, new Matrix(2, 0, 0, 0, 1, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 2, new Matrix(0, 0, 0, 6, 0, 4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 2, new Matrix(2, 0, 0, 6, 1, 4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1.1, 2.2, new Matrix(2.2, 0, 0, 6.6, 1.1, 4.4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, 3, new Matrix(4, 0, 0, 9, 2, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, 3, new Matrix(double.NaN, 0, 0, 9, double.NaN, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, double.NaN, new Matrix(4, 0, 0, double.NaN, 2, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -2, -3, new Matrix(-2, -1.5, -1.5, -3, -4, -9) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -2, 3, new Matrix(-2, 1.5, -1.5, 3, -4, 9) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, -3, new Matrix(2, -1.5, 1.5, -3, 4, -9) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, -2, new Matrix(-1, -1, -0.75, -2, -2, -6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, 0, new Matrix(-1, 0, -0.75, 0, -2, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, -2, new Matrix(0, -1, 0, -2, 0, -6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 1, new Matrix(1, 0.5, 0.75, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 0, new Matrix(1, 0, 0.75, 0, 2, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 2, new Matrix(0, 1, 0, 2, 0, 6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 2, new Matrix(1, 1, 0.75, 2, 2, 6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1.1, 2.2, new Matrix(1.1, 1.1, 0.825, 2.2, 2.2, 6.6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, 3, new Matrix(2, 1.5, 1.5, 3, 4, 9) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, 3, new Matrix(double.NaN, 1.5, double.NaN, 3, double.NaN, 9) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, double.NaN, new Matrix(2, double.NaN, 1.5, double.NaN, 4, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -2, -3, new Matrix(-4, -1.5, -1.5, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -2, 3, new Matrix(-4, 1.5, -1.5, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, -3, new Matrix(4, -1.5, 1.5, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, -2, new Matrix(-2, -1, -0.75, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, 0, new Matrix(-2, 0, -0.75, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, -2, new Matrix(0, -1, 0, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 1, new Matrix(2, 0.5, 0.75, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 0, new Matrix(2, 0, 0.75, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 2, new Matrix(0, 1, 0, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 2, new Matrix(2, 1, 0.75, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1.1, 2.2, new Matrix(2.2, 1.1, 0.825, 6.6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, 3, new Matrix(4, 1.5, 1.5, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, 3, new Matrix(double.NaN, 1.5, double.NaN, 9, double.NaN, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, double.NaN, new Matrix(4, double.NaN, 1.5, double.NaN, 0, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -2, -3, new Matrix(-4, -9, -8, -15, -12, -21) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -2, 3, new Matrix(-4, 9, -8, 15, -12, 21) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, -3, new Matrix(4, -9, 8, -15, 12, -21) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, -2, new Matrix(-2, -6, -4, -10, -6, -14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, 0, new Matrix(-2, 0, -4, 0, -6, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, -2, new Matrix(0, -6, 0, -10, 0, -14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 1, new Matrix(2, 3, 4, 5, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 0, new Matrix(2, 0, 4, 0, 6, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 2, new Matrix(0, 6, 0, 10, 0, 14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 2, new Matrix(2, 6, 4, 10, 6, 14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1.1, 2.2, new Matrix(2.2, 6.6, 4.4, 11, 6.6, 15.4) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, 3, new Matrix(4, 9, 8, 15, 12, 21) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, 3, new Matrix(double.NaN, 9, double.NaN, 15, double.NaN, 21) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, double.NaN, new Matrix(4, double.NaN, 8, double.NaN, 12, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // No inverse. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -2, -3, new Matrix(-0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -2, 3, new Matrix(-0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, -3, new Matrix(0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, -2, new Matrix(-0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, 0, new Matrix(-0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, -2, new Matrix(0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 1, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 2, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 2, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1.1, 2.2, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, 3, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, 3, new Matrix(double.NaN, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, double.NaN, new Matrix(0, 0, 0, double.NaN, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, 0, 0) }; + } + + [Theory] + [MemberData(nameof(ScalePrepend_TestData))] + public void ScalePrepend_Invoke_Success(Matrix matrix, double scaleX, double scaleY, Matrix expected) + { + matrix.Scale(scaleX, scaleY); + Helpers.AssertEqualRounded(expected, matrix); + Assert.Equal(expected.IsIdentity, matrix.IsIdentity); + Assert.Equal(expected.HasInverse, matrix.HasInverse); + Assert.Equal(expected.Determinant, matrix.Determinant, precision: 5); + } + + public static IEnumerable ScaleAt_TestData() + { + // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, -2, -3, 0, 0, new Matrix(-2, 0, 0, -3, 0, 0) }; + yield return new object[] { matrix, -2, 3, 0, 0, new Matrix(-2, 0, 0, 3, 0, 0) }; + yield return new object[] { matrix, 2, -3, 0, 0, new Matrix(2, 0, 0, -3, 0, 0) }; + yield return new object[] { matrix, -1, -2, 0, 0, new Matrix(-1, 0, 0, -2, 0, 0) }; + yield return new object[] { matrix, -1, 0, 0, 0, new Matrix(-1, 0, 0, 0, 0, 0) }; + yield return new object[] { matrix, 0, -2, 0, 0, new Matrix(0, 0, 0, -2, 0, 0) }; + yield return new object[] { matrix, 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { matrix, 1, 1, 0, 0, new Matrix(1, 0, 0, 1, 0, 0) }; + yield return new object[] { matrix, 1, 0, 0, 0, new Matrix(1, 0, 0, 0, 0, 0) }; + yield return new object[] { matrix, 0, 2, 0, 0, new Matrix(0, 0, 0, 2, 0, 0) }; + yield return new object[] { matrix, 1, 2, 0, 0, new Matrix(1, 0, 0, 2, 0, 0) }; + yield return new object[] { matrix, 1.1, 2.2, 0, 0, new Matrix(1.1, 0, 0, 2.2, 0, 0) }; + yield return new object[] { matrix, 2, 3, 0, 0, new Matrix(2, 0, 0, 3, 0, 0) }; + yield return new object[] { matrix, double.NaN, 3, 0, 0, new Matrix(double.NaN, 0, 0, 3, double.NaN, 0) }; + yield return new object[] { matrix, 2, double.NaN, 0, 0, new Matrix(2, 0, 0, double.NaN, 0, double.NaN) }; + yield return new object[] { matrix, double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { matrix, -2, -3, 1, 2, new Matrix(-2, 0, 0, -3, 3, 8) }; + yield return new object[] { matrix, -2, 3, 1, 2, new Matrix(-2, 0, 0, 3, 3, -4) }; + yield return new object[] { matrix, 2, -3, 1, 2, new Matrix(2, 0, 0, -3, -1, 8) }; + yield return new object[] { matrix, -1, -2, 1, 2, new Matrix(-1, 0, 0, -2, 2, 6) }; + yield return new object[] { matrix, -1, 0, 1, 2, new Matrix(-1, 0, 0, 0, 2, 2) }; + yield return new object[] { matrix, 0, -2, 1, 2, new Matrix(0, 0, 0, -2, 1, 6) }; + yield return new object[] { matrix, 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 1, 2) }; + yield return new object[] { matrix, 1, 1, 1, 2, new Matrix(1, 0, 0, 1, 0, 0) }; + yield return new object[] { matrix, 1, 0, 1, 2, new Matrix(1, 0, 0, 0, 0, 2) }; + yield return new object[] { matrix, 0, 2, 1, 2, new Matrix(0, 0, 0, 2, 1, -2) }; + yield return new object[] { matrix, 1, 2, 1, 2, new Matrix(1, 0, 0, 2, 0, -2) }; + yield return new object[] { matrix, 1.1, 2.2, 1, 2, new Matrix(1.1, 0, 0, 2.2, -0.1, -2.4) }; + yield return new object[] { matrix, 2, 3, 1, 2, new Matrix(2, 0, 0, 3, -1, -4) }; + yield return new object[] { matrix, double.NaN, 3, 1, 2, new Matrix(double.NaN, 0, 0, 3, double.NaN, -4) }; + yield return new object[] { matrix, 2, double.NaN, 1, 2, new Matrix(2, 0, 0, double.NaN, -1, double.NaN) }; + yield return new object[] { matrix, double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { matrix, 2, 3, double.NaN, 2, new Matrix(2, 0, 0, 3, double.NaN, -4) }; + yield return new object[] { matrix, 2, 3, 1, double.NaN, new Matrix(2, 0, 0, 3, -1, double.NaN) }; + yield return new object[] { matrix, 2, 3, double.NaN, double.NaN, new Matrix(2, 0, 0, 3, double.NaN, double.NaN) }; + yield return new object[] { matrix, double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -2, -3, 0, 0, new Matrix(-4, 0, 0, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -2, 3, 0, 0, new Matrix(-4, 0, 0, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, -3, 0, 0, new Matrix(4, 0, 0, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, -2, 0, 0, new Matrix(-2, 0, 0, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, 0, 0, 0, new Matrix(-2, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, -2, 0, 0, new Matrix(0, 0, 0, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 1, 0, 0, new Matrix(2, 0, 0, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 0, 0, 0, new Matrix(2, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 2, 0, 0, new Matrix(0, 0, 0, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 2, 0, 0, new Matrix(2, 0, 0, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1.1, 2.2, 0, 0, new Matrix(2.2, 0, 0, 6.6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, 3, 0, 0, new Matrix(4, 0, 0, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, 3, 0, 0, new Matrix(double.NaN, 0, 0, 9, double.NaN, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, double.NaN, 0, 0, new Matrix(4, 0, 0, double.NaN, 0, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -2, -3, 1, 2, new Matrix(-4, 0, 0, -9, 3, 8) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -2, 3, 1, 2, new Matrix(-4, 0, 0, 9, 3, -4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, -3, 1, 2, new Matrix(4, 0, 0, -9, -1, 8) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, -2, 1, 2, new Matrix(-2, 0, 0, -6, 2, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, 0, 1, 2, new Matrix(-2, 0, 0, 0, 2, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, -2, 1, 2, new Matrix(0, 0, 0, -6, 1, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 1, 1, 2, new Matrix(2, 0, 0, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 0, 1, 2, new Matrix(2, 0, 0, 0, 0, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 2, 1, 2, new Matrix(0, 0, 0, 6, 1, -2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 2, 1, 2, new Matrix(2, 0, 0, 6, 0, -2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1.1, 2.2, 1, 2, new Matrix(2.2, 0, 0, 6.6, -0.1, -2.4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, 3, 1, 2, new Matrix(4, 0, 0, 9, -1, -4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, 3, 1, 2, new Matrix(double.NaN, 0, 0, 9, double.NaN, -4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, double.NaN, 1, 2, new Matrix(4, 0, 0, double.NaN, -1, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, 3, double.NaN, 2, new Matrix(4, 0, 0, 9, double.NaN, -4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, 3, 1, double.NaN, new Matrix(4, 0, 0, 9, -1, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, 3, double.NaN, double.NaN, new Matrix(4, 0, 0, 9, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -2, -3, 0, 0, new Matrix(-2, -1.5, -1.5, -3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -2, 3, 0, 0, new Matrix(-2, 1.5, -1.5, 3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, -3, 0, 0, new Matrix(2, -1.5, 1.5, -3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, -2, 0, 0, new Matrix(-1, -1, -0.75, -2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, 0, 0, 0, new Matrix(-1, 0, -0.75, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, -2, 0, 0, new Matrix(0, -1, 0, -2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 1, 0, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 0, 0, 0, new Matrix(1, 0, 0.75, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 2, 0, 0, new Matrix(0, 1, 0, 2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 2, 0, 0, new Matrix(1, 1, 0.75, 2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1.1, 2.2, 0, 0, new Matrix(1.1, 1.1, 0.825, 2.2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, 3, 0, 0, new Matrix(2, 1.5, 1.5, 3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, 3, 0, 0, new Matrix(double.NaN, 1.5, double.NaN, 3, double.NaN, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, double.NaN, 0, 0, new Matrix(2, double.NaN, 1.5, double.NaN, 0, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -2, -3, 1, 2, new Matrix(-2, -1.5, -1.5, -3, 3, 8) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -2, 3, 1, 2, new Matrix(-2, 1.5, -1.5, 3, 3, -4) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, -3, 1, 2, new Matrix(2, -1.5, 1.5, -3, -1, 8) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, -2, 1, 2, new Matrix(-1, -1, -0.75, -2, 2, 6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, 0, 1, 2, new Matrix(-1, 0, -0.75, 0, 2, 2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, -2, 1, 2, new Matrix(0, -1, 0, -2, 1, 6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 1, 2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 1, 1, 2, new Matrix(1, 0.5, 0.75, 1, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 0, 1, 2, new Matrix(1, 0, 0.75, 0, 0, 2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 2, 1, 2, new Matrix(0, 1, 0, 2, 1, -2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 2, 1, 2, new Matrix(1, 1, 0.75, 2, 0, -2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1.1, 2.2, 1, 2, new Matrix(1.1, 1.1, 0.825, 2.2, -0.1, -2.4) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, 3, 1, 2, new Matrix(2, 1.5, 1.5, 3, -1, -4) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, 3, 1, 2, new Matrix(double.NaN, 1.5, double.NaN, 3, double.NaN, -4) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, double.NaN, 1, 2, new Matrix(2, double.NaN, 1.5, double.NaN, -1, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, 3, double.NaN, 2, new Matrix(2, 1.5, 1.5, 3, double.NaN, -4) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, 3, 1, double.NaN, new Matrix(2, 1.5, 1.5, 3, -1, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, 3, double.NaN, double.NaN, new Matrix(2, 1.5, 1.5, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -2, -3, 0, 0, new Matrix(-2, 0, 0, -3, -4, -9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -2, 3, 0, 0, new Matrix(-2, 0, 0, 3, -4, 9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, -3, 0, 0, new Matrix(2, 0, 0, -3, 4, -9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, -2, 0, 0, new Matrix(-1, 0, 0, -2, -2, -6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, 0, 0, 0, new Matrix(-1, 0, 0, 0, -2, 0) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, -2, 0, 0, new Matrix(0, 0, 0, -2, 0, -6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 1, 0, 0, new Matrix(1, 0, 0, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 0, 0, 0, new Matrix(1, 0, 0, 0, 2, 0) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 2, 0, 0, new Matrix(0, 0, 0, 2, 0, 6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 2, 0, 0, new Matrix(1, 0, 0, 2, 2, 6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1.1, 2.2, 0, 0, new Matrix(1.1, 0, 0, 2.2, 2.2, 6.6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, 3, 0, 0, new Matrix(2, 0, 0, 3, 4, 9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, 3, 0, 0, new Matrix(double.NaN, 0, 0, 3, double.NaN, 9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, double.NaN, 0, 0, new Matrix(2, 0, 0, double.NaN, 4, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -2, -3, 1, 2, new Matrix(-2, 0, 0, -3, -1, -1) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -2, 3, 1, 2, new Matrix(-2, 0, 0, 3, -1, 5) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, -3, 1, 2, new Matrix(2, 0, 0, -3, 3, -1) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, -2, 1, 2, new Matrix(-1, 0, 0, -2, 0, 0) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, 0, 1, 2, new Matrix(-1, 0, 0, 0, 0, 2) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, -2, 1, 2, new Matrix(0, 0, 0, -2, 1, 0) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 1, 2) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 1, 1, 2, new Matrix(1, 0, 0, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 0, 1, 2, new Matrix(1, 0, 0, 0, 2, 2) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 2, 1, 2, new Matrix(0, 0, 0, 2, 1, 4) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 2, 1, 2, new Matrix(1, 0, 0, 2, 2, 4) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1.1, 2.2, 1, 2, new Matrix(1.1, 0, 0, 2.2, 2.1, 4.2) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, 3, 1, 2, new Matrix(2, 0, 0, 3, 3, 5) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, 3, 1, 2, new Matrix(double.NaN, 0, 0, 3, double.NaN, 5) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, double.NaN, 1, 2, new Matrix(2, 0, 0, double.NaN, 3, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, 3, double.NaN, 2, new Matrix(2, 0, 0, 3, double.NaN, 5) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, 3, 1, double.NaN, new Matrix(2, 0, 0, 3, 3, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, 3, double.NaN, double.NaN, new Matrix(2, 0, 0, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -2, -3, 0, 0, new Matrix(-4, 0, 0, -9, -2, -6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -2, 3, 0, 0, new Matrix(-4, 0, 0, 9, -2, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, -3, 0, 0, new Matrix(4, 0, 0, -9, 2, -6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, -2, 0, 0, new Matrix(-2, 0, 0, -6, -1, -4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, 0, 0, 0, new Matrix(-2, 0, 0, 0, -1, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, -2, 0, 0, new Matrix(0, 0, 0, -6, 0, -4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 1, 0, 0, new Matrix(2, 0, 0, 3, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 0, 0, 0, new Matrix(2, 0, 0, 0, 1, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 2, 0, 0, new Matrix(0, 0, 0, 6, 0, 4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 2, 0, 0, new Matrix(2, 0, 0, 6, 1, 4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1.1, 2.2, 0, 0, new Matrix(2.2, 0, 0, 6.6, 1.1, 4.4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, 3, 0, 0, new Matrix(4, 0, 0, 9, 2, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, 3, 0, 0, new Matrix(double.NaN, 0, 0, 9, double.NaN, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, double.NaN, 0, 0, new Matrix(4, 0, 0, double.NaN, 2, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -2, -3, 1, 2, new Matrix(-4, 0, 0, -9, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -2, 3, 1, 2, new Matrix(-4, 0, 0, 9, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, -3, 1, 2, new Matrix(4, 0, 0, -9, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, -2, 1, 2, new Matrix(-2, 0, 0, -6, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, 0, 1, 2, new Matrix(-2, 0, 0, 0, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, -2, 1, 2, new Matrix(0, 0, 0, -6, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 1, 1, 2, new Matrix(2, 0, 0, 3, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 0, 1, 2, new Matrix(2, 0, 0, 0, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 2, 1, 2, new Matrix(0, 0, 0, 6, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 2, 1, 2, new Matrix(2, 0, 0, 6, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1.1, 2.2, 1, 2, new Matrix(2.2, 0, 0, 6.6, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, 3, 1, 2, new Matrix(4, 0, 0, 9, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, 3, 1, 2, new Matrix(double.NaN, 0, 0, 9, double.NaN, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, double.NaN, 1, 2, new Matrix(4, 0, 0, double.NaN, 1, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, 3, double.NaN, 2, new Matrix(4, 0, 0, 9, double.NaN, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, 3, 1, double.NaN, new Matrix(4, 0, 0, 9, 1, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, 3, double.NaN, double.NaN, new Matrix(4, 0, 0, 9, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -2, -3, 0, 0, new Matrix(-2, -1.5, -1.5, -3, -4, -9) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -2, 3, 0, 0, new Matrix(-2, 1.5, -1.5, 3, -4, 9) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, -3, 0, 0, new Matrix(2, -1.5, 1.5, -3, 4, -9) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, -2, 0, 0, new Matrix(-1, -1, -0.75, -2, -2, -6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, 0, 0, 0, new Matrix(-1, 0, -0.75, 0, -2, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, -2, 0, 0, new Matrix(0, -1, 0, -2, 0, -6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 1, 0, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 0, 0, 0, new Matrix(1, 0, 0.75, 0, 2, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 2, 0, 0, new Matrix(0, 1, 0, 2, 0, 6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 2, 0, 0, new Matrix(1, 1, 0.75, 2, 2, 6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1.1, 2.2, 0, 0, new Matrix(1.1, 1.1, 0.825, 2.2, 2.2, 6.6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, 3, 0, 0, new Matrix(2, 1.5, 1.5, 3, 4, 9) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, 3, 0, 0, new Matrix(double.NaN, 1.5, double.NaN, 3, double.NaN, 9) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, double.NaN, 0, 0, new Matrix(2, double.NaN, 1.5, double.NaN, 4, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -2, -3, 1, 2, new Matrix(-2, -1.5, -1.5, -3, -1, -1) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -2, 3, 1, 2, new Matrix(-2, 1.5, -1.5, 3, -1, 5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, -3, 1, 2, new Matrix(2, -1.5, 1.5, -3, 3, -1) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, -2, 1, 2, new Matrix(-1, -1, -0.75, -2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, 0, 1, 2, new Matrix(-1, 0, -0.75, 0, 0, 2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, -2, 1, 2, new Matrix(0, -1, 0, -2, 1, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 1, 2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 1, 1, 2, new Matrix(1, 0.5, 0.75, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 0, 1, 2, new Matrix(1, 0, 0.75, 0, 2, 2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 2, 1, 2, new Matrix(0, 1, 0, 2, 1, 4) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 2, 1, 2, new Matrix(1, 1, 0.75, 2, 2, 4) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1.1, 2.2, 1, 2, new Matrix(1.1, 1.1, 0.825, 2.2, 2.1, 4.2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, 3, 1, 2, new Matrix(2, 1.5, 1.5, 3, 3, 5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, 3, 1, 2, new Matrix(double.NaN, 1.5, double.NaN, 3, double.NaN, 5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, double.NaN, 1, 2, new Matrix(2, double.NaN, 1.5, double.NaN, 3, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, 3, double.NaN, 2, new Matrix(2, 1.5, 1.5, 3, double.NaN, 5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, 3, 1, double.NaN, new Matrix(2, 1.5, 1.5, 3, 3, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, 3, double.NaN, double.NaN, new Matrix(2, 1.5, 1.5, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -2, -3, 0, 0, new Matrix(-4, -1.5, -1.5, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -2, 3, 0, 0, new Matrix(-4, 1.5, -1.5, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, -3, 0, 0, new Matrix(4, -1.5, 1.5, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, -2, 0, 0, new Matrix(-2, -1, -0.75, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, 0, 0, 0, new Matrix(-2, 0, -0.75, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, -2, 0, 0, new Matrix(0, -1, 0, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 1, 0, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 0, 0, 0, new Matrix(2, 0, 0.75, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 2, 0, 0, new Matrix(0, 1, 0, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 2, 0, 0, new Matrix(2, 1, 0.75, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1.1, 2.2, 0, 0, new Matrix(2.2, 1.1, 0.825, 6.6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, 3, 0, 0, new Matrix(4, 1.5, 1.5, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, 3, 0, 0, new Matrix(double.NaN, 1.5, double.NaN, 9, double.NaN, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, double.NaN, 0, 0, new Matrix(4, double.NaN, 1.5, double.NaN, 0, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -2, -3, 1, 2, new Matrix(-4, -1.5, -1.5, -9, 3, 8) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -2, 3, 1, 2, new Matrix(-4, 1.5, -1.5, 9, 3, -4) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, -3, 1, 2, new Matrix(4, -1.5, 1.5, -9, -1, 8) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, -2, 1, 2, new Matrix(-2, -1, -0.75, -6, 2, 6) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, 0, 1, 2, new Matrix(-2, 0, -0.75, 0, 2, 2) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, -2, 1, 2, new Matrix(0, -1, 0, -6, 1, 6) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 1, 2) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 1, 1, 2, new Matrix(2, 0.5, 0.75, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 0, 1, 2, new Matrix(2, 0, 0.75, 0, 0, 2) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 2, 1, 2, new Matrix(0, 1, 0, 6, 1, -2) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 2, 1, 2, new Matrix(2, 1, 0.75, 6, 0, -2) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1.1, 2.2, 1, 2, new Matrix(2.2, 1.1, 0.825, 6.6, -0.1, -2.4) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, 3, 1, 2, new Matrix(4, 1.5, 1.5, 9, -1, -4) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, 3, 1, 2, new Matrix(double.NaN, 1.5, double.NaN, 9, double.NaN, -4) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, double.NaN, 1, 2, new Matrix(4, double.NaN, 1.5, double.NaN, -1, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, 3, double.NaN, 2, new Matrix(4, 1.5, 1.5, 9, double.NaN, -4) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, 3, 1, double.NaN, new Matrix(4, 1.5, 1.5, 9, -1, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, 3, double.NaN, double.NaN, new Matrix(4, 1.5, 1.5, 9, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -2, -3, 0, 0, new Matrix(-4, -9, -8, -15, -12, -21) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -2, 3, 0, 0, new Matrix(-4, 9, -8, 15, -12, 21) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, -3, 0, 0, new Matrix(4, -9, 8, -15, 12, -21) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, -2, 0, 0, new Matrix(-2, -6, -4, -10, -6, -14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, 0, 0, 0, new Matrix(-2, 0, -4, 0, -6, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, -2, 0, 0, new Matrix(0, -6, 0, -10, 0, -14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 1, 0, 0, new Matrix(2, 3, 4, 5, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 0, 0, 0, new Matrix(2, 0, 4, 0, 6, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 2, 0, 0, new Matrix(0, 6, 0, 10, 0, 14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 2, 0, 0, new Matrix(2, 6, 4, 10, 6, 14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1.1, 2.2, 0, 0, new Matrix(2.2, 6.6, 4.4, 11, 6.6, 15.4) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, 3, 0, 0, new Matrix(4, 9, 8, 15, 12, 21) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, 3, 0, 0, new Matrix(double.NaN, 9, double.NaN, 15, double.NaN, 21) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, double.NaN, 0, 0, new Matrix(4, double.NaN, 8, double.NaN, 12, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -2, -3, 1, 2, new Matrix(-4, -9, -8, -15, -9, -13) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -2, 3, 1, 2, new Matrix(-4, 9, -8, 15, -9, 17) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, -3, 1, 2, new Matrix(4, -9, 8, -15, 11, -13) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, -2, 1, 2, new Matrix(-2, -6, -4, -10, -4, -8) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, 0, 1, 2, new Matrix(-2, 0, -4, 0, -4, 2) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, -2, 1, 2, new Matrix(0, -6, 0, -10, 1, -8) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 1, 2) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 1, 1, 2, new Matrix(2, 3, 4, 5, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 0, 1, 2, new Matrix(2, 0, 4, 0, 6, 2) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 2, 1, 2, new Matrix(0, 6, 0, 10, 1, 12) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 2, 1, 2, new Matrix(2, 6, 4, 10, 6, 12) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1.1, 2.2, 1, 2, new Matrix(2.2, 6.6, 4.4, 11, 6.5, 13) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, 3, 1, 2, new Matrix(4, 9, 8, 15, 11, 17) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, 3, 1, 2, new Matrix(double.NaN, 9, double.NaN, 15, double.NaN, 17) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, double.NaN, 1, 2, new Matrix(4, double.NaN, 8, double.NaN, 11, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, 3, double.NaN, 2, new Matrix(4, 9, 8, 15, double.NaN, 17) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, 3, 1, double.NaN, new Matrix(4, 9, 8, 15, 11, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, 3, double.NaN, double.NaN, new Matrix(4, 9, 8, 15, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // No inverse. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -2, -3, 0, 0, new Matrix(-0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -2, 3, 0, 0, new Matrix(-0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, -3, 0, 0, new Matrix(0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, -2, 0, 0, new Matrix(-0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, 0, 0, 0, new Matrix(-0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, -2, 0, 0, new Matrix(0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 1, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 2, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 2, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1.1, 2.2, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, 3, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, 3, 0, 0, new Matrix(double.NaN, 0, 0, 0, double.NaN, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, double.NaN, 0, 0, new Matrix(0, 0, 0, double.NaN, 0, double.NaN) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -2, -3, 1, 2, new Matrix(-0, 0, 0, -0, 3, 8) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -2, 3, 1, 2, new Matrix(-0, 0, 0, 0, 3, -4) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, -3, 1, 2, new Matrix(0, 0, 0, -0, -1, 8) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, -2, 1, 2, new Matrix(-0, 0, 0, -0, 2, 6) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, 0, 1, 2, new Matrix(-0, 0, 0, 0, 2, 2) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, -2, 1, 2, new Matrix(0, 0, 0, -0, 1, 6) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 1, 2) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 1, 1, 2, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 0, 1, 2, new Matrix(0, 0, 0, 0, 0, 2) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 2, 1, 2, new Matrix(0, 0, 0, 0, 1, -2) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 2, 1, 2, new Matrix(0, 0, 0, 0, 0, -2) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1.1, 2.2, 1, 2, new Matrix(0, 0, 0, 0, -0.1, -2.4) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, 3, 1, 2, new Matrix(0, 0, 0, 0, -1, -4) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, 3, 1, 2, new Matrix(double.NaN, 0, 0, 0, double.NaN, -4) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, double.NaN, 1, 2, new Matrix(0, 0, 0, double.NaN, -1, double.NaN) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, 3, double.NaN, 2, new Matrix(0, 0, 0, 0, double.NaN, -4) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, 3, 1, double.NaN, new Matrix(0, 0, 0, 0, -1, double.NaN) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, 3, double.NaN, double.NaN, new Matrix(0, 0, 0, 0, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + } + + [Theory] + [MemberData(nameof(ScaleAt_TestData))] + public void ScaleAt_Invoke_Success(Matrix matrix, double scaleX, double scaleY, double centerX, double centerY, Matrix expected) + { + matrix.ScaleAt(scaleX, scaleY, centerX, centerY); + Helpers.AssertEqualRounded(expected, matrix); + Assert.Equal(expected.IsIdentity, matrix.IsIdentity); + Assert.Equal(expected.HasInverse, matrix.HasInverse); + Assert.Equal(expected.Determinant, matrix.Determinant, precision: 5); + } + + public static IEnumerable ScaleAtPrepend_TestData() + { + // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, -2, -3, 0, 0, new Matrix(-2, 0, 0, -3, 0, 0) }; + yield return new object[] { matrix, -2, 3, 0, 0, new Matrix(-2, 0, 0, 3, 0, 0) }; + yield return new object[] { matrix, 2, -3, 0, 0, new Matrix(2, 0, 0, -3, 0, 0) }; + yield return new object[] { matrix, -1, -2, 0, 0, new Matrix(-1, 0, 0, -2, 0, 0) }; + yield return new object[] { matrix, -1, 0, 0, 0, new Matrix(-1, 0, 0, 0, 0, 0) }; + yield return new object[] { matrix, 0, -2, 0, 0, new Matrix(0, 0, 0, -2, 0, 0) }; + yield return new object[] { matrix, 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { matrix, 1, 1, 0, 0, new Matrix(1, 0, 0, 1, 0, 0) }; + yield return new object[] { matrix, 1, 0, 0, 0, new Matrix(1, 0, 0, 0, 0, 0) }; + yield return new object[] { matrix, 0, 2, 0, 0, new Matrix(0, 0, 0, 2, 0, 0) }; + yield return new object[] { matrix, 1, 2, 0, 0, new Matrix(1, 0, 0, 2, 0, 0) }; + yield return new object[] { matrix, 1.1, 2.2, 0, 0, new Matrix(1.1, 0, 0, 2.2, 0, 0) }; + yield return new object[] { matrix, 2, 3, 0, 0, new Matrix(2, 0, 0, 3, 0, 0) }; + yield return new object[] { matrix, double.NaN, 3, 0, 0, new Matrix(double.NaN, 0, 0, 3, double.NaN, 0) }; + yield return new object[] { matrix, 2, double.NaN, 0, 0, new Matrix(2, 0, 0, double.NaN, 0, double.NaN) }; + yield return new object[] { matrix, double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { matrix, -2, -3, 1, 2, new Matrix(-2, 0, 0, -3, 3, 8) }; + yield return new object[] { matrix, -2, 3, 1, 2, new Matrix(-2, 0, 0, 3, 3, -4) }; + yield return new object[] { matrix, 2, -3, 1, 2, new Matrix(2, 0, 0, -3, -1, 8) }; + yield return new object[] { matrix, -1, -2, 1, 2, new Matrix(-1, 0, 0, -2, 2, 6) }; + yield return new object[] { matrix, -1, 0, 1, 2, new Matrix(-1, 0, 0, 0, 2, 2) }; + yield return new object[] { matrix, 0, -2, 1, 2, new Matrix(0, 0, 0, -2, 1, 6) }; + yield return new object[] { matrix, 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 1, 2) }; + yield return new object[] { matrix, 1, 1, 1, 2, new Matrix(1, 0, 0, 1, 0, 0) }; + yield return new object[] { matrix, 1, 0, 1, 2, new Matrix(1, 0, 0, 0, 0, 2) }; + yield return new object[] { matrix, 0, 2, 1, 2, new Matrix(0, 0, 0, 2, 1, -2) }; + yield return new object[] { matrix, 1, 2, 1, 2, new Matrix(1, 0, 0, 2, 0, -2) }; + yield return new object[] { matrix, 1.1, 2.2, 1, 2, new Matrix(1.1, 0, 0, 2.2, -0.1, -2.4) }; + yield return new object[] { matrix, 2, 3, 1, 2, new Matrix(2, 0, 0, 3, -1, -4) }; + yield return new object[] { matrix, double.NaN, 3, 1, 2, new Matrix(double.NaN, 0, 0, 3, double.NaN, -4) }; + yield return new object[] { matrix, 2, double.NaN, 1, 2, new Matrix(2, 0, 0, double.NaN, -1, double.NaN) }; + yield return new object[] { matrix, double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { matrix, 2, 3, double.NaN, 2, new Matrix(2, 0, 0, 3, double.NaN, -4) }; + yield return new object[] { matrix, 2, 3, 1, double.NaN, new Matrix(2, 0, 0, 3, -1, double.NaN) }; + yield return new object[] { matrix, 2, 3, double.NaN, double.NaN, new Matrix(2, 0, 0, 3, double.NaN, double.NaN) }; + yield return new object[] { matrix, double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -2, -3, 0, 0, new Matrix(-4, 0, 0, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -2, 3, 0, 0, new Matrix(-4, 0, 0, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, -3, 0, 0, new Matrix(4, 0, 0, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, -2, 0, 0, new Matrix(-2, 0, 0, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, 0, 0, 0, new Matrix(-2, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, -2, 0, 0, new Matrix(0, 0, 0, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 1, 0, 0, new Matrix(2, 0, 0, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 0, 0, 0, new Matrix(2, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 2, 0, 0, new Matrix(0, 0, 0, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 2, 0, 0, new Matrix(2, 0, 0, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1.1, 2.2, 0, 0, new Matrix(2.2, 0, 0, 6.6, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, 3, 0, 0, new Matrix(4, 0, 0, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, 3, 0, 0, new Matrix(double.NaN, 0, 0, 9, double.NaN, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, double.NaN, 0, 0, new Matrix(4, 0, 0, double.NaN, 0, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -2, -3, 1, 2, new Matrix(-4, 0, 0, -9, 6, 24) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -2, 3, 1, 2, new Matrix(-4, 0, 0, 9, 6, -12) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, -3, 1, 2, new Matrix(4, 0, 0, -9, -2, 24) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, -2, 1, 2, new Matrix(-2, 0, 0, -6, 4, 18) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, 0, 1, 2, new Matrix(-2, 0, 0, 0, 4, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, -2, 1, 2, new Matrix(0, 0, 0, -6, 2, 18) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 2, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 1, 1, 2, new Matrix(2, 0, 0, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 0, 1, 2, new Matrix(2, 0, 0, 0, 0, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 2, 1, 2, new Matrix(0, 0, 0, 6, 2, -6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 2, 1, 2, new Matrix(2, 0, 0, 6, 0, -6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1.1, 2.2, 1, 2, new Matrix(2.2, 0, 0, 6.6, -0.2, -7.2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, 3, 1, 2, new Matrix(4, 0, 0, 9, -2, -12) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, 3, 1, 2, new Matrix(double.NaN, 0, 0, 9, double.NaN, -12) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, double.NaN, 1, 2, new Matrix(4, 0, 0, double.NaN, -2, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, 3, double.NaN, 2, new Matrix(4, 0, 0, 9, double.NaN, -12) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, 3, 1, double.NaN, new Matrix(4, 0, 0, 9, -2, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 2, 3, double.NaN, double.NaN, new Matrix(4, 0, 0, 9, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -2, -3, 0, 0, new Matrix(-2, -1, -2.25, -3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -2, 3, 0, 0, new Matrix(-2, -1, 2.25, 3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, -3, 0, 0, new Matrix(2, 1, -2.25, -3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, -2, 0, 0, new Matrix(-1, -0.5, -1.5, -2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, 0, 0, 0, new Matrix(-1, -0.5, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, -2, 0, 0, new Matrix(0, 0, -1.5, -2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 1, 0, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 0, 0, 0, new Matrix(1, 0.5, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 2, 0, 0, new Matrix(0, 0, 1.5, 2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 2, 0, 0, new Matrix(1, 0.5, 1.5, 2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1.1, 2.2, 0, 0, new Matrix(1.1, 0.55, 1.65, 2.2, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, 3, 0, 0, new Matrix(2, 1, 2.25, 3, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, 3, 0, 0, new Matrix(double.NaN, double.NaN, 2.25, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, double.NaN, 0, 0, new Matrix(2, 1, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -2, -3, 1, 2, new Matrix(-2, -1, -2.25, -3, 9, 9.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -2, 3, 1, 2, new Matrix(-2, -1, 2.25, 3, 0, -2.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, -3, 1, 2, new Matrix(2, 1, -2.25, -3, 5, 7.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, -2, 1, 2, new Matrix(-1, -0.5, -1.5, -2, 6.5, 7) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, 0, 1, 2, new Matrix(-1, -0.5, 0, 0, 3.5, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, -2, 1, 2, new Matrix(0, 0, -1.5, -2, 5.5, 6.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 2.5, 2.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 1, 1, 2, new Matrix(1, 0.5, 0.75, 1, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 0, 1, 2, new Matrix(1, 0.5, 0, 0, 1.5, 2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 2, 1, 2, new Matrix(0, 0, 1.5, 2, -0.5, -1.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 2, 1, 2, new Matrix(1, 0.5, 1.5, 2, -1.5, -2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1.1, 2.2, 1, 2, new Matrix(1.1, 0.55, 1.65, 2.2, -1.9, -2.45) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, 3, 1, 2, new Matrix(2, 1, 2.25, 3, -4, -4.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, 3, 1, 2, new Matrix(double.NaN, double.NaN, 2.25, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, double.NaN, 1, 2, new Matrix(2, 1, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, 3, double.NaN, 2, new Matrix(2, 1, 2.25, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, 3, 1, double.NaN, new Matrix(2, 1, 2.25, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 2, 3, double.NaN, double.NaN, new Matrix(2, 1, 2.25, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -2, -3, 0, 0, new Matrix(-2, 0, 0, -3, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -2, 3, 0, 0, new Matrix(-2, 0, 0, 3, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, -3, 0, 0, new Matrix(2, 0, 0, -3, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, -2, 0, 0, new Matrix(-1, 0, 0, -2, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, 0, 0, 0, new Matrix(-1, 0, 0, 0, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, -2, 0, 0, new Matrix(0, 0, 0, -2, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 1, 0, 0, new Matrix(1, 0, 0, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 0, 0, 0, new Matrix(1, 0, 0, 0, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 2, 0, 0, new Matrix(0, 0, 0, 2, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 2, 0, 0, new Matrix(1, 0, 0, 2, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1.1, 2.2, 0, 0, new Matrix(1.1, 0, 0, 2.2, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, 3, 0, 0, new Matrix(2, 0, 0, 3, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, 3, 0, 0, new Matrix(double.NaN, 0, 0, 3, double.NaN, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, double.NaN, 0, 0, new Matrix(2, 0, 0, double.NaN, 2, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -2, -3, 1, 2, new Matrix(-2, 0, 0, -3, 5, 11) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -2, 3, 1, 2, new Matrix(-2, 0, 0, 3, 5, -1) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, -3, 1, 2, new Matrix(2, 0, 0, -3, 1, 11) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, -2, 1, 2, new Matrix(-1, 0, 0, -2, 4, 9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, 0, 1, 2, new Matrix(-1, 0, 0, 0, 4, 5) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, -2, 1, 2, new Matrix(0, 0, 0, -2, 3, 9) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 3, 5) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 1, 1, 2, new Matrix(1, 0, 0, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 0, 1, 2, new Matrix(1, 0, 0, 0, 2, 5) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 2, 1, 2, new Matrix(0, 0, 0, 2, 3, 1) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 2, 1, 2, new Matrix(1, 0, 0, 2, 2, 1) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1.1, 2.2, 1, 2, new Matrix(1.1, 0, 0, 2.2, 1.9, 0.6) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, 3, 1, 2, new Matrix(2, 0, 0, 3, 1, -1) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, 3, 1, 2, new Matrix(double.NaN, 0, 0, 3, double.NaN, -1) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, double.NaN, 1, 2, new Matrix(2, 0, 0, double.NaN, 1, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, 3, double.NaN, 2, new Matrix(2, 0, 0, 3, double.NaN, -1) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, 3, 1, double.NaN, new Matrix(2, 0, 0, 3, 1, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 2, 3, double.NaN, double.NaN, new Matrix(2, 0, 0, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -2, -3, 0, 0, new Matrix(-4, 0, 0, -9, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -2, 3, 0, 0, new Matrix(-4, 0, 0, 9, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, -3, 0, 0, new Matrix(4, 0, 0, -9, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, -2, 0, 0, new Matrix(-2, 0, 0, -6, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, 0, 0, 0, new Matrix(-2, 0, 0, 0, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, -2, 0, 0, new Matrix(0, 0, 0, -6, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 1, 0, 0, new Matrix(2, 0, 0, 3, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 0, 0, 0, new Matrix(2, 0, 0, 0, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 2, 0, 0, new Matrix(0, 0, 0, 6, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 2, 0, 0, new Matrix(2, 0, 0, 6, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1.1, 2.2, 0, 0, new Matrix(2.2, 0, 0, 6.6, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, 3, 0, 0, new Matrix(4, 0, 0, 9, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, 3, 0, 0, new Matrix(double.NaN, 0, 0, 9, double.NaN, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, double.NaN, 0, 0, new Matrix(4, 0, 0, double.NaN, 1, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -2, -3, 1, 2, new Matrix(-4, 0, 0, -9, 7, 26) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -2, 3, 1, 2, new Matrix(-4, 0, 0, 9, 7, -10) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, -3, 1, 2, new Matrix(4, 0, 0, -9, -1, 26) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, -2, 1, 2, new Matrix(-2, 0, 0, -6, 5, 20) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, 0, 1, 2, new Matrix(-2, 0, 0, 0, 5, 8) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, -2, 1, 2, new Matrix(0, 0, 0, -6, 3, 20) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 3, 8) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 1, 1, 2, new Matrix(2, 0, 0, 3, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 0, 1, 2, new Matrix(2, 0, 0, 0, 1, 8) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 2, 1, 2, new Matrix(0, 0, 0, 6, 3, -4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 2, 1, 2, new Matrix(2, 0, 0, 6, 1, -4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1.1, 2.2, 1, 2, new Matrix(2.2, 0, 0, 6.6, 0.8, -5.2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, 3, 1, 2, new Matrix(4, 0, 0, 9, -1, -10) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, 3, 1, 2, new Matrix(double.NaN, 0, 0, 9, double.NaN, -10) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, double.NaN, 1, 2, new Matrix(4, 0, 0, double.NaN, -1, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, 3, double.NaN, 2, new Matrix(4, 0, 0, 9, double.NaN, -10) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, 3, 1, double.NaN, new Matrix(4, 0, 0, 9, -1, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 2, 3, double.NaN, double.NaN, new Matrix(4, 0, 0, 9, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -2, -3, 0, 0, new Matrix(-2, -1, -2.25, -3, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -2, 3, 0, 0, new Matrix(-2, -1, 2.25, 3, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, -3, 0, 0, new Matrix(2, 1, -2.25, -3, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, -2, 0, 0, new Matrix(-1, -0.5, -1.5, -2, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, 0, 0, 0, new Matrix(-1, -0.5, 0, 0, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, -2, 0, 0, new Matrix(0, 0, -1.5, -2, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 1, 0, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 0, 0, 0, new Matrix(1, 0.5, 0, 0, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 2, 0, 0, new Matrix(0, 0, 1.5, 2, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 2, 0, 0, new Matrix(1, 0.5, 1.5, 2, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1.1, 2.2, 0, 0, new Matrix(1.1, 0.55, 1.65, 2.2, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, 3, 0, 0, new Matrix(2, 1, 2.25, 3, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, 3, 0, 0, new Matrix(double.NaN, double.NaN, 2.25, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, double.NaN, 0, 0, new Matrix(2, 1, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -2, -3, 1, 2, new Matrix(-2, -1, -2.25, -3, 11, 12.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -2, 3, 1, 2, new Matrix(-2, -1, 2.25, 3, 2, 0.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, -3, 1, 2, new Matrix(2, 1, -2.25, -3, 7, 10.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, -2, 1, 2, new Matrix(-1, -0.5, -1.5, -2, 8.5, 10) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, 0, 1, 2, new Matrix(-1, -0.5, 0, 0, 5.5, 6) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, -2, 1, 2, new Matrix(0, 0, -1.5, -2, 7.5, 9.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 4.5, 5.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 1, 1, 2, new Matrix(1, 0.5, 0.75, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 0, 1, 2, new Matrix(1, 0.5, 0, 0, 3.5, 5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 2, 1, 2, new Matrix(0, 0, 1.5, 2, 1.5, 1.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 2, 1, 2, new Matrix(1, 0.5, 1.5, 2, 0.5, 1) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1.1, 2.2, 1, 2, new Matrix(1.1, 0.55, 1.65, 2.2, 0.1, 0.55) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, 3, 1, 2, new Matrix(2, 1, 2.25, 3, -2, -1.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, 3, 1, 2, new Matrix(double.NaN, double.NaN, 2.25, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, double.NaN, 1, 2, new Matrix(2, 1, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, 3, double.NaN, 2, new Matrix(2, 1, 2.25, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, 3, 1, double.NaN, new Matrix(2, 1, 2.25, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 2, 3, double.NaN, double.NaN, new Matrix(2, 1, 2.25, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -2, -3, 0, 0, new Matrix(-4, -1, -2.25, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -2, 3, 0, 0, new Matrix(-4, -1, 2.25, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, -3, 0, 0, new Matrix(4, 1, -2.25, -9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, -2, 0, 0, new Matrix(-2, -0.5, -1.5, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, 0, 0, 0, new Matrix(-2, -0.5, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, -2, 0, 0, new Matrix(0, 0, -1.5, -6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 1, 0, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 0, 0, 0, new Matrix(2, 0.5, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 2, 0, 0, new Matrix(0, 0, 1.5, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 2, 0, 0, new Matrix(2, 0.5, 1.5, 6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1.1, 2.2, 0, 0, new Matrix(2.2, 0.55, 1.65, 6.6, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, 3, 0, 0, new Matrix(4, 1, 2.25, 9, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, 3, 0, 0, new Matrix(double.NaN, double.NaN, 2.25, 9, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, double.NaN, 0, 0, new Matrix(4, 1, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -2, -3, 1, 2, new Matrix(-4, -1, -2.25, -9, 12, 25.5) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -2, 3, 1, 2, new Matrix(-4, -1, 2.25, 9, 3, -10.5) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, -3, 1, 2, new Matrix(4, 1, -2.25, -9, 4, 23.5) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, -2, 1, 2, new Matrix(-2, -0.5, -1.5, -6, 8.5, 19) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, 0, 1, 2, new Matrix(-2, -0.5, 0, 0, 5.5, 7) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, -2, 1, 2, new Matrix(0, 0, -1.5, -6, 6.5, 18.5) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 3.5, 6.5) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 1, 1, 2, new Matrix(2, 0.5, 0.75, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 0, 1, 2, new Matrix(2, 0.5, 0, 0, 1.5, 6) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 2, 1, 2, new Matrix(0, 0, 1.5, 6, 0.5, -5.5) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 2, 1, 2, new Matrix(2, 0.5, 1.5, 6, -1.5, -6) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1.1, 2.2, 1, 2, new Matrix(2.2, 0.55, 1.65, 6.6, -2, -7.25) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, 3, 1, 2, new Matrix(4, 1, 2.25, 9, -5, -12.5) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, 3, 1, 2, new Matrix(double.NaN, double.NaN, 2.25, 9, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, double.NaN, 1, 2, new Matrix(4, 1, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, 3, double.NaN, 2, new Matrix(4, 1, 2.25, 9, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, 3, 1, double.NaN, new Matrix(4, 1, 2.25, 9, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 2, 3, double.NaN, double.NaN, new Matrix(4, 1, 2.25, 9, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -2, -3, 0, 0, new Matrix(-4, -6, -12, -15, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -2, 3, 0, 0, new Matrix(-4, -6, 12, 15, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, -3, 0, 0, new Matrix(4, 6, -12, -15, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, -2, 0, 0, new Matrix(-2, -3, -8, -10, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, 0, 0, 0, new Matrix(-2, -3, 0, 0, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, -2, 0, 0, new Matrix(0, 0, -8, -10, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 1, 0, 0, new Matrix(2, 3, 4, 5, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 0, 0, 0, new Matrix(2, 3, 0, 0, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 2, 0, 0, new Matrix(0, 0, 8, 10, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 2, 0, 0, new Matrix(2, 3, 8, 10, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1.1, 2.2, 0, 0, new Matrix(2.2, 3.3, 8.8, 11, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, 3, 0, 0, new Matrix(4, 6, 12, 15, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, 3, 0, 0, new Matrix(double.NaN, double.NaN, 12, 15, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, double.NaN, 0, 0, new Matrix(4, 6, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -2, -3, 1, 2, new Matrix(-4, -6, -12, -15, 44, 56) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -2, 3, 1, 2, new Matrix(-4, -6, 12, 15, -4, -4) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, -3, 1, 2, new Matrix(4, 6, -12, -15, 36, 44) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, -2, 1, 2, new Matrix(-2, -3, -8, -10, 34, 43) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, 0, 1, 2, new Matrix(-2, -3, 0, 0, 18, 23) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, -2, 1, 2, new Matrix(0, 0, -8, -10, 32, 40) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 16, 20) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 1, 1, 2, new Matrix(2, 3, 4, 5, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 0, 1, 2, new Matrix(2, 3, 0, 0, 14, 17) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 2, 1, 2, new Matrix(0, 0, 8, 10, 0, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 2, 1, 2, new Matrix(2, 3, 8, 10, -2, -3) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1.1, 2.2, 1, 2, new Matrix(2.2, 3.3, 8.8, 11, -3.8, -5.3) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, 3, 1, 2, new Matrix(4, 6, 12, 15, -12, -16) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, 3, 1, 2, new Matrix(double.NaN, double.NaN, 12, 15, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, double.NaN, 1, 2, new Matrix(4, 6, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, 3, double.NaN, 2, new Matrix(4, 6, 12, 15, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, 3, 1, double.NaN, new Matrix(4, 6, 12, 15, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 2, 3, double.NaN, double.NaN, new Matrix(4, 6, 12, 15, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN) }; + + // No inverse. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -2, -3, 0, 0, new Matrix(-0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -2, 3, 0, 0, new Matrix(-0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, -3, 0, 0, new Matrix(0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, -2, 0, 0, new Matrix(-0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, 0, 0, 0, new Matrix(-0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, -2, 0, 0, new Matrix(0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 1, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 0, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 2, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 2, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1.1, 2.2, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, 3, 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, 3, 0, 0, new Matrix(double.NaN, 0, 0, 0, double.NaN, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, double.NaN, 0, 0, new Matrix(0, 0, 0, double.NaN, 0, double.NaN) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, double.NaN, 0, 0, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -2, -3, 1, 2, new Matrix(-0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -2, 3, 1, 2, new Matrix(-0, 0, 0, 0, 0, -0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, -3, 1, 2, new Matrix(0, 0, 0, -0, -0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, -2, 1, 2, new Matrix(-0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, 0, 1, 2, new Matrix(-0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, -2, 1, 2, new Matrix(0, 0, 0, -0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 0, 1, 2, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 1, 1, 2, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 0, 1, 2, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 2, 1, 2, new Matrix(0, 0, 0, 0, 0, -0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 2, 1, 2, new Matrix(0, 0, 0, 0, 0, -0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1.1, 2.2, 1, 2, new Matrix(0, 0, 0, 0, -0, -0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, 3, 1, 2, new Matrix(0, 0, 0, 0, -0, -0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, 3, 1, 2, new Matrix(double.NaN, 0, 0, 0, double.NaN, -0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, double.NaN, 1, 2, new Matrix(0, 0, 0, double.NaN, -0, double.NaN) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, double.NaN, 1, 2, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, 3, double.NaN, 2, new Matrix(0, 0, 0, 0, double.NaN, -0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, 3, 1, double.NaN, new Matrix(0, 0, 0, 0, -0, double.NaN) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 2, 3, double.NaN, double.NaN, new Matrix(0, 0, 0, 0, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, double.NaN, double.NaN, double.NaN, new Matrix(double.NaN, 0, 0, double.NaN, double.NaN, double.NaN) }; + } + + [Theory] + [MemberData(nameof(ScaleAtPrepend_TestData))] + public void ScaleAtPrepend_Invoke_Success(Matrix matrix, double scaleX, double scaleY, double centerX, double centerY, Matrix expected) + { + matrix.ScaleAtPrepend(scaleX, scaleY, centerX, centerY); + Helpers.AssertEqualRounded(expected, matrix); + Assert.Equal(expected.IsIdentity, matrix.IsIdentity); + Assert.Equal(expected.HasInverse, matrix.HasInverse); + Assert.Equal(expected.Determinant, matrix.Determinant, precision: 5); + } + + public static IEnumerable SetIdentity_TestData() + { + // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0) }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0) }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3) }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2) }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3) }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0) }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7) }; + + // No inverse. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0) }; + } + + [Theory] + [MemberData(nameof(SetIdentity_TestData))] + public void SetIdentity_Invoke_Success(Matrix matrix) + { + matrix.SetIdentity(); + Assert.Equal(Matrix.Identity, matrix); + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.True(matrix.HasInverse); + Assert.True(matrix.IsIdentity); + Assert.Equal(1, matrix.Determinant); + Assert.Equal(new Matrix(), matrix); + } + + public static IEnumerable Skew_TestData() + { + // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, -90, -180, new Matrix(1, 0, -16331239353195370, 1, 0, 0), false, true, 3 }; + yield return new object[] { matrix, -90, 180, new Matrix(1, -0, -16331239353195370, 1, 0, 0), false, true, -1 }; + yield return new object[] { matrix, 90, -180, new Matrix(1, 0, 16331239353195370, 1, 0, 0), false, true, -1 }; + yield return new object[] { matrix, -90, 0, new Matrix(1, 0, -16331239353195370, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, 0, -180, new Matrix(1, 0, 0, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, 0, 0, new Matrix(1, 0, 0, 1, 0, 0), true, true, 1 }; + yield return new object[] { matrix, 90, 180, new Matrix(1, -0, 16331239353195370, 1, 0, 0), false, true, 3 }; + yield return new object[] { matrix, 90, 0, new Matrix(1, 0, 16331239353195370, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, 180, 0, new Matrix(1, 0, -0, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, 0, 180, new Matrix(1, -0, 0, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, 180, 180, new Matrix(1, -0, -0, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, 270, 270, new Matrix(1, 5443746451065123, 5443746451065123, 1, 0, 0), false, true, -2.963437542348412E+31 }; + yield return new object[] { matrix, 180, 270, new Matrix(1, 5443746451065123, -0, 1, 0, 0), false, true, 1.66667 }; + yield return new object[] { matrix, 270, 270, new Matrix(1, 5443746451065123, 5443746451065123, 1, 0, 0), false, true, -2.963437542348412E+31 }; + yield return new object[] { matrix, -20, -30, new Matrix(1, -0.57735, -0.36397, 1, 0, 0), false, true, 0.78986 }; + yield return new object[] { matrix, 20, 30, new Matrix(1, 0.57735, 0.36397, 1, 0, 0), false, true, 0.78986 }; + yield return new object[] { matrix, -380, -390, new Matrix(1, -0.57735, -0.36397, 1, 0, 0), false, true, 0.78986 }; + yield return new object[] { matrix, 380, 390, new Matrix(1, 0.57735, 0.36397, 1, 0, 0), false, true, 0.78986 }; + yield return new object[] { matrix, -740, -750, new Matrix(1, -0.57735, -0.36397, 1, 0, 0), false, true, 0.78986 }; + yield return new object[] { matrix, 740, 750, new Matrix(1, 0.57735, 0.36397, 1, 0, 0), false, true, 0.78986 }; + yield return new object[] { matrix, 35, 0, new Matrix(1, 0, 0.70021, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, 0, 35, new Matrix(1, 0.70021, 0, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, -720, -720, new Matrix(1, -0, -0, 1, 0, 0), true, true, 1 }; + yield return new object[] { matrix, -360, -360, new Matrix(1, -0, -0, 1, 0, 0), true, true, 1 }; + yield return new object[] { matrix, 360, 360, new Matrix(1, 0, 0, 1, 0, 0), true, true, 1 }; + yield return new object[] { matrix, 720, 720, new Matrix(1, 0, 0, 1, 0, 0), true, true, 1 }; + yield return new object[] { matrix, 720, 720, new Matrix(1, 0, 0, 1, 0, 0), true, true, 1 }; + yield return new object[] { matrix, 20, double.NaN, new Matrix(1, double.NaN, 0.36397, 1, 0, 0), false, true, double.NaN }; + yield return new object[] { matrix, double.NaN, 30, new Matrix(1, 0.57735, double.NaN, 1, 0, 0), false, true, double.NaN }; + yield return new object[] { matrix, double.NaN, double.NaN, new Matrix(1, double.NaN, double.NaN, 1, 0, 0), false, true, double.NaN }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -90, -180, new Matrix(2, 0, -48993718059586110, 3, 0, 0), false, true, 18 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -90, 180, new Matrix(2, -0, -48993718059586110, 3, 0, 0), false, true, -6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 90, -180, new Matrix(2, 0, 48993718059586110, 3, 0, 0), false, true, -6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -90, 0, new Matrix(2, 0, -48993718059586110, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, -180, new Matrix(2, 0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 0, new Matrix(2, 0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 90, 180, new Matrix(2, -0, 48993718059586110, 3, 0, 0), false, true, 18 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 90, 0, new Matrix(2, 0, 48993718059586110, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 180, 0, new Matrix(2, 0, -0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 180, new Matrix(2, -0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 180, 180, new Matrix(2, -0, -0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 270, 270, new Matrix(2, 10887492902130246, 16331239353195366, 3, 0, 0), false, true, -1.7780625254090473E+32 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 180, 270, new Matrix(2, 10887492902130246, -0, 3, 0, 0), false, true, 10 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 270, 270, new Matrix(2, 10887492902130246, 16331239353195366, 3, 0, 0), false, true, -1.7780625254090473E+32 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -20, -30, new Matrix(2, -1.1547, -1.09191, 3, 0, 0), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 20, 30, new Matrix(2, 1.1547, 1.09191, 3, 0, 0), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -380, -390, new Matrix(2, -1.1547, -1.09191, 3, 0, 0), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 380, 390, new Matrix(2, 1.1547, 1.09191, 3, 0, 0), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -740, -750, new Matrix(2, -1.1547, -1.09191, 3, 0, 0), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 740, 750, new Matrix(2, 1.1547, 1.09191, 3, 0, 0), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 35, 0, new Matrix(2, 0, 2.10062, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 35, new Matrix(2, 1.40042, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -720, -720, new Matrix(2, 0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -360, -360, new Matrix(2, 0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 360, 360, new Matrix(2, 0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 720, 720, new Matrix(2, 0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 720, 720, new Matrix(2, 0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 20, double.NaN, new Matrix(2, double.NaN, 1.09191, double.NaN, 0, double.NaN), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, 30, new Matrix(double.NaN, 1.1547, double.NaN, 3, double.NaN, 0), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), false, true, double.NaN }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -90, -180, new Matrix(-8165619676597683, 0.5, -16331239353195370, 1, 0, 0), false, true, 3 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -90, 180, new Matrix(-8165619676597683, 0.5, -16331239353195370, 1, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 90, -180, new Matrix(8165619676597686, 0.5, 16331239353195370, 1, 0, 0), false, true, -1 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -90, 0, new Matrix(-8165619676597683, 0.5, -16331239353195370, 1, 0, 0), false, true, 1 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, -180, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 90, 180, new Matrix(8165619676597686, 0.5, 16331239353195370, 1, 0, 0), false, true, 2 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 90, 0, new Matrix(8165619676597686, 0.5, 16331239353195370, 1, 0, 0), false, true, 1 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 180, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 180, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 180, 180, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 270, 270, new Matrix(2721873225532562.5, 5443746451065124, 5443746451065124, 4082809838298843, 0, 0), false, true, -1.8521484639677582E+31 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 180, 270, new Matrix(1, 5443746451065124, 0.75, 4082809838298843, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 270, 270, new Matrix(2721873225532562.5, 5443746451065124, 5443746451065124, 4082809838298843, 0, 0), false, true, -1.8521484639677582E+31 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -20, -30, new Matrix(0.81801, -0.07735, 0.38603, 0.56699, 0, 0), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 20, 30, new Matrix(1.18199, 1.07735, 1.11396, 1.43301, 0, 0), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -380, -390, new Matrix(0.81801, -0.07735, 0.38603, 0.56699, 0, 0), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 380, 390, new Matrix(1.18199, 1.07735, 1.11396, 1.43301, 0, 0), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -740, -750, new Matrix(0.81801, -0.07735, 0.38603, 0.56699, 0, 0), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 740, 750, new Matrix(1.18199, 1.07735, 1.11396, 1.43301, 0, 0), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 35, 0, new Matrix(1.3501, 0.5, 1.45021, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 35, new Matrix(1, 1.20021, 0.75, 1.52516, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -720, -720, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -360, -360, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 360, 360, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 720, 720, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 720, 720, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 20, double.NaN, new Matrix(1.18199, double.NaN, 1.11396, double.NaN, 0, double.NaN), false, true, double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, 30, new Matrix(double.NaN, 1.07735, double.NaN, 1.43301, double.NaN, 0), false, true, double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), false, true, double.NaN }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -90, -180, new Matrix(1, 0, -16331239353195370, 1, -48993718059586110, 3), false, true, 3 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -90, 180, new Matrix(1, -0, -16331239353195370, 1, -48993718059586110, 3), false, true, -1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 90, -180, new Matrix(1, 0, 16331239353195370, 1, 48993718059586110, 3), false, true, -1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -90, 0, new Matrix(1, 0, -16331239353195370, 1, -48993718059586110, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, -180, new Matrix(1, 0, 0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 0, new Matrix(1, 0, 0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 90, 180, new Matrix(1, -0, 16331239353195370, 1, 48993718059586110, 3), false, true, 3 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 90, 0, new Matrix(1, 0, 16331239353195370, 1, 48993718059586110, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 180, 0, new Matrix(1, 0, -0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 180, new Matrix(1, -0, 0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 180, 180, new Matrix(1, -0, -0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 270, 270, new Matrix(1, 5443746451065123, 5443746451065123, 1, 16331239353195370, 10887492902130248), false, true, -2.963437542348412E+31 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 180, 270, new Matrix(1, 5443746451065123, -0, 1, 2, 10887492902130248), false, true, 1.66667 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 270, 270, new Matrix(1, 5443746451065123, 5443746451065123, 1, 16331239353195370, 10887492902130248), false, true, -2.963437542348412E+31 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -20, -30, new Matrix(1, -0.57735, -0.36397, 1, 0.90809, 1.8453), false, true, 0.78986 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 20, 30, new Matrix(1, 0.57735, 0.36397, 1, 3.09191, 4.1547), false, true, 0.78986 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -380, -390, new Matrix(1, -0.57735, -0.36397, 1, 0.90809, 1.8453), false, true, 0.78986 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 380, 390, new Matrix(1, 0.57735, 0.36397, 1, 3.09191, 4.1547), false, true, 0.78986 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -740, -750, new Matrix(1, -0.57735, -0.36397, 1, 0.90809, 1.8453), false, true, 0.78986 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 740, 750, new Matrix(1, 0.57735, 0.36397, 1, 3.09191, 4.1547), false, true, 0.78986 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 35, 0, new Matrix(1, 0, 0.70021, 1, 4.10062, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 35, new Matrix(1, 0.70021, 0, 1, 2, 4.40042), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -720, -720, new Matrix(1, -0, -0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -360, -360, new Matrix(1, -0, -0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 360, 360, new Matrix(1, 0, 0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 720, 720, new Matrix(1, 0, 0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 720, 720, new Matrix(1, 0, 0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 20, double.NaN, new Matrix(1, double.NaN, 0.36397, 1, 3.09191, double.NaN), false, true, double.NaN }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, 30, new Matrix(1, 0.57735, double.NaN, 1, double.NaN, 4.1547), false, true, double.NaN }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, double.NaN, new Matrix(1, double.NaN, double.NaN, 1, double.NaN, double.NaN), false, true, double.NaN }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -90, -180, new Matrix(2, 0, -48993718059586110, 3, -32662478706390740, 2), false, true, 18 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -90, 180, new Matrix(2, -0, -48993718059586110, 3, -32662478706390740, 2), false, true, -6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 90, -180, new Matrix(2, 0, 48993718059586110, 3, 32662478706390740, 2), false, true, -6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -90, 0, new Matrix(2, 0, -48993718059586110, 3, -32662478706390740, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, -180, new Matrix(2, 0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 0, new Matrix(2, 0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 90, 180, new Matrix(2, -0, 48993718059586110, 3, 32662478706390740, 2), false, true, 18 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 90, 0, new Matrix(2, 0, 48993718059586110, 3, 32662478706390740, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 180, 0, new Matrix(2, 0, -0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 180, new Matrix(2, -0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 180, 180, new Matrix(2, -0, -0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 270, 270, new Matrix(2, 10887492902130246, 16331239353195366, 3, 10887492902130248, 5443746451065125), false, true, -1.7780625254090473E+32 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 180, 270, new Matrix(2, 10887492902130246, -0, 3, 1, 5443746451065125), false, true, 10 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 270, 270, new Matrix(2, 10887492902130246, 16331239353195366, 3, 10887492902130248, 5443746451065125), false, true, -1.7780625254090473E+32 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -20, -30, new Matrix(2, -1.1547, -1.09191, 3, 0.27206, 1.42265), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 20, 30, new Matrix(2, 1.1547, 1.09191, 3, 1.72794, 2.57735), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -380, -390, new Matrix(2, -1.1547, -1.09191, 3, 0.27206, 1.42265), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 380, 390, new Matrix(2, 1.1547, 1.09191, 3, 1.72794, 2.57735), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -740, -750, new Matrix(2, -1.1547, -1.09191, 3, 0.27206, 1.42265), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 740, 750, new Matrix(2, 1.1547, 1.09191, 3, 1.72794, 2.57735), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 35, 0, new Matrix(2, 0, 2.10062, 3, 2.40042, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 35, new Matrix(2, 1.40042, 0, 3, 1, 2.70021), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -720, -720, new Matrix(2, 0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -360, -360, new Matrix(2, 0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 360, 360, new Matrix(2, 0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 720, 720, new Matrix(2, 0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 720, 720, new Matrix(2, 0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 20, double.NaN, new Matrix(2, double.NaN, 1.09191, double.NaN, 1.72794, double.NaN), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, 30, new Matrix(double.NaN, 1.1547, double.NaN, 3, double.NaN, 2.57735), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), false, true, double.NaN }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -90, -180, new Matrix(-8165619676597683, 0.5, -16331239353195370, 1, -48993718059586110, 3), false, true, 3 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -90, 180, new Matrix(-8165619676597683, 0.5, -16331239353195370, 1, -48993718059586110, 3), false, false, 0 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 90, -180, new Matrix(8165619676597686, 0.5, 16331239353195370, 1, 48993718059586110, 3), false, true, -1 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -90, 0, new Matrix(-8165619676597683, 0.5, -16331239353195370, 1, -48993718059586110, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, -180, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 90, 180, new Matrix(8165619676597686, 0.5, 16331239353195370, 1, 48993718059586110, 3), false, true, 2 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 90, 0, new Matrix(8165619676597686, 0.5, 16331239353195370, 1, 48993718059586110, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 180, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 180, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 180, 180, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 270, 270, new Matrix(2721873225532562.5, 5443746451065124, 5443746451065124, 4082809838298843, 16331239353195370, 10887492902130248), false, true, -1.8521484639677582E+31 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 180, 270, new Matrix(1, 5443746451065124, 0.75, 4082809838298843, 2, 10887492902130248), false, false, 0 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 270, 270, new Matrix(2721873225532562.5, 5443746451065124, 5443746451065124, 4082809838298843, 16331239353195370, 10887492902130248), false, true, -1.8521484639677582E+31 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -20, -30, new Matrix(0.81801, -0.07735, 0.38603, 0.56699, 0.90809, 1.8453), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 20, 30, new Matrix(1.18199, 1.07735, 1.11396, 1.43301, 3.09191, 4.1547), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -380, -390, new Matrix(0.81801, -0.07735, 0.38603, 0.56699, 0.90809, 1.8453), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 380, 390, new Matrix(1.18199, 1.07735, 1.11396, 1.43301, 3.09191, 4.1547), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -740, -750, new Matrix(0.81801, -0.07735, 0.38603, 0.56699, 0.90809, 1.8453), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 740, 750, new Matrix(1.18199, 1.07735, 1.11396, 1.43301, 3.09191, 4.1547), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 35, 0, new Matrix(1.3501, 0.5, 1.45021, 1, 4.10062, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 35, new Matrix(1, 1.20021, 0.75, 1.52516, 2, 4.40042), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -720, -720, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -360, -360, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 360, 360, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 720, 720, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 720, 720, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 20, double.NaN, new Matrix(1.18199, double.NaN, 1.11396, double.NaN, 3.09191, double.NaN), false, true, double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, 30, new Matrix(double.NaN, 1.07735, double.NaN, 1.43301, double.NaN, 4.1547), false, true, double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), false, true, double.NaN }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -90, -180, new Matrix(-8165619676597683, 0.5, -48993718059586110, 3, 0, 0), false, true, 20 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -90, 180, new Matrix(-8165619676597683, 0.5, -48993718059586110, 3, 0, 0), false, true, -4 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 90, -180, new Matrix(8165619676597687, 0.5, 48993718059586110, 3, 0, 0), false, true, -8 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -90, 0, new Matrix(-8165619676597683, 0.5, -48993718059586110, 3, 0, 0), false, true, 8 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, -180, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 90, 180, new Matrix(8165619676597687, 0.5, 48993718059586110, 3, 0, 0), false, true, 16 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 90, 0, new Matrix(8165619676597687, 0.5, 48993718059586110, 3, 0, 0), false, true, 4 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 180, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 180, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 180, 180, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 270, 270, new Matrix(2721873225532563.5, 10887492902130246, 16331239353195366, 4082809838298845, 0, 0), false, true, -1.6669336175709816E+32 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 180, 270, new Matrix(2, 10887492902130246, 0.75, 4082809838298845, 0, 0), false, true, 9 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 270, 270, new Matrix(2721873225532563.5, 10887492902130246, 16331239353195366, 4082809838298845, 0, 0), false, true, -1.6669336175709816E+32 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -20, -30, new Matrix(1.81801, -0.65469, -0.34191, 2.56699, 0, 0), false, true, 4.44297 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 20, 30, new Matrix(2.18199, 1.6547, 1.84191, 3.43301, 0, 0), false, true, 4.44297 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -380, -390, new Matrix(1.81801, -0.65469, -0.34191, 2.56699, 0, 0), false, true, 4.44297 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 380, 390, new Matrix(2.18199, 1.6547, 1.84191, 3.43301, 0, 0), false, true, 4.44297 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -740, -750, new Matrix(1.81801, -0.65469, -0.34191, 2.56699, 0, 0), false, true, 4.44297 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 740, 750, new Matrix(2.18199, 1.6547, 1.84191, 3.43301, 0, 0), false, true, 4.44297 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 35, 0, new Matrix(2.3501, 0.5, 2.85062, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 35, new Matrix(2, 1.90042, 0.75, 3.52516, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -720, -720, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -360, -360, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 360, 360, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 720, 720, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 720, 720, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 20, double.NaN, new Matrix(2.18199, double.NaN, 1.84191, double.NaN, 0, double.NaN), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, 30, new Matrix(double.NaN, 1.6547, double.NaN, 3.43301, double.NaN, 0), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), false, true, double.NaN }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -90, -180, new Matrix(-48993718059586110, 3, -81656196765976850, 5, -1.1431867547236758E+17, 7), false, false, 0 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -90, 180, new Matrix(-48993718059586110, 3, -81656196765976850, 5, -1.1431867547236758E+17, 7), false, true, -32 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 90, -180, new Matrix(48993718059586110, 3, 81656196765976850, 5, 1.1431867547236758E+17, 7), false, false, 0 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -90, 0, new Matrix(-48993718059586110, 3, -81656196765976850, 5, -1.1431867547236758E+17, 7), false, false, 0 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, -180, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 0, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 90, 180, new Matrix(48993718059586110, 3, 81656196765976850, 5, 1.1431867547236758E+17, 7), false, true, 32 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 90, 0, new Matrix(48993718059586110, 3, 81656196765976850, 5, 1.1431867547236758E+17, 7), false, false, 0 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 180, 0, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 180, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 180, 180, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 270, 270, new Matrix(16331239353195370, 10887492902130248, 27218732255325624, 21774985804260496, 38106225157455870, 32662478706390744), false, true, 5.926875084696823E+31 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 180, 270, new Matrix(2, 10887492902130248, 4, 21774985804260496, 6, 32662478706390744), false, false, 0 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 270, 270, new Matrix(16331239353195370, 10887492902130248, 27218732255325624, 21774985804260496, 38106225157455870, 32662478706390744), false, true, 5.926875084696823E+31 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -20, -30, new Matrix(0.90809, 1.8453, 2.18014, 2.6906, 3.45221, 3.5359), false, true, -1.57972 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 20, 30, new Matrix(3.09191, 4.1547, 5.81985, 7.3094, 8.54779, 10.4641), false, true, -1.57972 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -380, -390, new Matrix(0.90809, 1.8453, 2.18014, 2.6906, 3.45221, 3.5359), false, true, -1.57972 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 380, 390, new Matrix(3.09191, 4.1547, 5.81985, 7.3094, 8.54779, 10.4641), false, true, -1.57972 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -740, -750, new Matrix(0.90809, 1.8453, 2.18014, 2.6906, 3.45221, 3.5359), false, true, -1.57972 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 740, 750, new Matrix(3.09191, 4.1547, 5.81985, 7.3094, 8.54779, 10.4641), false, true, -1.57972 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 35, 0, new Matrix(4.10062, 3, 7.50104, 5, 10.90145, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 35, new Matrix(2, 4.40042, 4, 7.80083, 6, 11.20125), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -720, -720, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -360, -360, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 360, 360, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 720, 720, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 720, 720, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 20, double.NaN, new Matrix(3.09191, double.NaN, 5.81985, double.NaN, 8.54779, double.NaN), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, 30, new Matrix(double.NaN, 4.1547, double.NaN, 7.3094, double.NaN, 10.4641), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), false, true, double.NaN }; + + // No inverse. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -90, -180, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -90, 180, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 90, -180, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -90, 0, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, -180, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 0, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 90, 180, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 90, 0, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 180, 0, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 180, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 180, 180, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 270, 270, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 180, 270, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 270, 270, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -20, -30, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 20, 30, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -380, -390, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 380, 390, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -740, -750, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 740, 750, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 35, 0, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 35, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -720, -720, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -360, -360, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 360, 360, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 720, 720, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 720, 720, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 20, double.NaN, new Matrix(0, double.NaN, 0, double.NaN, 0, double.NaN), false, true, double.NaN }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, 30, new Matrix(double.NaN, 0, double.NaN, 0, double.NaN, 0), false, true, double.NaN }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, double.NaN, double.NaN), false, true, double.NaN }; + + } + + [Theory] + [MemberData(nameof(Skew_TestData))] + public void Skew_Invoke_Success(Matrix matrix, double skewX, double skewY, Matrix expected, bool expectedIsIdentity, bool expectedHasInverse, double expectedDeterminant) + { + matrix.Skew(skewX, skewY); + Helpers.AssertEqualRounded(expected, matrix); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + Assert.Equal(expectedHasInverse, matrix.HasInverse); + Assert.Equal(expectedDeterminant, matrix.Determinant, precision: 5); + } + + public static IEnumerable SkewPrepend_TestData() + { + // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, -90, -180, new Matrix(1, 0, -16331239353195370, 1, 0, 0), false, true, 3 }; + yield return new object[] { matrix, -90, 180, new Matrix(1, -0, -16331239353195370, 1, 0, 0), false, true, -1 }; + yield return new object[] { matrix, 90, -180, new Matrix(1, 0, 16331239353195370, 1, 0, 0), false, true, -1 }; + yield return new object[] { matrix, -90, 0, new Matrix(1, 0, -16331239353195370, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, 0, -180, new Matrix(1, 0, 0, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, 0, 0, new Matrix(1, 0, 0, 1, 0, 0), true, true, 1 }; + yield return new object[] { matrix, 90, 180, new Matrix(1, -0, 16331239353195370, 1, 0, 0), false, true, 3 }; + yield return new object[] { matrix, 90, 0, new Matrix(1, 0, 16331239353195370, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, 180, 0, new Matrix(1, 0, -0, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, 0, 180, new Matrix(1, -0, 0, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, 180, 180, new Matrix(1, -0, -0, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, 270, 270, new Matrix(1, 5443746451065123, 5443746451065123, 1, 0, 0), false, true, -2.963437542348412E+31 }; + yield return new object[] { matrix, 180, 270, new Matrix(1, 5443746451065123, -0, 1, 0, 0), false, true, 1.66667 }; + yield return new object[] { matrix, 270, 270, new Matrix(1, 5443746451065123, 5443746451065123, 1, 0, 0), false, true, -2.963437542348412E+31 }; + yield return new object[] { matrix, -20, -30, new Matrix(1, -0.57735, -0.36397, 1, 0, 0), false, true, 0.78986 }; + yield return new object[] { matrix, 20, 30, new Matrix(1, 0.57735, 0.36397, 1, 0, 0), false, true, 0.78986 }; + yield return new object[] { matrix, -380, -390, new Matrix(1, -0.57735, -0.36397, 1, 0, 0), false, true, 0.78986 }; + yield return new object[] { matrix, 380, 390, new Matrix(1, 0.57735, 0.36397, 1, 0, 0), false, true, 0.78986 }; + yield return new object[] { matrix, -740, -750, new Matrix(1, -0.57735, -0.36397, 1, 0, 0), false, true, 0.78986 }; + yield return new object[] { matrix, 740, 750, new Matrix(1, 0.57735, 0.36397, 1, 0, 0), false, true, 0.78986 }; + yield return new object[] { matrix, 35, 0, new Matrix(1, 0, 0.70021, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, 0, 35, new Matrix(1, 0.70021, 0, 1, 0, 0), false, true, 1 }; + yield return new object[] { matrix, -720, -720, new Matrix(1, -0, -0, 1, 0, 0), true, true, 1 }; + yield return new object[] { matrix, -360, -360, new Matrix(1, -0, -0, 1, 0, 0), true, true, 1 }; + yield return new object[] { matrix, 360, 360, new Matrix(1, 0, 0, 1, 0, 0), true, true, 1 }; + yield return new object[] { matrix, 720, 720, new Matrix(1, 0, 0, 1, 0, 0), true, true, 1 }; + yield return new object[] { matrix, 720, 720, new Matrix(1, 0, 0, 1, 0, 0), true, true, 1 }; + yield return new object[] { matrix, 20, double.NaN, new Matrix(1, double.NaN, 0.36397, 1, 0, 0), false, true, double.NaN }; + yield return new object[] { matrix, double.NaN, 30, new Matrix(1, 0.57735, double.NaN, 1, 0, 0), false, true, double.NaN }; + yield return new object[] { matrix, double.NaN, double.NaN, new Matrix(1, double.NaN, double.NaN, 1, 0, 0), false, true, double.NaN }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -90, -180, new Matrix(2, 0, -32662478706390740, 3, 0, 0), false, true, 18 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -90, 180, new Matrix(2, -0, -32662478706390740, 3, 0, 0), false, true, -6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 90, -180, new Matrix(2, 0, 32662478706390740, 3, 0, 0), false, true, -6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -90, 0, new Matrix(2, 0, -32662478706390740, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, -180, new Matrix(2, 0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 0, new Matrix(2, 0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 90, 180, new Matrix(2, -0, 32662478706390740, 3, 0, 0), false, true, 18 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 90, 0, new Matrix(2, 0, 32662478706390740, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 180, 0, new Matrix(2, 0, -0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 180, new Matrix(2, -0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 180, 180, new Matrix(2, -0, -0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 270, 270, new Matrix(2, 16331239353195366, 10887492902130246, 3, 0, 0), false, true, -1.7780625254090473E+32 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 180, 270, new Matrix(2, 16331239353195366, -0, 3, 0, 0), false, true, 10 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 270, 270, new Matrix(2, 16331239353195366, 10887492902130246, 3, 0, 0), false, true, -1.7780625254090473E+32 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -20, -30, new Matrix(2, -1.73205, -0.72794, 3, 0, 0), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 20, 30, new Matrix(2, 1.73205, 0.72794, 3, 0, 0), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -380, -390, new Matrix(2, -1.73205, -0.72794, 3, 0, 0), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 380, 390, new Matrix(2, 1.73205, 0.72794, 3, 0, 0), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -740, -750, new Matrix(2, -1.73205, -0.72794, 3, 0, 0), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 740, 750, new Matrix(2, 1.73205, 0.72794, 3, 0, 0), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 35, 0, new Matrix(2, 0, 1.40042, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 35, new Matrix(2, 2.10062, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -720, -720, new Matrix(2, 0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -360, -360, new Matrix(2, 0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 360, 360, new Matrix(2, 0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 720, 720, new Matrix(2, 0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 720, 720, new Matrix(2, 0, 0, 3, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 20, double.NaN, new Matrix(double.NaN, double.NaN, 0.72794, 3, 0, 0), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, 30, new Matrix(2, 1.73205, double.NaN, double.NaN, 0, 0), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, 0, 0), false, true, double.NaN }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -90, -180, new Matrix(1, 0.5, -16331239353195370, -8165619676597683, 0, 0), false, true, 3 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -90, 180, new Matrix(1, 0.5, -16331239353195370, -8165619676597683, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 90, -180, new Matrix(1, 0.5, 16331239353195370, 8165619676597686, 0, 0), false, true, -1 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -90, 0, new Matrix(1, 0.5, -16331239353195370, -8165619676597683, 0, 0), false, true, 1 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, -180, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 90, 180, new Matrix(1, 0.5, 16331239353195370, 8165619676597686, 0, 0), false, true, 2 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 90, 0, new Matrix(1, 0.5, 16331239353195370, 8165619676597686, 0, 0), false, true, 1 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 180, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 180, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 180, 180, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 270, 270, new Matrix(4082809838298843, 5443746451065124, 5443746451065124, 2721873225532562.5, 0, 0), false, true, -1.8521484639677582E+31 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 180, 270, new Matrix(4082809838298843, 5443746451065124, 0.75, 1, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 270, 270, new Matrix(4082809838298843, 5443746451065124, 5443746451065124, 2721873225532562.5, 0, 0), false, true, -1.8521484639677582E+31 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -20, -30, new Matrix(0.56699, -0.07735, 0.38603, 0.81801, 0, 0), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 20, 30, new Matrix(1.43301, 1.07735, 1.11396, 1.18199, 0, 0), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -380, -390, new Matrix(0.56699, -0.07735, 0.38603, 0.81801, 0, 0), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 380, 390, new Matrix(1.43301, 1.07735, 1.11396, 1.18199, 0, 0), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -740, -750, new Matrix(0.56699, -0.07735, 0.38603, 0.81801, 0, 0), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 740, 750, new Matrix(1.43301, 1.07735, 1.11396, 1.18199, 0, 0), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 35, 0, new Matrix(1, 0.5, 1.45021, 1.3501, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 35, new Matrix(1.52516, 1.20021, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -720, -720, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -360, -360, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 360, 360, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 720, 720, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 720, 720, new Matrix(1, 0.5, 0.75, 1, 0, 0), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 20, double.NaN, new Matrix(double.NaN, double.NaN, 1.11396, 1.18199, 0, 0), false, true, double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, 30, new Matrix(1.43301, 1.07735, double.NaN, double.NaN, 0, 0), false, true, double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, 0, 0), false, true, double.NaN }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -90, -180, new Matrix(1, 0, -16331239353195370, 1, 2, 3), false, true, 3 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -90, 180, new Matrix(1, -0, -16331239353195370, 1, 2, 3), false, true, -1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 90, -180, new Matrix(1, 0, 16331239353195370, 1, 2, 3), false, true, -1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -90, 0, new Matrix(1, 0, -16331239353195370, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, -180, new Matrix(1, 0, 0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 0, new Matrix(1, 0, 0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 90, 180, new Matrix(1, -0, 16331239353195370, 1, 2, 3), false, true, 3 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 90, 0, new Matrix(1, 0, 16331239353195370, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 180, 0, new Matrix(1, 0, -0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 180, new Matrix(1, -0, 0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 180, 180, new Matrix(1, -0, -0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 270, 270, new Matrix(1, 5443746451065123, 5443746451065123, 1, 2, 3), false, true, -2.963437542348412E+31 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 180, 270, new Matrix(1, 5443746451065123, -0, 1, 2, 3), false, true, 1.66667 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 270, 270, new Matrix(1, 5443746451065123, 5443746451065123, 1, 2, 3), false, true, -2.963437542348412E+31 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -20, -30, new Matrix(1, -0.57735, -0.36397, 1, 2, 3), false, true, 0.78986 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 20, 30, new Matrix(1, 0.57735, 0.36397, 1, 2, 3), false, true, 0.78986 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -380, -390, new Matrix(1, -0.57735, -0.36397, 1, 2, 3), false, true, 0.78986 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 380, 390, new Matrix(1, 0.57735, 0.36397, 1, 2, 3), false, true, 0.78986 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -740, -750, new Matrix(1, -0.57735, -0.36397, 1, 2, 3), false, true, 0.78986 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 740, 750, new Matrix(1, 0.57735, 0.36397, 1, 2, 3), false, true, 0.78986 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 35, 0, new Matrix(1, 0, 0.70021, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 35, new Matrix(1, 0.70021, 0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -720, -720, new Matrix(1, -0, -0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -360, -360, new Matrix(1, -0, -0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 360, 360, new Matrix(1, 0, 0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 720, 720, new Matrix(1, 0, 0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 720, 720, new Matrix(1, 0, 0, 1, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 20, double.NaN, new Matrix(1, double.NaN, 0.36397, 1, 2, 3), false, true, double.NaN }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, 30, new Matrix(1, 0.57735, double.NaN, 1, 2, 3), false, true, double.NaN }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, double.NaN, new Matrix(1, double.NaN, double.NaN, 1, 2, 3), false, true, double.NaN }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -90, -180, new Matrix(2, 0, -32662478706390740, 3, 1, 2), false, true, 18 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -90, 180, new Matrix(2, -0, -32662478706390740, 3, 1, 2), false, true, -6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 90, -180, new Matrix(2, 0, 32662478706390740, 3, 1, 2), false, true, -6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -90, 0, new Matrix(2, 0, -32662478706390740, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, -180, new Matrix(2, 0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 0, new Matrix(2, 0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 90, 180, new Matrix(2, -0, 32662478706390740, 3, 1, 2), false, true, 18 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 90, 0, new Matrix(2, 0, 32662478706390740, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 180, 0, new Matrix(2, 0, -0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 180, new Matrix(2, -0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 180, 180, new Matrix(2, -0, -0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 270, 270, new Matrix(2, 16331239353195366, 10887492902130246, 3, 1, 2), false, true, -1.7780625254090473E+32 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 180, 270, new Matrix(2, 16331239353195366, -0, 3, 1, 2), false, true, 10 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 270, 270, new Matrix(2, 16331239353195366, 10887492902130246, 3, 1, 2), false, true, -1.7780625254090473E+32 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -20, -30, new Matrix(2, -1.73205, -0.72794, 3, 1, 2), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 20, 30, new Matrix(2, 1.73205, 0.72794, 3, 1, 2), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -380, -390, new Matrix(2, -1.73205, -0.72794, 3, 1, 2), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 380, 390, new Matrix(2, 1.73205, 0.72794, 3, 1, 2), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -740, -750, new Matrix(2, -1.73205, -0.72794, 3, 1, 2), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 740, 750, new Matrix(2, 1.73205, 0.72794, 3, 1, 2), false, true, 4.73917 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 35, 0, new Matrix(2, 0, 1.40042, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 35, new Matrix(2, 2.10062, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -720, -720, new Matrix(2, 0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -360, -360, new Matrix(2, 0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 360, 360, new Matrix(2, 0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 720, 720, new Matrix(2, 0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 720, 720, new Matrix(2, 0, 0, 3, 1, 2), false, true, 6 }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 20, double.NaN, new Matrix(double.NaN, double.NaN, 0.72794, 3, 1, 2), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, 30, new Matrix(2, 1.73205, double.NaN, double.NaN, 1, 2), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, 1, 2), false, true, double.NaN }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -90, -180, new Matrix(1, 0.5, -16331239353195370, -8165619676597683, 2, 3), false, true, 3 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -90, 180, new Matrix(1, 0.5, -16331239353195370, -8165619676597683, 2, 3), false, false, 0 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 90, -180, new Matrix(1, 0.5, 16331239353195370, 8165619676597686, 2, 3), false, true, -1 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -90, 0, new Matrix(1, 0.5, -16331239353195370, -8165619676597683, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, -180, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 90, 180, new Matrix(1, 0.5, 16331239353195370, 8165619676597686, 2, 3), false, true, 2 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 90, 0, new Matrix(1, 0.5, 16331239353195370, 8165619676597686, 2, 3), false, true, 1 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 180, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 180, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 180, 180, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 270, 270, new Matrix(4082809838298843, 5443746451065124, 5443746451065124, 2721873225532562.5, 2, 3), false, true, -1.8521484639677582E+31 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 180, 270, new Matrix(4082809838298843, 5443746451065124, 0.75, 1, 2, 3), false, false, 0 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 270, 270, new Matrix(4082809838298843, 5443746451065124, 5443746451065124, 2721873225532562.5, 2, 3), false, true, -1.8521484639677582E+31 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -20, -30, new Matrix(0.56699, -0.07735, 0.38603, 0.81801, 2, 3), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 20, 30, new Matrix(1.43301, 1.07735, 1.11396, 1.18199, 2, 3), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -380, -390, new Matrix(0.56699, -0.07735, 0.38603, 0.81801, 2, 3), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 380, 390, new Matrix(1.43301, 1.07735, 1.11396, 1.18199, 2, 3), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -740, -750, new Matrix(0.56699, -0.07735, 0.38603, 0.81801, 2, 3), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 740, 750, new Matrix(1.43301, 1.07735, 1.11396, 1.18199, 2, 3), false, true, 0.49366 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 35, 0, new Matrix(1, 0.5, 1.45021, 1.3501, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 35, new Matrix(1.52516, 1.20021, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -720, -720, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -360, -360, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 360, 360, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 720, 720, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 720, 720, new Matrix(1, 0.5, 0.75, 1, 2, 3), false, true, 0.625 }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 20, double.NaN, new Matrix(double.NaN, double.NaN, 1.11396, 1.18199, 2, 3), false, true, double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, 30, new Matrix(1.43301, 1.07735, double.NaN, double.NaN, 2, 3), false, true, double.NaN }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, 2, 3), false, true, double.NaN }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -90, -180, new Matrix(2, 0.5, -32662478706390740, -8165619676597682, 0, 0), false, true, 16 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -90, 180, new Matrix(2, 0.5, -32662478706390740, -8165619676597682, 0, 0), false, true, -6 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 90, -180, new Matrix(2, 0.5, 32662478706390740, 8165619676597687, 0, 0), false, true, -4 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -90, 0, new Matrix(2, 0.5, -32662478706390740, -8165619676597682, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, -180, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 90, 180, new Matrix(2, 0.5, 32662478706390740, 8165619676597687, 0, 0), false, true, 18 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 90, 0, new Matrix(2, 0.5, 32662478706390740, 8165619676597687, 0, 0), false, true, 6 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 180, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 180, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 180, 180, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 270, 270, new Matrix(4082809838298843.5, 16331239353195366, 10887492902130246, 2721873225532564.5, 0, 0), false, true, -1.6669336175709816E+32 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 180, 270, new Matrix(4082809838298843.5, 16331239353195366, 0.75, 3, 0, 0), false, true, 10 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 270, 270, new Matrix(4082809838298843.5, 16331239353195366, 10887492902130246, 2721873225532564.5, 0, 0), false, true, -1.6669336175709816E+32 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -20, -30, new Matrix(1.56699, -1.23205, 0.02206, 2.81801, 0, 0), false, true, 4.44297 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 20, 30, new Matrix(2.43301, 2.23205, 1.47794, 3.18199, 0, 0), false, true, 4.44297 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -380, -390, new Matrix(1.56699, -1.23205, 0.02206, 2.81801, 0, 0), false, true, 4.44297 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 380, 390, new Matrix(2.43301, 2.23205, 1.47794, 3.18199, 0, 0), false, true, 4.44297 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -740, -750, new Matrix(1.56699, -1.23205, 0.02206, 2.81801, 0, 0), false, true, 4.44297 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 740, 750, new Matrix(2.43301, 2.23205, 1.47794, 3.18199, 0, 0), false, true, 4.44297 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 35, 0, new Matrix(2, 0.5, 2.15042, 3.3501, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 35, new Matrix(2.52516, 2.60062, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -720, -720, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -360, -360, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 360, 360, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 720, 720, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 720, 720, new Matrix(2, 0.5, 0.75, 3, 0, 0), false, true, 5.625 }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 20, double.NaN, new Matrix(double.NaN, double.NaN, 1.47794, 3.18199, 0, 0), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, 30, new Matrix(2.43301, 2.23205, double.NaN, double.NaN, 0, 0), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, 0, 0), false, true, double.NaN }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -90, -180, new Matrix(2, 3, -32662478706390732, -48993718059586104, 6, 7), false, false, 0 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -90, 180, new Matrix(2, 3, -32662478706390732, -48993718059586104, 6, 7), false, false, 0 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 90, -180, new Matrix(2, 3, 32662478706390744, 48993718059586120, 6, 7), false, true, 16 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -90, 0, new Matrix(2, 3, -32662478706390732, -48993718059586104, 6, 7), false, false, 0 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, -180, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 0, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 90, 180, new Matrix(2, 3, 32662478706390744, 48993718059586120, 6, 7), false, false, 0 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 90, 0, new Matrix(2, 3, 32662478706390744, 48993718059586120, 6, 7), false, true, 16 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 180, 0, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 180, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 180, 180, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 270, 270, new Matrix(21774985804260496, 27218732255325624, 10887492902130250, 16331239353195372, 6, 7), false, true, 5.926875084696823E+31 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 180, 270, new Matrix(21774985804260496, 27218732255325624, 4, 5, 6, 7), false, true, 16 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 270, 270, new Matrix(21774985804260496, 27218732255325624, 10887492902130250, 16331239353195372, 6, 7), false, true, 5.926875084696823E+31 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -20, -30, new Matrix(-0.3094, 0.11325, 3.27206, 3.90809, 6, 7), false, true, -1.57972 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 20, 30, new Matrix(4.3094, 5.88675, 4.72794, 6.09191, 6, 7), false, true, -1.57972 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -380, -390, new Matrix(-0.3094, 0.11325, 3.27206, 3.90809, 6, 7), false, true, -1.57972 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 380, 390, new Matrix(4.3094, 5.88675, 4.72794, 6.09191, 6, 7), false, true, -1.57972 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -740, -750, new Matrix(-0.3094, 0.11325, 3.27206, 3.90809, 6, 7), false, true, -1.57972 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 740, 750, new Matrix(4.3094, 5.88675, 4.72794, 6.09191, 6, 7), false, true, -1.57972 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 35, 0, new Matrix(2, 3, 5.40042, 7.10062, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 35, new Matrix(4.80083, 6.50104, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -720, -720, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -360, -360, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 360, 360, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 720, 720, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 720, 720, new Matrix(2, 3, 4, 5, 6, 7), false, true, -2 }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 20, double.NaN, new Matrix(double.NaN, double.NaN, 4.72794, 6.09191, 6, 7), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, 30, new Matrix(4.3094, 5.88675, double.NaN, double.NaN, 6, 7), false, true, double.NaN }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, 6, 7), false, true, double.NaN }; + + // No inverse. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -90, -180, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -90, 180, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 90, -180, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -90, 0, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, -180, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 0, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 90, 180, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 90, 0, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 180, 0, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 180, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 180, 180, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 270, 270, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 180, 270, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 270, 270, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -20, -30, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 20, 30, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -380, -390, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 380, 390, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -740, -750, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 740, 750, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 35, 0, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 35, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -720, -720, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -360, -360, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 360, 360, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 720, 720, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 720, 720, new Matrix(0, 0, 0, 0, 0, 0), false, false, 0 }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 20, double.NaN, new Matrix(double.NaN, double.NaN, 0, 0, 0, 0), false, true, double.NaN }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, 30, new Matrix(0, 0, double.NaN, double.NaN, 0, 0), false, true, double.NaN }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, double.NaN, new Matrix(double.NaN, double.NaN, double.NaN, double.NaN, 0, 0), false, true, double.NaN }; + } + + [Theory] + [MemberData(nameof(SkewPrepend_TestData))] + public void SkewPrepend_Invoke_Success(Matrix matrix, double skewX, double skewY, Matrix expected, bool expectedIsIdentity, bool expectedHasInverse, double expectedDeterminant) + { + matrix.SkewPrepend(skewX, skewY); + Helpers.AssertEqualRounded(expected, matrix); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + Assert.Equal(expectedHasInverse, matrix.HasInverse); + Assert.Equal(expectedDeterminant, matrix.Determinant, precision: 5); + } + + public static IEnumerable ToString_TestData() + { + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, "Identity" }; + } + + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), "0,0,0,0,0,0" }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), "2,3,4,5,6,7" }; + yield return new object[] { new Matrix(2.2, 3.3, 4.4, 5.5, 6.6, 7.7), "2.2,3.3,4.4,5.5,6.6,7.7" }; + yield return new object[] { new Matrix(-1, -2, -3, -4, -5, -6), "-1,-2,-3,-4,-5,-6" }; + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_Invoke_ReturnsExpected(Matrix matrix, string expected) + { + Assert.Equal(expected, matrix.ToString()); + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_InvokeIFormatProviderInvariantCulture_ReturnsExpected(Matrix matrix, string expected) + { + Assert.Equal(expected, matrix.ToString(CultureInfo.InvariantCulture)); + } + + public static IEnumerable ToString_IFormatProviderCustom_TestData() + { + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, "|", "Identity" }; + yield return new object[] { matrix, "|_", "Identity" }; + yield return new object[] { matrix, ",_", "Identity" }; + yield return new object[] { matrix, ",", "Identity" }; + yield return new object[] { matrix, ";", "Identity" }; + yield return new object[] { matrix, " ", "Identity" }; + } + + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), "|", "0,0,0,0,0,0" }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), "|_", "0,0,0,0,0,0" }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), ",_", "0;0;0;0;0;0" }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), ",", "0;0;0;0;0;0" }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), ";", "0,0,0,0,0,0" }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), " ", "0,0,0,0,0,0" }; + + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), "|", "2,3,4,5,6,7" }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), "|_", "2,3,4,5,6,7" }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), ",_", "2;3;4;5;6;7" }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), ",", "2;3;4;5;6;7" }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), ";", "2,3,4,5,6,7" }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), " ", "2,3,4,5,6,7" }; + + yield return new object[] { new Matrix(2.2, 3.3, 4.4, 5.5, 6.6, 7.7), "|", "2|2,3|3,4|4,5|5,6|6,7|7" }; + yield return new object[] { new Matrix(2.2, 3.3, 4.4, 5.5, 6.6, 7.7), "|_", "2|_2,3|_3,4|_4,5|_5,6|_6,7|_7" }; + yield return new object[] { new Matrix(2.2, 3.3, 4.4, 5.5, 6.6, 7.7), ",_", "2,_2;3,_3;4,_4;5,_5;6,_6;7,_7" }; + yield return new object[] { new Matrix(2.2, 3.3, 4.4, 5.5, 6.6, 7.7), ",", "2,2;3,3;4,4;5,5;6,6;7,7" }; + yield return new object[] { new Matrix(2.2, 3.3, 4.4, 5.5, 6.6, 7.7), ";", "2;2,3;3,4;4,5;5,6;6,7;7" }; + yield return new object[] { new Matrix(2.2, 3.3, 4.4, 5.5, 6.6, 7.7), " ", "2 2,3 3,4 4,5 5,6 6,7 7" }; + + yield return new object[] { new Matrix(-1, -2, -3, -4, -5, -6), "|", "-1,-2,-3,-4,-5,-6" }; + yield return new object[] { new Matrix(-1, -2, -3, -4, -5, -6), "|_", "-1,-2,-3,-4,-5,-6" }; + yield return new object[] { new Matrix(-1, -2, -3, -4, -5, -6), ",_", "-1;-2;-3;-4;-5;-6" }; + yield return new object[] { new Matrix(-1, -2, -3, -4, -5, -6), ",", "-1;-2;-3;-4;-5;-6" }; + yield return new object[] { new Matrix(-1, -2, -3, -4, -5, -6), ";", "-1,-2,-3,-4,-5,-6" }; + yield return new object[] { new Matrix(-1, -2, -3, -4, -5, -6), " ", "-1,-2,-3,-4,-5,-6" }; + } + + [Theory] + [MemberData(nameof(ToString_IFormatProviderCustom_TestData))] + public void ToString_InvokeIFormatProviderCustom_ReturnsExpected(Matrix matrix, string numberDecimalSeparator, string expected) + { + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + Assert.Equal(expected, matrix.ToString(formatInfo)); + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_InvokeIFormattableInvariantCulture_ReturnsExpected(Matrix matrix, string expected) + { + IFormattable formattable = matrix; + + Assert.Equal(expected, formattable.ToString(null, null)); + Assert.Equal(expected, formattable.ToString(null, CultureInfo.InvariantCulture)); + } + + public static IEnumerable ToString_IFormattableCustomFormat_TestData() + { + yield return new object[] { "|", "1|23,2|35,3|00,4|00,5|00,6|00" }; + yield return new object[] { "|_", "1|_23,2|_35,3|_00,4|_00,5|_00,6|_00" }; + yield return new object[] { ",_", "1,_23;2,_35;3,_00;4,_00;5,_00;6,_00" }; + yield return new object[] { ",", "1,23;2,35;3,00;4,00;5,00;6,00" }; + yield return new object[] { ";", "1;23,2;35,3;00,4;00,5;00,6;00" }; + yield return new object[] { " ", "1 23,2 35,3 00,4 00,5 00,6 00" }; + } + + [Theory] + [MemberData(nameof(ToString_IFormattableCustomFormat_TestData))] + public void ToString_InvokeIFormattableCustomFormat_ReturnsExpected(string numberDecimalSeparator, string expected) + { + var matrix = new Matrix(1.23456, 2.34567, 3, 4, 5, 6); + IFormattable formattable = matrix; + + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + Assert.Equal(expected, formattable.ToString("F2", formatInfo)); + } + + public static IEnumerable Transform_Point_TestData() + { + // Identity. + foreach (Matrix matrix in MatrixTests.IdentityMatrices()) + { + yield return new object[] { matrix, new Point(1, 2), new Point(1, 2) }; + } + + // Scale. + yield return new object[] { new Matrix(2, 0, 0, 1, 0, 0), new Point(1, 2), new Point(2, 2) }; + yield return new object[] { new Matrix(1, 0, 0, 2, 0, 0), new Point(1, 2), new Point(1, 4) }; + yield return new object[] { new Matrix(0, 0, 0, 3, 0, 0), new Point(1, 2), new Point(0, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 0, 0, 0), new Point(1, 2), new Point(2, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Point(1, 2), new Point(0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), new Point(1, 2), new Point(2, 6) }; + yield return new object[] { new Matrix(-2, 0, 0, 3, 0, 0), new Point(1, 2), new Point(-2, 6) }; + yield return new object[] { new Matrix(2, 0, 0, -3, 0, 0), new Point(1, 2), new Point(2, -6) }; + yield return new object[] { new Matrix(-2, 0, 0, -3, 0, 0), new Point(1, 2), new Point(-2, -6) }; + + // Skew. + yield return new object[] { new Matrix(1, 2, 0, 1, 0, 0), new Point(1, 2), new Point(1, 4) }; + yield return new object[] { new Matrix(1, 0, 3, 1, 0, 0), new Point(1, 2), new Point(7, 2) }; + yield return new object[] { new Matrix(1, 2, 3, 1, 0, 0), new Point(1, 2), new Point(7, 4) }; + + // Translate. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 0), new Point(1, 2), new Point(3, 2) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 0, 3), new Point(1, 2), new Point(1, 5) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), new Point(1, 2), new Point(3, 5) }; + yield return new object[] { new Matrix(1, 0, 0, 1, -2, 0), new Point(1, 2), new Point(-1, 2) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 0, -3), new Point(1, 2), new Point(1, -1) }; + yield return new object[] { new Matrix(1, 0, 0, 1, -2, -3), new Point(1, 2), new Point(-1, -1) }; + + // Translate + Scale. + yield return new object[] { new Matrix(2, 0, 0, 3, 4, 5), new Point(1, 2), new Point(6, 11) }; + + // Translate + Skew. + yield return new object[] { new Matrix(1, 2, 3, 1, 4, 5), new Point(1, 2), new Point(11, 9) }; + + // Skew + Scale. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Point(1, 2), new Point(10, 13) }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Point(1, 2), new Point(16, 20) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Point(1, 2), new Point(0, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Point(1.1, 2.2), new Point(17, 21.3) }; + + // Other cases. + yield return new object[] { Matrix.Identity, new Point(-1, -2), new Point(-1, -2) }; + yield return new object[] { new Matrix(), new Point(-1, -2), new Point(-1, -2) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 0, 0), new Point(-1, -2), new Point(-1, -2) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Point(-1, -2), new Point(-4, -6) }; + + yield return new object[] { Matrix.Identity, new Point(), new Point() }; + yield return new object[] { new Matrix(), new Point(), new Point() }; + yield return new object[] { new Matrix(1, 0, 0, 1, 0, 0), new Point(), new Point() }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Point(), new Point(6, 7) }; + } + + [Theory] + [MemberData(nameof(Transform_Point_TestData))] + public void Transform_InvokePoint_ReturnsExpected(Matrix matrix, Point point, Point expected) + { + Assert.Equal(expected, matrix.Transform(point)); + } + + public static IEnumerable Transform_PointArray_TestData() + { + foreach (object[] testData in Transform_Point_TestData()) + { + yield return new object[] { (Matrix)testData[0], new Point[] { (Point)testData[1] }, new Point[] { (Point)testData[2] } }; + } + + // Other cases. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Point[0], new Point[0] }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Point[] { new Point(1, 2), new Point(2, 3) }, new Point[] { new Point(16, 20), new Point(22, 28) } }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), null, null }; + } + + [Theory] + [MemberData(nameof(Transform_PointArray_TestData))] + public void Transform_InvokePointArray_ReturnsExpected(Matrix matrix, Point[] points, Point[] expected) + { + matrix.Transform(points); + Assert.Equal(expected, points); + } + + public static IEnumerable Transform_Vector_TestData() + { + // Identity. + foreach (Matrix matrix in MatrixTests.IdentityMatrices()) + { + yield return new object[] { matrix, new Vector(1, 2), new Vector(1, 2) }; + } + + // Scale. + yield return new object[] { new Matrix(2, 0, 0, 1, 0, 0), new Vector(1, 2), new Vector(2, 2) }; + yield return new object[] { new Matrix(1, 0, 0, 2, 0, 0), new Vector(1, 2), new Vector(1, 4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), new Vector(1, 2), new Vector(2, 6) }; + yield return new object[] { new Matrix(0, 0, 0, 3, 0, 0), new Vector(1, 2), new Vector(0, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 0, 0, 0), new Vector(1, 2), new Vector(2, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Vector(1, 2), new Vector(0, 0) }; + yield return new object[] { new Matrix(-2, 0, 0, 3, 0, 0), new Vector(1, 2), new Vector(-2, 6) }; + yield return new object[] { new Matrix(2, 0, 0, -3, 0, 0), new Vector(1, 2), new Vector(2, -6) }; + yield return new object[] { new Matrix(-2, 0, 0, -3, 0, 0), new Vector(1, 2), new Vector(-2, -6) }; + + // Skew. + yield return new object[] { new Matrix(1, 2, 0, 1, 0, 0), new Vector(1, 2), new Vector(1, 4) }; + yield return new object[] { new Matrix(1, 0, 3, 1, 0, 0), new Vector(1, 2), new Vector(7, 2) }; + yield return new object[] { new Matrix(1, 2, 3, 1, 0, 0), new Vector(1, 2), new Vector(7, 4) }; + + // Translate. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 0), new Vector(1, 2), new Vector(1, 2) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 0, 3), new Vector(1, 2), new Vector(1, 2) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), new Vector(1, 2), new Vector(1, 2) }; + yield return new object[] { new Matrix(1, 0, 0, 1, -2, 0), new Vector(1, 2), new Vector(1, 2) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 0, -3), new Vector(1, 2), new Vector(1, 2) }; + yield return new object[] { new Matrix(1, 0, 0, 1, -2, -3), new Vector(1, 2), new Vector(1, 2) }; + + // Translate + Scale. + yield return new object[] { new Matrix(2, 0, 0, 3, 4, 5), new Vector(1, 2), new Vector(2, 6) }; + + // Translate + Skew. + yield return new object[] { new Matrix(1, 2, 3, 1, 4, 5), new Vector(1, 2), new Vector(7, 4) }; + + // Skew + Scale. + yield return new object[] { new Matrix(2, 3, 4, 5, 0, 0), new Vector(1, 2), new Vector(10, 13) }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Vector(1, 2), new Vector(10, 13) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), new Vector(1, 2), new Vector(0, 0) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Vector(1.1, 2.2), new Vector(11, 14.3) }; + + // Other cases. + yield return new object[] { Matrix.Identity, new Vector(-1, -2), new Vector(-1, -2) }; + yield return new object[] { new Matrix(), new Vector(-1, -2), new Vector(-1, -2) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 0, 0), new Vector(-1, -2), new Vector(-1, -2) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Vector(-1, -2), new Vector(-10, -13) }; + + // Zero. + yield return new object[] { Matrix.Identity, new Vector(0, 0), new Vector() }; + yield return new object[] { new Matrix(), new Vector(0, 0), new Vector() }; + yield return new object[] { new Matrix(1, 0, 0, 1, 0, 0), new Vector(0, 0), new Vector() }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Vector(0, 0), new Vector() }; + + // Default. + yield return new object[] { Matrix.Identity, new Vector(), new Vector() }; + yield return new object[] { new Matrix(), new Vector(), new Vector() }; + yield return new object[] { new Matrix(1, 0, 0, 1, 0, 0), new Vector(), new Vector() }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Vector(), new Vector() }; + } + + [Theory] + [MemberData(nameof(Transform_Vector_TestData))] + public void Transform_InvokeVector_ReturnsExpected(Matrix matrix, Vector vector, Vector expected) + { + Assert.Equal(expected, matrix.Transform(vector)); + } + + public static IEnumerable Transform_VectorArray_TestData() + { + foreach (object[] testData in Transform_Vector_TestData()) + { + yield return new object[] { (Matrix)testData[0], new Vector[] { (Vector)testData[1] }, new Vector[] { (Vector)testData[2] } }; + } + + // Other cases. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Vector[0], new Vector[0] }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), new Vector[] { new Vector(1, 2), new Vector(2, 3) }, new Vector[] { new Vector(10, 13), new Vector(16, 21) } }; + yield return new object?[] { new Matrix(2, 3, 4, 5, 6, 7), null, null }; + } + + [Theory] + [MemberData(nameof(Transform_VectorArray_TestData))] + public void Transform_InvokeVectorArray_ReturnsExpected(Matrix matrix, Vector[] vectors, Vector[] expected) + { + matrix.Transform(vectors); + Assert.Equal(expected, vectors); + } + + public static IEnumerable Translate_TestData() + { // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, -1, -2, new Matrix(1, 0, 0, 1, -1, -2) }; + yield return new object[] { matrix, -1, 0, new Matrix(1, 0, 0, 1, -1, 0) }; + yield return new object[] { matrix, 0, -2, new Matrix(1, 0, 0, 1, 0, -2) }; + yield return new object[] { matrix, 0, 0, new Matrix(1, 0, 0, 1, 0, 0) }; + yield return new object[] { matrix, 1, 0, new Matrix(1, 0, 0, 1, 1, 0) }; + yield return new object[] { matrix, 0, 2, new Matrix(1, 0, 0, 1, 0, 2) }; + yield return new object[] { matrix, 1, 2, new Matrix(1, 0, 0, 1, 1, 2) }; + yield return new object[] { matrix, 1.1, 2.2, new Matrix(1, 0, 0, 1, 1.1, 2.2) }; + yield return new object[] { matrix, -2, -3, new Matrix(1, 0, 0, 1, -2, -3) }; + yield return new object[] { matrix, double.NaN, 2, new Matrix(1, 0, 0, 1, double.NaN, 2) }; + yield return new object[] { matrix, 1, double.NaN, new Matrix(1, 0, 0, 1, 1, double.NaN) }; + yield return new object[] { matrix, double.NaN, double.NaN, new Matrix(1, 0, 0, 1, double.NaN, double.NaN) }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, -2, new Matrix(2, 0, 0, 3, -1, -2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, 0, new Matrix(2, 0, 0, 3, -1, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, -2, new Matrix(2, 0, 0, 3, 0, -2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 0, new Matrix(2, 0, 0, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 0, new Matrix(2, 0, 0, 3, 1, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 2, new Matrix(2, 0, 0, 3, 0, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 2, new Matrix(2, 0, 0, 3, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1.1, 2.2, new Matrix(2, 0, 0, 3, 1.1, 2.2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -2, -3, new Matrix(2, 0, 0, 3, -2, -3) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, 2, new Matrix(2, 0, 0, 3, double.NaN, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, double.NaN, new Matrix(2, 0, 0, 3, 1, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, double.NaN, new Matrix(2, 0, 0, 3, double.NaN, double.NaN) }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, -2, new Matrix(1, 0.5, 0.75, 1, -1, -2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, 0, new Matrix(1, 0.5, 0.75, 1, -1, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, -2, new Matrix(1, 0.5, 0.75, 1, 0, -2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 0, new Matrix(1, 0.5, 0.75, 1, 1, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 2, new Matrix(1, 0.5, 0.75, 1, 0, 2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 2, new Matrix(1, 0.5, 0.75, 1, 1, 2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1.1, 2.2, new Matrix(1, 0.5, 0.75, 1, 1.1, 2.2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -2, -3, new Matrix(1, 0.5, 0.75, 1, -2, -3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, 2, new Matrix(1, 0.5, 0.75, 1, double.NaN, 2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, double.NaN, new Matrix(1, 0.5, 0.75, 1, 1, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, double.NaN, new Matrix(1, 0.5, 0.75, 1, double.NaN, double.NaN) }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, -2, new Matrix(1, 0, 0, 1, 1, 1) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, 0, new Matrix(1, 0, 0, 1, 1, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, -2, new Matrix(1, 0, 0, 1, 2, 1) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 0, new Matrix(1, 0, 0, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 0, new Matrix(1, 0, 0, 1, 3, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 2, new Matrix(1, 0, 0, 1, 2, 5) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 2, new Matrix(1, 0, 0, 1, 3, 5) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1.1, 2.2, new Matrix(1, 0, 0, 1, 3.1, 5.2) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -2, -3, new Matrix(1, 0, 0, 1, 0, 0) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, 2, new Matrix(1, 0, 0, 1, double.NaN, 5) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, double.NaN, new Matrix(1, 0, 0, 1, 3, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, double.NaN, new Matrix(1, 0, 0, 1, double.NaN, double.NaN) }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, -2, new Matrix(2, 0, 0, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, 0, new Matrix(2, 0, 0, 3, 0, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, -2, new Matrix(2, 0, 0, 3, 1, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 0, new Matrix(2, 0, 0, 3, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 0, new Matrix(2, 0, 0, 3, 2, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 2, new Matrix(2, 0, 0, 3, 1, 4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 2, new Matrix(2, 0, 0, 3, 2, 4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1.1, 2.2, new Matrix(2, 0, 0, 3, 2.1, 4.2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -2, -3, new Matrix(2, 0, 0, 3, -1, -1) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, 2, new Matrix(2, 0, 0, 3, double.NaN, 4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, double.NaN, new Matrix(2, 0, 0, 3, 2, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, double.NaN, new Matrix(2, 0, 0, 3, double.NaN, double.NaN) }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, -2, new Matrix(1, 0.5, 0.75, 1, 1, 1) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, 0, new Matrix(1, 0.5, 0.75, 1, 1, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, -2, new Matrix(1, 0.5, 0.75, 1, 2, 1) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 0, new Matrix(1, 0.5, 0.75, 1, 3, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 2, new Matrix(1, 0.5, 0.75, 1, 2, 5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 2, new Matrix(1, 0.5, 0.75, 1, 3, 5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1.1, 2.2, new Matrix(1, 0.5, 0.75, 1, 3.1, 5.2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -2, -3, new Matrix(1, 0.5, 0.75, 1, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, 2, new Matrix(1, 0.5, 0.75, 1, double.NaN, 5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, double.NaN, new Matrix(1, 0.5, 0.75, 1, 3, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, double.NaN, new Matrix(1, 0.5, 0.75, 1, double.NaN, double.NaN) }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, -2, new Matrix(2, 0.5, 0.75, 3, -1, -2) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, 0, new Matrix(2, 0.5, 0.75, 3, -1, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, -2, new Matrix(2, 0.5, 0.75, 3, 0, -2) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 0, new Matrix(2, 0.5, 0.75, 3, 1, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 2, new Matrix(2, 0.5, 0.75, 3, 0, 2) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 2, new Matrix(2, 0.5, 0.75, 3, 1, 2) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1.1, 2.2, new Matrix(2, 0.5, 0.75, 3, 1.1, 2.2) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -2, -3, new Matrix(2, 0.5, 0.75, 3, -2, -3) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, 2, new Matrix(2, 0.5, 0.75, 3, double.NaN, 2) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, double.NaN, new Matrix(2, 0.5, 0.75, 3, 1, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, double.NaN, new Matrix(2, 0.5, 0.75, 3, double.NaN, double.NaN) }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, -2, new Matrix(2, 3, 4, 5, 5, 5) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, 0, new Matrix(2, 3, 4, 5, 5, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, -2, new Matrix(2, 3, 4, 5, 6, 5) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 0, new Matrix(2, 3, 4, 5, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 0, new Matrix(2, 3, 4, 5, 7, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 2, new Matrix(2, 3, 4, 5, 6, 9) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 2, new Matrix(2, 3, 4, 5, 7, 9) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1.1, 2.2, new Matrix(2, 3, 4, 5, 7.1, 9.2) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -2, -3, new Matrix(2, 3, 4, 5, 4, 4) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, 2, new Matrix(2, 3, 4, 5, double.NaN, 9) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, double.NaN, new Matrix(2, 3, 4, 5, 7, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, double.NaN, new Matrix(2, 3, 4, 5, double.NaN, double.NaN) }; + + // No inverse. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, -2, new Matrix(0, 0, 0, 0, -1, -2) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, 0, new Matrix(0, 0, 0, 0, -1, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, -2, new Matrix(0, 0, 0, 0, 0, -2) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 0, new Matrix(0, 0, 0, 0, 1, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 2, new Matrix(0, 0, 0, 0, 0, 2) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 2, new Matrix(0, 0, 0, 0, 1, 2) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1.1, 2.2, new Matrix(0, 0, 0, 0, 1.1, 2.2) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -2, -3, new Matrix(0, 0, 0, 0, -2, -3) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, 2, new Matrix(0, 0, 0, 0, double.NaN, 2) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, double.NaN, new Matrix(0, 0, 0, 0, 1, double.NaN) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, double.NaN, new Matrix(0, 0, 0, 0, double.NaN, double.NaN) }; + } + + [Theory] + [MemberData(nameof(Translate_TestData))] + public void Translate_Invoke_Success(Matrix matrix, double offsetX, double offsetY, Matrix expected) + { + matrix.Translate(offsetX, offsetY); + Helpers.AssertEqualRounded(expected, matrix); + Assert.Equal(expected.IsIdentity, matrix.IsIdentity); + Assert.Equal(expected.HasInverse, matrix.HasInverse); + Assert.Equal(expected.Determinant, matrix.Determinant, precision: 5); + } + + public static IEnumerable TranslatePrepend_TestData() + { + // Identity + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, -1, -2, new Matrix(1, 0, 0, 1, -1, -2) }; + yield return new object[] { matrix, -1, 0, new Matrix(1, 0, 0, 1, -1, 0) }; + yield return new object[] { matrix, 0, -2, new Matrix(1, 0, 0, 1, 0, -2) }; + yield return new object[] { matrix, 0, 0, new Matrix(1, 0, 0, 1, 0, 0) }; + yield return new object[] { matrix, 1, 0, new Matrix(1, 0, 0, 1, 1, 0) }; + yield return new object[] { matrix, 0, 2, new Matrix(1, 0, 0, 1, 0, 2) }; + yield return new object[] { matrix, 1, 2, new Matrix(1, 0, 0, 1, 1, 2) }; + yield return new object[] { matrix, 1.1, 2.2, new Matrix(1, 0, 0, 1, 1.1, 2.2) }; + yield return new object[] { matrix, -2, -3, new Matrix(1, 0, 0, 1, -2, -3) }; + yield return new object[] { matrix, double.NaN, 2, new Matrix(1, 0, 0, 1, double.NaN, 2) }; + yield return new object[] { matrix, 1, double.NaN, new Matrix(1, 0, 0, 1, 1, double.NaN) }; + yield return new object[] { matrix, double.NaN, double.NaN, new Matrix(1, 0, 0, 1, double.NaN, double.NaN) }; + } + + // Scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, -2, new Matrix(2, 0, 0, 3, -2, -6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -1, 0, new Matrix(2, 0, 0, 3, -2, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, -2, new Matrix(2, 0, 0, 3, 0, -6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 0, new Matrix(2, 0, 0, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 0, new Matrix(2, 0, 0, 3, 2, 0) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 0, 2, new Matrix(2, 0, 0, 3, 0, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, 2, new Matrix(2, 0, 0, 3, 2, 6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1.1, 2.2, new Matrix(2, 0, 0, 3, 2.2, 6.6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), -2, -3, new Matrix(2, 0, 0, 3, -4, -9) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, 2, new Matrix(2, 0, 0, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), 1, double.NaN, new Matrix(2, 0, 0, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 0, 0), double.NaN, double.NaN, new Matrix(2, 0, 0, 3, double.NaN, double.NaN) }; + + // Skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, -2, new Matrix(1, 0.5, 0.75, 1, -2.5, -2.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -1, 0, new Matrix(1, 0.5, 0.75, 1, -1, -0.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, -2, new Matrix(1, 0.5, 0.75, 1, -1.5, -2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 0, new Matrix(1, 0.5, 0.75, 1, 0, 0) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 0, new Matrix(1, 0.5, 0.75, 1, 1, 0.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 0, 2, new Matrix(1, 0.5, 0.75, 1, 1.5, 2) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, 2, new Matrix(1, 0.5, 0.75, 1, 2.5, 2.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1.1, 2.2, new Matrix(1, 0.5, 0.75, 1, 2.75, 2.75) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), -2, -3, new Matrix(1, 0.5, 0.75, 1, -4.25, -4) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, 2, new Matrix(1, 0.5, 0.75, 1, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), 1, double.NaN, new Matrix(1, 0.5, 0.75, 1, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 0, 0), double.NaN, double.NaN, new Matrix(1, 0.5, 0.75, 1, double.NaN, double.NaN) }; + + // Translated. + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, -2, new Matrix(1, 0, 0, 1, 1, 1) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -1, 0, new Matrix(1, 0, 0, 1, 1, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, -2, new Matrix(1, 0, 0, 1, 2, 1) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 0, new Matrix(1, 0, 0, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 0, new Matrix(1, 0, 0, 1, 3, 3) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 0, 2, new Matrix(1, 0, 0, 1, 2, 5) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, 2, new Matrix(1, 0, 0, 1, 3, 5) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1.1, 2.2, new Matrix(1, 0, 0, 1, 3.1, 5.2) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), -2, -3, new Matrix(1, 0, 0, 1, 0, 0) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, 2, new Matrix(1, 0, 0, 1, double.NaN, 5) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), 1, double.NaN, new Matrix(1, 0, 0, 1, 3, double.NaN) }; + yield return new object[] { new Matrix(1, 0, 0, 1, 2, 3), double.NaN, double.NaN, new Matrix(1, 0, 0, 1, double.NaN, double.NaN) }; + + // Translated and scaled. + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, -2, new Matrix(2, 0, 0, 3, -1, -4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -1, 0, new Matrix(2, 0, 0, 3, -1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, -2, new Matrix(2, 0, 0, 3, 1, -4) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 0, new Matrix(2, 0, 0, 3, 1, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 0, new Matrix(2, 0, 0, 3, 3, 2) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 0, 2, new Matrix(2, 0, 0, 3, 1, 8) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, 2, new Matrix(2, 0, 0, 3, 3, 8) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1.1, 2.2, new Matrix(2, 0, 0, 3, 3.2, 8.6) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), -2, -3, new Matrix(2, 0, 0, 3, -3, -7) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, 2, new Matrix(2, 0, 0, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), 1, double.NaN, new Matrix(2, 0, 0, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0, 0, 3, 1, 2), double.NaN, double.NaN, new Matrix(2, 0, 0, 3, double.NaN, double.NaN) }; + + // Translated and skewed. + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, -2, new Matrix(1, 0.5, 0.75, 1, -0.5, 0.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -1, 0, new Matrix(1, 0.5, 0.75, 1, 1, 2.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, -2, new Matrix(1, 0.5, 0.75, 1, 0.5, 1) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 0, new Matrix(1, 0.5, 0.75, 1, 2, 3) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 0, new Matrix(1, 0.5, 0.75, 1, 3, 3.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 0, 2, new Matrix(1, 0.5, 0.75, 1, 3.5, 5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, 2, new Matrix(1, 0.5, 0.75, 1, 4.5, 5.5) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1.1, 2.2, new Matrix(1, 0.5, 0.75, 1, 4.75, 5.75) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), -2, -3, new Matrix(1, 0.5, 0.75, 1, -2.25, -1) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, 2, new Matrix(1, 0.5, 0.75, 1, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), 1, double.NaN, new Matrix(1, 0.5, 0.75, 1, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(1, 0.5, 0.75, 1, 2, 3), double.NaN, double.NaN, new Matrix(1, 0.5, 0.75, 1, double.NaN, double.NaN) }; + + // Skewed and scaled. + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, -2, new Matrix(2, 0.5, 0.75, 3, -3.5, -6.5) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -1, 0, new Matrix(2, 0.5, 0.75, 3, -2, -0.5) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, -2, new Matrix(2, 0.5, 0.75, 3, -1.5, -6) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 0, new Matrix(2, 0.5, 0.75, 3, 0, 0) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 0, new Matrix(2, 0.5, 0.75, 3, 2, 0.5) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 0, 2, new Matrix(2, 0.5, 0.75, 3, 1.5, 6) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, 2, new Matrix(2, 0.5, 0.75, 3, 3.5, 6.5) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1.1, 2.2, new Matrix(2, 0.5, 0.75, 3, 3.85, 7.15) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), -2, -3, new Matrix(2, 0.5, 0.75, 3, -6.25, -10) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, 2, new Matrix(2, 0.5, 0.75, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), 1, double.NaN, new Matrix(2, 0.5, 0.75, 3, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 0.5, 0.75, 3, 0, 0), double.NaN, double.NaN, new Matrix(2, 0.5, 0.75, 3, double.NaN, double.NaN) }; + + // Complex. + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, -2, new Matrix(2, 3, 4, 5, -4, -6) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -1, 0, new Matrix(2, 3, 4, 5, 4, 4) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, -2, new Matrix(2, 3, 4, 5, -2, -3) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 0, new Matrix(2, 3, 4, 5, 6, 7) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 0, new Matrix(2, 3, 4, 5, 8, 10) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 0, 2, new Matrix(2, 3, 4, 5, 14, 17) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, 2, new Matrix(2, 3, 4, 5, 16, 20) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1.1, 2.2, new Matrix(2, 3, 4, 5, 17, 21.3) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), -2, -3, new Matrix(2, 3, 4, 5, -10, -14) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, 2, new Matrix(2, 3, 4, 5, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), 1, double.NaN, new Matrix(2, 3, 4, 5, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(2, 3, 4, 5, 6, 7), double.NaN, double.NaN, new Matrix(2, 3, 4, 5, double.NaN, double.NaN) }; + + // No inverse. + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, -2, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -1, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, -2, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 0, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 0, 2, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, 2, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1.1, 2.2, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), -2, -3, new Matrix(0, 0, 0, 0, 0, 0) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, 2, new Matrix(0, 0, 0, 0, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), 1, double.NaN, new Matrix(0, 0, 0, 0, double.NaN, double.NaN) }; + yield return new object[] { new Matrix(0, 0, 0, 0, 0, 0), double.NaN, double.NaN, new Matrix(0, 0, 0, 0, double.NaN, double.NaN) }; + } + + [Theory] + [MemberData(nameof(TranslatePrepend_TestData))] + public void TranslatePrepend_Invoke_Success(Matrix matrix, double offsetX, double offsetY, Matrix expected) + { + matrix.TranslatePrepend(offsetX, offsetY); + Helpers.AssertEqualRounded(expected, matrix); + Assert.Equal(expected.IsIdentity, matrix.IsIdentity); + Assert.Equal(expected.HasInverse, matrix.HasInverse); + Assert.Equal(expected.Determinant, matrix.Determinant, precision: 5); + } + + public static IEnumerable Scale_SetIdentity_TestData() + { + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, double.NegativeInfinity, false }; + yield return new object[] { matrix, double.MinValue, false }; + yield return new object[] { matrix, -1, false }; + yield return new object[] { matrix, double.NegativeZero, false }; + yield return new object[] { matrix, 0, false }; + yield return new object[] { matrix, 1, true }; + yield return new object[] { matrix, double.MaxValue, false }; + yield return new object[] { matrix, double.PositiveInfinity, false }; + yield return new object[] { matrix, double.NaN, false }; + } + } + + [Theory] + [MemberData(nameof(Scale_SetIdentity_TestData))] + public void M11_SetIdentity_GetReturnsExpected(Matrix matrix, double value, bool expectedIsIdentity) + { + // Set. + matrix.M11 = value; + Assert.Equal(value, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + + // Set again. + matrix.M11 = value; + Assert.Equal(value, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + } + + public static IEnumerable Scale_SetToIdentity_TestData() + { + yield return new object[] { double.NegativeInfinity, false }; + yield return new object[] { double.MinValue, false }; + yield return new object[] { -1, false }; + yield return new object[] { double.NegativeZero, false }; + yield return new object[] { 0, false }; + yield return new object[] { 1, true }; + yield return new object[] { double.MaxValue, false }; + yield return new object[] { double.PositiveInfinity, false }; + yield return new object[] { double.NaN, false }; + } + + [Theory] + [MemberData(nameof(Scale_SetToIdentity_TestData))] + public void M11_SetScaleToIdentity_GetReturnsExpected(double value, bool expectedIsIdentity) + { + var matrix = new Matrix(2, 0, 0, 1, 0, 0); + + // Set. + matrix.M11 = value; + Assert.Equal(value, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + + // Set again. + matrix.M11 = value; + Assert.Equal(value, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + } + + public static IEnumerable MatrixElement_Set_TestData() + { + yield return new object[] { double.NegativeInfinity }; + yield return new object[] { double.MinValue }; + yield return new object[] { -1 }; + yield return new object[] { double.NegativeZero }; + yield return new object[] { 0 }; + yield return new object[] { 1 }; + yield return new object[] { double.MaxValue }; + yield return new object[] { double.PositiveInfinity }; + yield return new object[] { double.NaN }; + } + + [Theory] + [MemberData(nameof(MatrixElement_Set_TestData))] + public void M11_Set_GetReturnsExpected(double value) + { + var matrix = new Matrix(2, 3, 4, 5, 6, 7); + + // Set. + matrix.M11 = value; + Assert.Equal(value, matrix.M11); + Assert.Equal(3, matrix.M12); + Assert.Equal(4, matrix.M21); + Assert.Equal(5, matrix.M22); + Assert.Equal(6, matrix.OffsetX); + Assert.Equal(7, matrix.OffsetY); + Assert.False(matrix.IsIdentity); + + // Set again. + matrix.M11 = value; + Assert.Equal(value, matrix.M11); + Assert.Equal(3, matrix.M12); + Assert.Equal(4, matrix.M21); + Assert.Equal(5, matrix.M22); + Assert.Equal(6, matrix.OffsetX); + Assert.Equal(7, matrix.OffsetY); + Assert.False(matrix.IsIdentity); + } + + public static IEnumerable NonScale_SetIdentity_TestData() + { + foreach (Matrix matrix in IdentityMatrices()) + { + yield return new object[] { matrix, double.NegativeInfinity, false }; + yield return new object[] { matrix, double.MinValue, false }; + yield return new object[] { matrix, -1, false }; + yield return new object[] { matrix, double.NegativeZero, true }; + yield return new object[] { matrix, 0, true }; + yield return new object[] { matrix, 1, false }; + yield return new object[] { matrix, double.MaxValue, false }; + yield return new object[] { matrix, double.PositiveInfinity, false }; + yield return new object[] { matrix, double.NaN, false }; + } + } + + [Theory] + [MemberData(nameof(NonScale_SetIdentity_TestData))] + public void M12_SetIdentity_GetReturnsExpected(Matrix matrix, double value, bool expectedIsIdentity) + { + // Set. + matrix.M12 = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(value, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + + // Set again. + matrix.M12 = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(value, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + } + + public static IEnumerable NonScale_SetToIdentity_TestData() + { + yield return new object[] { double.NegativeInfinity, false }; + yield return new object[] { double.MinValue, false }; + yield return new object[] { -1, false }; + yield return new object[] { double.NegativeZero, true }; + yield return new object[] { 0, true }; + yield return new object[] { 1, false }; + yield return new object[] { double.MaxValue, false }; + yield return new object[] { double.PositiveInfinity, false }; + yield return new object[] { double.NaN, false }; + } + + [Theory] + [MemberData(nameof(NonScale_SetToIdentity_TestData))] + public void M12_SetScaleToIdentity_GetReturnsExpected(double value, bool expectedIsIdentity) + { + var matrix = new Matrix(1, 1, 0, 1, 0, 0); + + // Set. + matrix.M12 = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(value, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + + // Set again. + matrix.M12 = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(value, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + } + + [Theory] + [MemberData(nameof(MatrixElement_Set_TestData))] + public void M12_Set_GetReturnsExpected(double value) + { + var matrix = new Matrix(2, 3, 4, 5, 6, 7); + + // Set. + matrix.M12 = value; + Assert.Equal(2, matrix.M11); + Assert.Equal(value, matrix.M12); + Assert.Equal(4, matrix.M21); + Assert.Equal(5, matrix.M22); + Assert.Equal(6, matrix.OffsetX); + Assert.Equal(7, matrix.OffsetY); + Assert.False(matrix.IsIdentity); + + // Set again. + matrix.M12 = value; + Assert.Equal(2, matrix.M11); + Assert.Equal(value, matrix.M12); + Assert.Equal(4, matrix.M21); + Assert.Equal(5, matrix.M22); + Assert.Equal(6, matrix.OffsetX); + Assert.Equal(7, matrix.OffsetY); + Assert.False(matrix.IsIdentity); + } + + [Theory] + [MemberData(nameof(NonScale_SetIdentity_TestData))] + public void M21_SetIdentity_GetReturnsExpected(Matrix matrix, double value, bool expectedIsIdentity) + { + // Set. + matrix.M21 = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(value, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + + // Set again. + matrix.M21 = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(value, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + } + + [Theory] + [MemberData(nameof(NonScale_SetToIdentity_TestData))] + public void M21_SetScaleToIdentity_GetReturnsExpected(double value, bool expectedIsIdentity) + { + var matrix = new Matrix(1, 0, 1, 1, 0, 0); + + // Set. + matrix.M21 = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(value, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + + // Set again. + matrix.M21 = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(value, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + } + + [Theory] + [MemberData(nameof(MatrixElement_Set_TestData))] + public void M21_Set_GetReturnsExpected(double value) + { + var matrix = new Matrix(2, 3, 4, 5, 6, 7); + + // Set. + matrix.M21 = value; + Assert.Equal(2, matrix.M11); + Assert.Equal(3, matrix.M12); + Assert.Equal(value, matrix.M21); + Assert.Equal(5, matrix.M22); + Assert.Equal(6, matrix.OffsetX); + Assert.Equal(7, matrix.OffsetY); + Assert.False(matrix.IsIdentity); + + // Set again. + matrix.M21 = value; + Assert.Equal(2, matrix.M11); + Assert.Equal(3, matrix.M12); + Assert.Equal(value, matrix.M21); + Assert.Equal(5, matrix.M22); + Assert.Equal(6, matrix.OffsetX); + Assert.Equal(7, matrix.OffsetY); + Assert.False(matrix.IsIdentity); + } + + [Theory] + [MemberData(nameof(Scale_SetIdentity_TestData))] + public void M22_SetIdentity_GetReturnsExpected(Matrix matrix, double value, bool expectedIsIdentity) + { + // Set. + matrix.M22 = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(value, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + + // Set again. + matrix.M22 = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(value, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + } + + [Theory] + [MemberData(nameof(Scale_SetToIdentity_TestData))] + public void M22_SetScaleToIdentity_GetReturnsExpected(double value, bool expectedIsIdentity) + { + var matrix = new Matrix(1, 0, 0, 2, 0, 0); + + // Set. + matrix.M22 = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(value, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + + // Set again. + matrix.M22 = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(value, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + } + + [Theory] + [MemberData(nameof(MatrixElement_Set_TestData))] + public void M22_Set_GetReturnsExpected(double value) + { + var matrix = new Matrix(2, 3, 4, 5, 6, 7); + + // Set. + matrix.M22 = value; + Assert.Equal(2, matrix.M11); + Assert.Equal(3, matrix.M12); + Assert.Equal(4, matrix.M21); + Assert.Equal(value, matrix.M22); + Assert.Equal(6, matrix.OffsetX); + Assert.Equal(7, matrix.OffsetY); + Assert.False(matrix.IsIdentity); + + // Set again. + matrix.M22 = value; + Assert.Equal(2, matrix.M11); + Assert.Equal(3, matrix.M12); + Assert.Equal(4, matrix.M21); + Assert.Equal(value, matrix.M22); + Assert.Equal(6, matrix.OffsetX); + Assert.Equal(7, matrix.OffsetY); + Assert.False(matrix.IsIdentity); + } + + [Theory] + [MemberData(nameof(NonScale_SetIdentity_TestData))] + public void OffsetX_SetIdentity_GetReturnsExpected(Matrix matrix, double value, bool expectedIsIdentity) + { + // Set. + matrix.OffsetX = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(value, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + + // Set again. + matrix.OffsetX = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(value, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + } + + [Theory] + [MemberData(nameof(NonScale_SetToIdentity_TestData))] + public void OffsetX_SetScaleToIdentity_GetReturnsExpected(double value, bool expectedIsIdentity) + { + var matrix = new Matrix(1, 0, 0, 1, 1, 0); + + // Set. + matrix.OffsetX = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(value, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + + // Set again. + matrix.OffsetX = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(value, matrix.OffsetX); + Assert.Equal(0, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + } + + [Theory] + [MemberData(nameof(MatrixElement_Set_TestData))] + public void OffsetX_Set_GetReturnsExpected(double value) + { + var matrix = new Matrix(2, 3, 4, 5, 6, 7); + + // Set. + matrix.OffsetX = value; + Assert.Equal(2, matrix.M11); + Assert.Equal(3, matrix.M12); + Assert.Equal(4, matrix.M21); + Assert.Equal(5, matrix.M22); + Assert.Equal(value, matrix.OffsetX); + Assert.Equal(7, matrix.OffsetY); + Assert.False(matrix.IsIdentity); + + // Set again. + matrix.OffsetX = value; + Assert.Equal(2, matrix.M11); + Assert.Equal(3, matrix.M12); + Assert.Equal(4, matrix.M21); + Assert.Equal(5, matrix.M22); + Assert.Equal(value, matrix.OffsetX); + Assert.Equal(7, matrix.OffsetY); + Assert.False(matrix.IsIdentity); + } + + [Theory] + [MemberData(nameof(NonScale_SetIdentity_TestData))] + public void OffsetY_SetIdentity_GetReturnsExpected(Matrix matrix, double value, bool expectedIsIdentity) + { + // Set. + matrix.OffsetY = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(value, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + + // Set again. + matrix.OffsetY = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(value, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + } + + [Theory] + [MemberData(nameof(NonScale_SetToIdentity_TestData))] + public void OffsetY_SetScaleToIdentity_GetReturnsExpected(double value, bool expectedIsIdentity) + { + var matrix = new Matrix(1, 0, 0, 1, 0, 1); + + // Set. + matrix.OffsetY = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(value, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + + // Set again. + matrix.OffsetY = value; + Assert.Equal(1, matrix.M11); + Assert.Equal(0, matrix.M12); + Assert.Equal(0, matrix.M21); + Assert.Equal(1, matrix.M22); + Assert.Equal(0, matrix.OffsetX); + Assert.Equal(value, matrix.OffsetY); + Assert.Equal(expectedIsIdentity, matrix.IsIdentity); + } + + [Theory] + [MemberData(nameof(MatrixElement_Set_TestData))] + public void OffsetY_Set_GetReturnsExpected(double value) + { + var matrix = new Matrix(2, 3, 4, 5, 6, 7); + + // Set. + matrix.OffsetY = value; + Assert.Equal(2, matrix.M11); + Assert.Equal(3, matrix.M12); + Assert.Equal(4, matrix.M21); + Assert.Equal(5, matrix.M22); + Assert.Equal(6, matrix.OffsetX); + Assert.Equal(value, matrix.OffsetY); + Assert.False(matrix.IsIdentity); + + // Set again. + matrix.OffsetY = value; + Assert.Equal(2, matrix.M11); + Assert.Equal(3, matrix.M12); + Assert.Equal(4, matrix.M21); + Assert.Equal(5, matrix.M22); + Assert.Equal(6, matrix.OffsetX); + Assert.Equal(value, matrix.OffsetY); + Assert.False(matrix.IsIdentity); + } + + [Fact] + public void TypeConverter_Get_ReturnsExpected() + { + Assert.IsType(TypeDescriptor.GetConverter(typeof(Matrix))); + } + + [Fact] + public void ValueSerializer_Get_ReturnsExpected() + { + Assert.IsType(ValueSerializer.GetSerializerFor(typeof(Matrix))); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/NameScopeTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/NameScopeTests.cs new file mode 100644 index 00000000000..c68af4339e2 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/NameScopeTests.cs @@ -0,0 +1,1200 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections; +using System.Linq; +using System.Reflection; +using System.Windows.Markup; + +namespace System.Windows.Tests; + +public class NameScopeTests +{ +#pragma warning disable xUnit2013 // Do not use equality check to check for collection size. +#pragma warning disable xUnit2017 // Do not use Contains() to check if a value exists in a collection + + [Fact] + public void Ctor_Default() + { + var nameScope = new NameScope(); + Assert.Equal(0, nameScope.Count); + Assert.False(nameScope.IsReadOnly); + Assert.Null(nameScope.Keys); + Assert.Null(nameScope.Values); + } + + [Fact] + public void GetNameScope_InvokeDefault_ReturnsExpected() + { + var dependencyObject = new DependencyObject(); + Assert.Null(NameScope.GetNameScope(dependencyObject)); + } + + [Fact] + public void GetNameScope_NullDependencyObject_ThrowsArgumentNullException() + { + Assert.Throws("dependencyObject", () => NameScope.GetNameScope(null)); + } + + [Fact] + public void SetNameScope_Invoke_GetNameScopeReturnsExpected() + { + var dependencyObject = new DependencyObject(); + var nameScope1 = new CustomNameScope(); + var nameScope2 = new CustomNameScope(); + + // Set. + NameScope.SetNameScope(dependencyObject, nameScope1); + Assert.Same(nameScope1, NameScope.GetNameScope(dependencyObject)); + + // Set same. + NameScope.SetNameScope(dependencyObject, nameScope1); + Assert.Same(nameScope1, NameScope.GetNameScope(dependencyObject)); + + // Set different. + NameScope.SetNameScope(dependencyObject, nameScope2); + Assert.Same(nameScope2, NameScope.GetNameScope(dependencyObject)); + } + + [Fact] + public void SetNameScope_NullDependencyObject_ThrowsArgumentNullException() + { + var nameScope = new CustomNameScope(); + Assert.Throws("dependencyObject", () => NameScope.SetNameScope(null, nameScope)); + } + + [Fact] + public void Item_Get_ReturnsExpected() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName("name", scopedElement); + Assert.Same(scopedElement, nameScope["name"]); + } + + [Theory] + [InlineData("")] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void Item_GetNoSuchNameEmpty_ReturnsNull(string name) + { + var nameScope = new NameScope(); + Assert.Null(nameScope[name]); + } + + [Theory] + [InlineData("")] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void Item_GetNoSuchNameNotEmpty_ReturnsNull(string name) + { + var nameScope = new NameScope(); + nameScope.RegisterName("name1", new object()); + + Assert.Null(nameScope[name]); + } + + [Fact] + public void Item_GetNullKey_ThrowsArgumentNullException() + { + var nameScope = new NameScope(); + Assert.Throws("key", () => nameScope[null]); + } + + [Theory] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void Item_SetInvoke_Success(string name) + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope[name] = scopedElement; + Assert.Equal(1, nameScope.Count); + Assert.Same(scopedElement, nameScope.FindName(name)); + Assert.True(nameScope.ContainsKey(name)); + Assert.True(nameScope.Contains(new KeyValuePair(name, scopedElement))); + Assert.Equal(1, nameScope.Keys.Count); + Assert.NotSame(nameScope.Keys, nameScope.Keys); + Assert.Equal(name, nameScope.Keys.First()); + Assert.Equal(1, nameScope.Values.Count); + Assert.NotSame(nameScope.Values, nameScope.Values); + Assert.Equal(scopedElement, nameScope.Values.First()); + + // Set same. + nameScope[name] = scopedElement; + Assert.Equal(1, nameScope.Count); + Assert.Same(scopedElement, nameScope.FindName(name)); + Assert.True(nameScope.ContainsKey(name)); + Assert.True(nameScope.Contains(new KeyValuePair(name, scopedElement))); + Assert.Equal(1, nameScope.Keys.Count); + Assert.NotSame(nameScope.Keys, nameScope.Keys); + Assert.Equal(name, nameScope.Keys.First()); + Assert.Equal(1, nameScope.Values.Count); + Assert.NotSame(nameScope.Values, nameScope.Values); + Assert.Equal(scopedElement, nameScope.Values.First()); + } + + [Fact] + public void Item_SetInvokeMultiple_Success() + { + var nameScope = new NameScope(); + var scopedElement1 = new object(); + var scopedElement2 = new object(); + + nameScope["name1"] = scopedElement1; + Assert.Same(scopedElement1, nameScope.FindName("name1")); + Assert.Equal(1, nameScope.Count); + + nameScope["NAME1"] = scopedElement2; + Assert.Same(scopedElement1, nameScope.FindName("name1")); + Assert.Same(scopedElement2, nameScope.FindName("NAME1")); + Assert.Equal(2, nameScope.Count); + + nameScope["name2"] = scopedElement2; + Assert.Same(scopedElement1, nameScope.FindName("name1")); + Assert.Same(scopedElement2, nameScope.FindName("NAME1")); + Assert.Same(scopedElement2, nameScope.FindName("name2")); + Assert.Equal(3, nameScope.Count); + } + + [Fact] + public void Item_SetNullName_ThrowsArgumentNullException() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + Assert.Throws("key", () => nameScope.Add(null, scopedElement)); + } + + [Fact] + public void Item_SetEmptyName_ThrowsArgumentException() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + Assert.Throws(() => nameScope.Add(string.Empty, scopedElement)); + } + + [Theory] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + public void Item_SetInvalidName_ThrowsArgumentException(string name) + { + var nameScope = new NameScope(); + var scopedElement = new object(); + Assert.Throws(() => nameScope[name] = scopedElement); + } + + [Fact] + public void Item_SetNullValue_ThrowsArgumentNullException() + { + var nameScope = new NameScope(); + Assert.Throws("value", () => nameScope["name"] = null); + } + + [Fact] + public void Item_SetDuplicate_ThrowsArgumentException() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope["name"] = scopedElement; + Assert.Throws(() => nameScope["name"] = new object()); + } + + [Fact] + public void NameScopeProperty_Get_ReturnsExpected() + { + DependencyProperty property = NameScope.NameScopeProperty; + Assert.NotNull(property.DefaultMetadata); + Assert.Same(property.DefaultMetadata, property.DefaultMetadata); + Assert.Null(property.DefaultMetadata.CoerceValueCallback); + Assert.Null(property.DefaultMetadata.DefaultValue); + Assert.Null(property.DefaultMetadata.PropertyChangedCallback); + Assert.True(property.GlobalIndex >= 0); + Assert.Equal("NameScope", property.Name); + Assert.Equal(typeof(NameScope), property.OwnerType); + Assert.Equal(typeof(INameScope), property.PropertyType); + Assert.False(property.ReadOnly); + Assert.Null(property.ValidateValueCallback); + Assert.Same(property, NameScope.NameScopeProperty); + } + + [Theory] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void Add_InvokeStringObject_Success(string name) + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.Add(name, scopedElement); + Assert.Equal(1, nameScope.Count); + Assert.Same(scopedElement, nameScope.FindName(name)); + Assert.True(nameScope.ContainsKey(name)); + Assert.True(nameScope.Contains(new KeyValuePair(name, scopedElement))); + Assert.Equal(1, nameScope.Keys.Count); + Assert.NotSame(nameScope.Keys, nameScope.Keys); + Assert.Equal(name, nameScope.Keys.First()); + Assert.Equal(1, nameScope.Values.Count); + Assert.NotSame(nameScope.Values, nameScope.Values); + Assert.Equal(scopedElement, nameScope.Values.First()); + + // Set same. + nameScope.Add(name, scopedElement); + Assert.Equal(1, nameScope.Count); + Assert.Same(scopedElement, nameScope.FindName(name)); + Assert.True(nameScope.ContainsKey(name)); + Assert.True(nameScope.Contains(new KeyValuePair(name, scopedElement))); + Assert.Equal(1, nameScope.Keys.Count); + Assert.NotSame(nameScope.Keys, nameScope.Keys); + Assert.Equal(name, nameScope.Keys.First()); + Assert.Equal(1, nameScope.Values.Count); + Assert.NotSame(nameScope.Values, nameScope.Values); + Assert.Equal(scopedElement, nameScope.Values.First()); + } + + [Fact] + public void Add_InvokeStringObjectMultiple_Success() + { + var nameScope = new NameScope(); + var scopedElement1 = new object(); + var scopedElement2 = new object(); + + nameScope.Add("name1", scopedElement1); + Assert.Same(scopedElement1, nameScope.FindName("name1")); + Assert.Equal(1, nameScope.Count); + + nameScope.Add("NAME1", scopedElement2); + Assert.Same(scopedElement1, nameScope.FindName("name1")); + Assert.Same(scopedElement2, nameScope.FindName("NAME1")); + Assert.Equal(2, nameScope.Count); + + nameScope.Add("name2", scopedElement2); + Assert.Same(scopedElement1, nameScope.FindName("name1")); + Assert.Same(scopedElement2, nameScope.FindName("NAME1")); + Assert.Same(scopedElement2, nameScope.FindName("name2")); + Assert.Equal(3, nameScope.Count); + } + + [Fact] + public void Add_NullKey_ThrowsArgumentNullException() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + Assert.Throws(() => nameScope.Add(null, scopedElement)); + } + + [Fact] + public void Add_EmptyName_ThrowsArgumentException() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + Assert.Throws(() => nameScope.Add(string.Empty, scopedElement)); + } + + [Theory] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + public void Add_InvalidName_ThrowsArgumentException(string name) + { + var nameScope = new NameScope(); + var scopedElement = new object(); + Assert.Throws(() => nameScope.Add(name, scopedElement)); + } + + [Fact] + public void Add_NullScopedElement_ThrowsArgumentNullException() + { + var nameScope = new NameScope(); + Assert.Throws("scopedElement", () => nameScope.Add("name", null!)); + } + + [Theory] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void Add_InvokeKVPStringObject_Success(string name) + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.Add(new KeyValuePair(name, scopedElement)); + Assert.Equal(1, nameScope.Count); + Assert.Same(scopedElement, nameScope.FindName(name)); + Assert.True(nameScope.ContainsKey(name)); + Assert.True(nameScope.Contains(new KeyValuePair(name, scopedElement))); + Assert.Equal(1, nameScope.Keys.Count); + Assert.NotSame(nameScope.Keys, nameScope.Keys); + Assert.Equal(name, nameScope.Keys.First()); + Assert.Equal(1, nameScope.Values.Count); + Assert.NotSame(nameScope.Values, nameScope.Values); + Assert.Equal(scopedElement, nameScope.Values.First()); + + // Set same. + nameScope.Add(new KeyValuePair(name, scopedElement)); + Assert.Equal(1, nameScope.Count); + Assert.Same(scopedElement, nameScope.FindName(name)); + Assert.True(nameScope.ContainsKey(name)); + Assert.True(nameScope.Contains(new KeyValuePair(name, scopedElement))); + Assert.Equal(1, nameScope.Keys.Count); + Assert.NotSame(nameScope.Keys, nameScope.Keys); + Assert.Equal(name, nameScope.Keys.First()); + Assert.Equal(1, nameScope.Values.Count); + Assert.NotSame(nameScope.Values, nameScope.Values); + Assert.Equal(scopedElement, nameScope.Values.First()); + } + + [Fact] + public void Add_InvokeKVPStringObjectMultiple_Success() + { + var nameScope = new NameScope(); + var scopedElement1 = new object(); + var scopedElement2 = new object(); + + nameScope.Add(new KeyValuePair("name1", scopedElement1)); + Assert.Same(scopedElement1, nameScope.FindName("name1")); + Assert.Equal(1, nameScope.Count); + + nameScope.Add(new KeyValuePair("NAME1", scopedElement2)); + Assert.Same(scopedElement1, nameScope.FindName("name1")); + Assert.Same(scopedElement2, nameScope.FindName("NAME1")); + Assert.Equal(2, nameScope.Count); + + nameScope.Add(new KeyValuePair("name2", scopedElement2)); + Assert.Same(scopedElement1, nameScope.FindName("name1")); + Assert.Same(scopedElement2, nameScope.FindName("NAME1")); + Assert.Same(scopedElement2, nameScope.FindName("name2")); + Assert.Equal(3, nameScope.Count); + } + + [Fact] + public void Add_NullItemKey_ThrowsArgumentException() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + Assert.Throws("item", () => nameScope.Add(new KeyValuePair(null!, scopedElement))); + } + + [Fact] + public void Add_EmptyItemKey_ThrowsArgumentException() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + Assert.Throws(() => nameScope.Add(new KeyValuePair(string.Empty, scopedElement))); + } + + [Theory] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + public void Add_InvalidItemKey_ThrowsArgumentException(string name) + { + var nameScope = new NameScope(); + var scopedElement = new object(); + Assert.Throws(() => nameScope.Add(new KeyValuePair(name, scopedElement))); + } + + [Fact] + public void Add_NullItemValue_ThrowsArgumentException() + { + var nameScope = new NameScope(); + Assert.Throws("item", () => nameScope.Add(new KeyValuePair("name", null!))); + } + + [Fact] + public void Add_Duplicate_ThrowsArgumentException() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.Add("name", scopedElement); + Assert.Throws(() => nameScope.Add("name", new object())); + Assert.Throws(() => nameScope.Add(new KeyValuePair("name", new object()))); + } + + [Fact] + public void Clear_Invoke_Success() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.Add("name", scopedElement); + Assert.Equal(1, nameScope.Count); + + nameScope.Clear(); + Assert.Equal(0, nameScope.Count); + Assert.Null(nameScope.FindName("name")); + Assert.False(nameScope.ContainsKey("name")); + Assert.False(nameScope.Contains(new KeyValuePair("name", scopedElement))); + Assert.Null(nameScope.Keys); + Assert.Null(nameScope.Values); + + // Clear again. + nameScope.Clear(); + Assert.Equal(0, nameScope.Count); + Assert.Null(nameScope.FindName("name")); + Assert.False(nameScope.ContainsKey("name")); + Assert.False(nameScope.Contains(new KeyValuePair("name", scopedElement))); + Assert.Null(nameScope.Keys); + Assert.Null(nameScope.Values); + } + + [Fact] + public void Clear_InvokeEmpty_Success() + { + var nameScope = new NameScope(); + + // Clear. + nameScope.Clear(); + Assert.Equal(0, nameScope.Count); + Assert.Null(nameScope.FindName("name")); + Assert.False(nameScope.ContainsKey("name")); + Assert.False(nameScope.Contains(new KeyValuePair("name", new object()))); + Assert.Null(nameScope.Keys); + Assert.Null(nameScope.Values); + + // Clear again. + nameScope.Clear(); + Assert.Equal(0, nameScope.Count); + Assert.Null(nameScope.FindName("name")); + Assert.False(nameScope.ContainsKey("name")); + Assert.False(nameScope.Contains(new KeyValuePair("name", new object()))); + Assert.Null(nameScope.Keys); + Assert.Null(nameScope.Values); + } + + [Fact] + public void CopyTo_Invoke_Success() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName("name", scopedElement); + + var array = new KeyValuePair[3]; + + // Copy to start. + nameScope.CopyTo(array, 0); + Assert.Equal(new KeyValuePair("name", scopedElement), array[0]); + + // Copy to middle. + nameScope.CopyTo(array, 1); + Assert.Equal(new KeyValuePair("name", scopedElement), array[1]); + + // Copy to end. + nameScope.CopyTo(array, 2); + Assert.Equal(new KeyValuePair("name", scopedElement), array[1]); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + public void CopyTo_InvokeEmpty_Nop(int arrayIndex) + { + var nameScope = new NameScope(); + + // Not null. + var array = new KeyValuePair[1]; + nameScope.CopyTo(array, arrayIndex); + Assert.Equal(default, array[0]); + + // Null. + nameScope.CopyTo(null, arrayIndex); + } + + [Fact] + public void CopyTo_NullArray_ThrowsNullReferenceException() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName("name", scopedElement); + + Assert.Throws(() => nameScope.CopyTo(null, 0)); + } + + [Fact] + public void CopyTo_ArrayTooShort_ThrowsIndexOutOfRangeException() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName("name", scopedElement); + + var array = new KeyValuePair[0]; + Assert.Throws(() => nameScope.CopyTo(array, 0)); + } + + [Fact] + public void CopyTo_InvalidIndex_ThrowsIndexOutOfRangeException() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName("name", scopedElement); + + var array = new KeyValuePair[1]; + Assert.Throws(() => nameScope.CopyTo(array, -1)); + Assert.Throws(() => nameScope.CopyTo(array, 1)); + } + + [Fact] + public void Contains_Invoke_ReturnsExpected() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName("name", scopedElement); + + Assert.True(nameScope.Contains(new KeyValuePair("name", scopedElement))); + Assert.True(nameScope.Contains(new KeyValuePair("name", 1))); + Assert.True(nameScope.Contains(new KeyValuePair("name", new object()))); + Assert.False(nameScope.Contains(new KeyValuePair("NAME", scopedElement))); + Assert.False(nameScope.Contains(new KeyValuePair("nAmE", scopedElement))); + Assert.False(nameScope.Contains(new KeyValuePair("name2", scopedElement))); + Assert.False(nameScope.Contains(new KeyValuePair("", scopedElement))); + } + + [Theory] + [InlineData("")] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void Contains_InvokeNoSuchNameEmpty_ReturnsFalse(string name) + { + var nameScope = new NameScope(); + Assert.False(nameScope.Contains(new KeyValuePair(name, new object()))); + } + + [Theory] + [InlineData("")] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void Contains_InvokeNoSuchNameNotEmpty_ReturnsFalse(string name) + { + var nameScope = new NameScope(); + nameScope.RegisterName("name1", new object()); + + Assert.False(nameScope.Contains(new KeyValuePair(name, new object()))); + } + + [Fact] + public void Contains_NullKey_ThrowsArgumentException() + { + var nameScope = new NameScope(); + Assert.Throws("item", () => nameScope.Contains(new KeyValuePair(null!, new object()))); + Assert.Throws("item", () => nameScope.Contains(default)); + } + + [Fact] + public void ContainsKey_Invoke_ReturnsExpected() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName("name", scopedElement); + + Assert.True(nameScope.ContainsKey("name")); + Assert.True(nameScope.ContainsKey("name")); + Assert.False(nameScope.ContainsKey("NAME")); + Assert.False(nameScope.ContainsKey("nAmE")); + Assert.False(nameScope.ContainsKey("name2")); + Assert.False(nameScope.ContainsKey("")); + } + + [Theory] + [InlineData("")] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void ContainsKey_InvokeNoSuchNameEmpty_ReturnsFalse(string name) + { + var nameScope = new NameScope(); + Assert.False(nameScope.ContainsKey(name)); + } + + [Theory] + [InlineData("")] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void ContainsKey_InvokeNoSuchNameNotEmpty_ReturnsFalse(string name) + { + var nameScope = new NameScope(); + nameScope.RegisterName("name1", new object()); + + Assert.False(nameScope.ContainsKey(name)); + } + + [Fact] + public void ContainsKey_NullKey_ThrowsArgumentNullException() + { + var nameScope = new NameScope(); + Assert.Throws("key", () => nameScope.ContainsKey(null!)); + } + + [Fact] + public void FindName_Invoke_ReturnsExpected() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName("name", scopedElement); + Assert.Same(scopedElement, nameScope.FindName("name")); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void FindName_InvokeNoSuchNameEmpty_ReturnsNull(string? name) + { + var nameScope = new NameScope(); + Assert.Null(nameScope.FindName(name)); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void FindName_InvokeNoSuchNameNotEmpty_ReturnsNull(string? name) + { + var nameScope = new NameScope(); + nameScope.RegisterName("name1", new object()); + + Assert.Null(nameScope.FindName(name)); + } + + [Fact] + public void GetEnumerator_InvokeIEnumerableKVPNotEmpty_ReturnsExpected() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName("name", scopedElement); + + IEnumerable> collection = nameScope; + + using IEnumerator> enumerator = collection.GetEnumerator(); + for (int i = 0; i < 2; i++) + { + Assert.Throws(() => enumerator.Current); + + // Move. + Assert.True(enumerator.MoveNext()); + Assert.Equal("name", enumerator.Current.Key); + Assert.Same(scopedElement, enumerator.Current.Value); + + // Move end. + Assert.False(enumerator.MoveNext()); + Assert.Throws(() => enumerator.Current); + + // Move again. + Assert.False(enumerator.MoveNext()); + Assert.Throws(() => enumerator.Current); + + // Reset. + enumerator.Reset(); + } + } + + [Fact] + public void GetEnumerator_InvokeIEnumerableKVPEmpty_ReturnsExpected() + { + var nameScope = new NameScope(); + IEnumerable> collection = nameScope; + + using IEnumerator> enumerator = collection.GetEnumerator(); + for (int i = 0; i < 2; i++) + { + Assert.Equal(default, enumerator.Current); + + // Move end. + Assert.False(enumerator.MoveNext()); + Assert.Equal(default, enumerator.Current); + + // Move again. + Assert.False(enumerator.MoveNext()); + Assert.Equal(default, enumerator.Current); + + // Reset. + enumerator.Reset(); + } + } + + [Fact] + public void GetEnumerator_InvokeIEnumerableNotEmpty_ReturnsExpected() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName("name", scopedElement); + + IEnumerable collection = nameScope; + + IEnumerator enumerator = collection.GetEnumerator(); + for (int i = 0; i < 2; i++) + { + Assert.Throws(() => enumerator.Current); + + // Move. + Assert.True(enumerator.MoveNext()); + Assert.Equal("name", ((KeyValuePair)enumerator.Current).Key); + Assert.Same(scopedElement, ((KeyValuePair)enumerator.Current).Value); + + // Move end. + Assert.False(enumerator.MoveNext()); + Assert.Throws(() => enumerator.Current); + + // Move again. + Assert.False(enumerator.MoveNext()); + Assert.Throws(() => enumerator.Current); + + // Reset. + enumerator.Reset(); + } + } + + [Fact] + public void GetEnumerator_InvokeIEnumerableEmpty_ReturnsExpected() + { + var nameScope = new NameScope(); + IEnumerable collection = nameScope; + + IEnumerator enumerator = collection.GetEnumerator(); + for (int i = 0; i < 2; i++) + { + Assert.Equal(default(KeyValuePair), enumerator.Current); + + // Move end. + Assert.False(enumerator.MoveNext()); + Assert.Equal(default(KeyValuePair), enumerator.Current); + + // Move again. + Assert.False(enumerator.MoveNext()); + Assert.Equal(default(KeyValuePair), enumerator.Current); + + // Reset. + enumerator.Reset(); + } + } + + [Theory] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void RegisterName_Invoke_Success(string name) + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName(name, scopedElement); + Assert.Equal(1, nameScope.Count); + Assert.Same(scopedElement, nameScope.FindName(name)); + Assert.True(nameScope.ContainsKey(name)); + Assert.True(nameScope.Contains(new KeyValuePair(name, scopedElement))); + Assert.Equal(1, nameScope.Keys.Count); + Assert.NotSame(nameScope.Keys, nameScope.Keys); + Assert.Equal(name, nameScope.Keys.First()); + Assert.Equal(1, nameScope.Values.Count); + Assert.NotSame(nameScope.Values, nameScope.Values); + Assert.Equal(scopedElement, nameScope.Values.First()); + + // Set same. + nameScope.RegisterName(name, scopedElement); + Assert.Equal(1, nameScope.Count); + Assert.Same(scopedElement, nameScope.FindName(name)); + Assert.True(nameScope.ContainsKey(name)); + Assert.True(nameScope.Contains(new KeyValuePair(name, scopedElement))); + Assert.Equal(1, nameScope.Keys.Count); + Assert.NotSame(nameScope.Keys, nameScope.Keys); + Assert.Equal(name, nameScope.Keys.First()); + Assert.Equal(1, nameScope.Values.Count); + Assert.NotSame(nameScope.Values, nameScope.Values); + Assert.Equal(scopedElement, nameScope.Values.First()); + } + + [Fact] + public void RegisterName_InvokeMultiple_Success() + { + var nameScope = new NameScope(); + var scopedElement1 = new object(); + var scopedElement2 = new object(); + + nameScope.RegisterName("name1", scopedElement1); + Assert.Same(scopedElement1, nameScope.FindName("name1")); + Assert.Equal(1, nameScope.Count); + + nameScope.RegisterName("NAME1", scopedElement2); + Assert.Same(scopedElement1, nameScope.FindName("name1")); + Assert.Same(scopedElement2, nameScope.FindName("NAME1")); + Assert.Equal(2, nameScope.Count); + + nameScope.RegisterName("name2", scopedElement2); + Assert.Same(scopedElement1, nameScope.FindName("name1")); + Assert.Same(scopedElement2, nameScope.FindName("NAME1")); + Assert.Same(scopedElement2, nameScope.FindName("name2")); + Assert.Equal(3, nameScope.Count); + } + + [Fact] + public void RegisterName_NullName_ThrowsArgumentNullException() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + Assert.Throws("name", () => nameScope.RegisterName(null, scopedElement)); + } + + [Fact] + public void RegisterName_EmptyName_ThrowsArgumentException() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + Assert.Throws(() => nameScope.RegisterName(string.Empty, scopedElement)); + } + + [Theory] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + public void RegisterName_InvalidName_ThrowsArgumentException(string name) + { + var nameScope = new NameScope(); + var scopedElement = new object(); + Assert.Throws(() => nameScope.RegisterName(name, scopedElement)); + } + + [Fact] + public void RegisterName_NullScopedElement_ThrowsArgumentNullException() + { + var nameScope = new NameScope(); + Assert.Throws("scopedElement", () => nameScope.RegisterName("name", null!)); + } + + [Fact] + public void RegisterName_Duplicate_ThrowsArgumentException() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName("name", scopedElement); + Assert.Throws(() => nameScope.RegisterName("name", new object())); + } + + [Fact] + public void Remove_InvokeString_Success() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName("name", scopedElement); + nameScope.Remove("name"); + Assert.Null(nameScope.FindName("name")); + Assert.Equal(0, nameScope.Count); + + // Remove again. + Assert.False(nameScope.Remove("name")); + Assert.Null(nameScope.FindName("name")); + Assert.Equal(0, nameScope.Count); + } + + [Fact] + public void Remove_NullKey_ThrowsArgumentNullException() + { + var nameScope = new NameScope(); + Assert.Throws("key", () => nameScope.Remove(null)); + } + + [Theory] + [InlineData("")] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void Remove_NoSuchKeyEmpty_ReturnsFalse(string name) + { + var nameScope = new NameScope(); + Assert.False(nameScope.Remove(name)); + } + + [Theory] + [InlineData("")] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void Remove_NoSuchKeyNotEmpty_ReturnsFalse(string name) + { + var nameScope = new NameScope(); + nameScope.RegisterName("name1", new object()); + + Assert.False(nameScope.Remove(name)); + Assert.Equal(1, nameScope.Count); + } + + [Fact] + public void Remove_InvokeKVPStringObject_Success() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName("name", scopedElement); + nameScope.Remove(new KeyValuePair("name", scopedElement)); + Assert.Null(nameScope.FindName("name")); + Assert.Equal(0, nameScope.Count); + + // Remove again. + Assert.False(nameScope.Remove(new KeyValuePair("name", scopedElement))); + Assert.Null(nameScope.FindName("name")); + Assert.Equal(0, nameScope.Count); + } + + [Fact] + public void Remove_NullItemKey_ThrowsArgumentException() + { + var nameScope = new NameScope(); + Assert.Throws("item", () => nameScope.Remove(new KeyValuePair(null!, new object()))); + } + + [Theory] + [InlineData("")] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void Remove_NoSuchItemKeyEmpty_ReturnsFalse(string name) + { + var nameScope = new NameScope(); + Assert.False(nameScope.Remove(new KeyValuePair(name, new object()))); + } + + [Theory] + [InlineData("")] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void Remove_NoSuchItemKeyNotEmpty_ReturnsFalse(string name) + { + var nameScope = new NameScope(); + nameScope.RegisterName("name1", new object()); + + Assert.False(nameScope.Remove(new KeyValuePair(name, new object()))); + Assert.Equal(1, nameScope.Count); + } + + [Fact] + public void Remove_NoSuchItemValue_ReturnsFalse() + { + var nameScope = new NameScope(); + nameScope.RegisterName("name", new object()); + + Assert.False(nameScope.Remove(new KeyValuePair("name", new object()))); + Assert.False(nameScope.Remove(new KeyValuePair("name", null!))); + Assert.Equal(1, nameScope.Count); + } + + [Fact] + public void TryGetValue_Invoke_ReturnsExpected() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName("name", scopedElement); + + Assert.True(nameScope.TryGetValue("name", out object value)); + Assert.Same(scopedElement, value); + } + + [Theory] + [InlineData("")] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void TryGetValue_InvokeNoSuchNameEmpty_ReturnsNull(string key) + { + var nameScope = new NameScope(); + Assert.False(nameScope.TryGetValue(key, out object value)); + Assert.Null(value); + } + + [Theory] + [InlineData("")] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void TryGetValue_InvokeNoSuchNameNotEmpty_ReturnsNull(string key) + { + var nameScope = new NameScope(); + nameScope.RegisterName("name1", new object()); + + Assert.False(nameScope.TryGetValue(key, out object value)); + Assert.Null(value); + } + + [Fact] + public void TryGetValue_NullKey_ThrowsArgumentNullException() + { + var nameScope = new NameScope(); + Assert.Throws("key", () => nameScope.TryGetValue(null, out object value)); + } + + [Fact] + public void UnregisterName_Invoke_Success() + { + var nameScope = new NameScope(); + var scopedElement = new object(); + nameScope.RegisterName("name", scopedElement); + nameScope.UnregisterName("name"); + Assert.Null(nameScope.FindName("name")); + Assert.Equal(0, nameScope.Count); + + // Unregister again. + Assert.Throws(() => nameScope.UnregisterName("name")); + Assert.Null(nameScope.FindName("name")); + Assert.Equal(0, nameScope.Count); + } + + [Fact] + public void UnregisterName_NullName_ThrowsArgumentNullException() + { + var nameScope = new NameScope(); + Assert.Throws("name", () => nameScope.UnregisterName(null)); + } + + [Fact] + public void UnregisterName_EmptyName_ThrowsArgumentException() + { + var nameScope = new NameScope(); + Assert.Throws(() => nameScope.UnregisterName(string.Empty)); + } + + [Theory] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void UnregisterName_NoSuchNameEmpty_ThrowsArgumentException(string name) + { + var nameScope = new NameScope(); + Assert.Throws(() => nameScope.UnregisterName(name)); + } + + [Theory] + [InlineData("0")] + [InlineData("0name")] + [InlineData("na\0me")] + [InlineData("name")] + [InlineData("NAME")] + [InlineData("_")] + [InlineData("na0me")] + [InlineData("_name")] + [InlineData("NaMe")] + public void UnregisterName_NoSuchNameNotEmpty_ThrowsArgumentException(string name) + { + var nameScope = new NameScope(); + nameScope.RegisterName("name1", new object()); + + Assert.Throws(() => nameScope.UnregisterName(name)); + Assert.Equal(1, nameScope.Count); + } + + private class CustomNameScope : INameScope + { + public object FindName(string name) => throw new NotImplementedException(); + + public void RegisterName(string name, object scopedElement) => throw new NotImplementedException(); + + public void UnregisterName(string name) => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/PointConverterTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/PointConverterTests.cs new file mode 100644 index 00000000000..19be3704221 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/PointConverterTests.cs @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.ComponentModel.Design.Serialization; +using System.Globalization; + +namespace System.Windows.Tests; + +public class PointConverterTests +{ + [Theory] + [InlineData(null, false)] + [InlineData(typeof(object), false)] + [InlineData(typeof(string), true)] + [InlineData(typeof(InstanceDescriptor), false)] + [InlineData(typeof(Point), false)] + public void CanConvertTo_Invoke_ReturnsExpected(Type? destinationType, bool expected) + { + var converter = new PointConverter(); + Assert.Equal(expected, converter.CanConvertTo(null, destinationType)); + Assert.Equal(expected, converter.CanConvertTo(new CustomTypeDescriptorContext(), destinationType)); + } + + [Theory] + [MemberData(nameof(PointTests.ToString_TestData), MemberType = typeof(PointTests))] + public void ConvertTo_InvokePointToString_ReturnsExpected(Point matrix, string expected) + { + var converter = new PointConverter(); + Assert.Equal(expected, converter.ConvertTo(matrix, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), null, matrix, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, matrix, typeof(string))); + } + + [Theory] + [MemberData(nameof(PointTests.ToString_IFormatProviderCustom_TestData), MemberType = typeof(PointTests))] + public void ConvertTo_InvokePointToStringCustomCulture_ReturnsExpected(Point matrix, string numberDecimalSeparator, string expected) + { + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + var converter = new PointConverter(); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), culture, matrix, typeof(string))); + } + + [Theory] + [InlineData(null, "")] + [InlineData("", "")] + [InlineData("value", "value")] + [InlineData(1, "1")] + public void ConvertTo_InvokeNotPointToString_ReturnsExpected(object? value, string expected) + { + var converter = new PointConverter(); + Assert.Equal(expected, converter.ConvertTo(value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + } + + public static IEnumerable ConvertTo_CantConvert_TestData() + { + yield return new object?[] { null, typeof(object) }; + yield return new object?[] { string.Empty, typeof(object) }; + yield return new object?[] { "value", typeof(object) }; + yield return new object?[] { new object(), typeof(object) }; + yield return new object?[] { new Point(), typeof(object) }; + + yield return new object?[] { null, typeof(Point) }; + yield return new object?[] { string.Empty, typeof(Point) }; + yield return new object?[] { "value", typeof(Point) }; + yield return new object?[] { new object(), typeof(Point) }; + yield return new object?[] { new Point(), typeof(Point) }; + } + + [Theory] + [MemberData(nameof(ConvertTo_CantConvert_TestData))] + public void ConvertTo_CantConvert_ThrowsNotSupportedException(object value, Type destinationType) + { + var converter = new PointConverter(); + Assert.Throws(() => converter.ConvertTo(value, destinationType)); + Assert.Throws(() => converter.ConvertTo(null, null, value, destinationType)); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, destinationType)); + } + + public static IEnumerable ConvertTo_NullDestinationType_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + yield return new object?[] { new object() }; + yield return new object?[] { new Point() }; + } + + [Theory] + [MemberData(nameof(ConvertTo_NullDestinationType_TestData))] + public void ConvertTo_NullDestinationType_ThrowsArgumentNullException(object value) + { + var converter = new PointConverter(); + Assert.Throws("destinationType", () => converter.ConvertTo(value, null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(null, null, new Point(), null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, new Point(), null!)); + } + + [Theory] + [InlineData(null, false)] + [InlineData(typeof(object), false)] + [InlineData(typeof(string), true)] + [InlineData(typeof(InstanceDescriptor), true)] + [InlineData(typeof(Point), false)] + public void CanConvertFrom_Invoke_ReturnsExpected(Type? sourceType, bool expected) + { + var converter = new PointConverter(); + Assert.Equal(expected, converter.CanConvertFrom(sourceType!)); + Assert.Equal(expected, converter.CanConvertFrom(null, sourceType)); + Assert.Equal(expected, converter.CanConvertFrom(new CustomTypeDescriptorContext(), sourceType)); + } + + [Theory] + [MemberData(nameof(PointTests.Parse_TestData), MemberType = typeof(PointTests))] + public void ConvertFrom_InvokeStringValue_ReturnsExpected(object value, Point expected) + { + var converter = new PointConverter(); + Assert.Equal(expected, converter.ConvertFrom(value)); + Assert.Equal(expected, converter.ConvertFrom(null, null, value)); + Assert.Equal(expected, converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Fact] + public void ConvertFrom_NullValue_ThrowsNotSupportedException() + { + var converter = new PointConverter(); + Assert.Throws(() => converter.ConvertFrom(null!)); + Assert.Throws(() => converter.ConvertFrom(null, null, null)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, null)); + } + + [Theory] + [MemberData(nameof(PointTests.Parse_InvalidSource_TestData), MemberType = typeof(PointTests))] + public void ConvertFrom_InvalidValue_ThrowsInvalidOperationException(object value) + { + var converter = new PointConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Theory] + [MemberData(nameof(PointTests.Parse_NotDouble_TestData), MemberType = typeof(PointTests))] + public void ConvertFrom_NotDoubleStringValue_ThrowsFormatException(object value) + { + var converter = new PointConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + public static IEnumerable ConvertFrom_CantConvert_TestData() + { + yield return new object[] { new object() }; + yield return new object[] { new Point() }; + } + + [Theory] + [MemberData(nameof(ConvertFrom_CantConvert_TestData))] + public void ConvertFrom_CantConvert_ThrowsNotSupportedException(object value) + { + var converter = new PointConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + private class CustomTypeDescriptorContext : ITypeDescriptorContext + { + public IContainer Container => throw new NotImplementedException(); + + public object Instance => throw new NotImplementedException(); + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/PointTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/PointTests.cs new file mode 100644 index 00000000000..d100d5b2846 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/PointTests.cs @@ -0,0 +1,580 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Globalization; +using System.Windows.Converters; +using System.Windows.Markup; +using System.Windows.Media; +using System.Windows.Media.Tests; + +namespace System.Windows.Tests; + +public class PointTests +{ + [Fact] + public void Ctor_Default() + { + var point = new Point(); + Assert.Equal(0, point.X); + Assert.Equal(0, point.Y); + } + + [Theory] + [InlineData(double.NegativeInfinity, double.NegativeInfinity)] + [InlineData(double.MinValue, double.MinValue)] + [InlineData(-1, -2)] + [InlineData(0, 0)] + [InlineData(1, 0)] + [InlineData(0, 2)] + [InlineData(0.1, 0.2)] + [InlineData(double.NegativeZero, double.NegativeZero)] + [InlineData(1, 2)] + [InlineData(double.MaxValue, double.MaxValue)] + [InlineData(double.PositiveInfinity, double.PositiveInfinity)] + [InlineData(double.NaN, double.NaN)] + [InlineData(double.NaN, 1)] + [InlineData(1, double.NaN)] + public void Ctor_Double_Double(double x, double y) + { + var point = new Point(x, y); + Assert.Equal(x, point.X); + Assert.Equal(y, point.Y); + } + + [Theory] + [InlineData(double.NegativeInfinity)] + [InlineData(double.MinValue)] + [InlineData(-1)] + [InlineData(double.NegativeZero)] + [InlineData(0)] + [InlineData(0.2)] + [InlineData(1)] + [InlineData(double.MaxValue)] + [InlineData(double.PositiveInfinity)] + [InlineData(double.NaN)] + public void X_Set_GetReturnsExpected(double value) + { + var point = new Point(); + + // Set. + point.X = value; + Assert.Equal(value, point.X); + + // Set same. + point.X = value; + Assert.Equal(value, point.X); + } + + [Theory] + [InlineData(double.NegativeInfinity)] + [InlineData(double.MinValue)] + [InlineData(-1)] + [InlineData(double.NegativeZero)] + [InlineData(0)] + [InlineData(0.2)] + [InlineData(1)] + [InlineData(double.MaxValue)] + [InlineData(double.PositiveInfinity)] + [InlineData(double.NaN)] + public void Y_Set_GetReturnsExpected(double value) + { + var point = new Point(); + + // Set. + point.Y = value; + Assert.Equal(value, point.Y); + + // Set same. + point.Y = value; + Assert.Equal(value, point.Y); + } + + public static IEnumerable Add_TestData() + { + yield return new object[] { new Point(1, 2), 1, 2, new Point(2, 4) }; + yield return new object[] { new Point(1, 2), 0.1, 0.2, new Point(1.1, 2.2) }; + yield return new object[] { new Point(1.2, 2.3), 0.1, 0.2, new Point(1.3, 2.5) }; + yield return new object[] { new Point(1, 2), 0, 0, new Point(1, 2) }; + yield return new object[] { new Point(1, 2), -1, -2, new Point(0, 0) }; + + yield return new object[] { new Point(1.2, 2.3), double.NegativeInfinity, double.NegativeInfinity, new Point(double.NegativeInfinity, double.NegativeInfinity) }; + yield return new object[] { new Point(1.2, 2.3), double.NegativeInfinity, 0.2, new Point(double.NegativeInfinity, 2.5) }; + yield return new object[] { new Point(1.2, 2.3), 0.1, double.NegativeInfinity, new Point(1.3, double.NegativeInfinity) }; + + yield return new object[] { new Point(-1.2, -2.3), double.MinValue, double.MinValue, new Point(double.MinValue, double.MinValue) }; + yield return new object[] { new Point(-1.2, -2.3), double.MinValue, 0.2, new Point(double.MinValue, -2.1) }; + yield return new object[] { new Point(-1.2, -2.3), 0.1, double.MinValue, new Point(-1.1, double.MinValue) }; + + yield return new object[] { new Point(1.2, 2.3), double.MaxValue, double.MaxValue, new Point(double.MaxValue, double.MaxValue) }; + yield return new object[] { new Point(1.2, 2.3), double.MaxValue, 0.2, new Point(double.MaxValue, 2.5) }; + yield return new object[] { new Point(1.2, 2.3), 0.1, double.MaxValue, new Point(1.3, double.MaxValue) }; + + yield return new object[] { new Point(1.2, 2.3), double.PositiveInfinity, double.PositiveInfinity, new Point(double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Point(1.2, 2.3), double.PositiveInfinity, 0.2, new Point(double.PositiveInfinity, 2.5) }; + yield return new object[] { new Point(1.2, 2.3), 0.1, double.PositiveInfinity, new Point(1.3, double.PositiveInfinity) }; + + yield return new object[] { new Point(1.2, 2.3), double.NaN, double.NaN, new Point(double.NaN, double.NaN) }; + yield return new object[] { new Point(1.2, 2.3), double.NaN, 0.2, new Point(double.NaN, 2.5) }; + yield return new object[] { new Point(1.2, 2.3), 0.1, double.NaN, new Point(1.3, double.NaN) }; + + yield return new object[] { new Point(), 1, 2, new Point(1, 2) }; + yield return new object[] { new Point(), 0, 0, new Point() }; + yield return new object[] { new Point(), -1, -2, new Point(-1, -2) }; + } + + [Theory] + [MemberData(nameof(Add_TestData))] + public void Add_InvokeVector_ReturnsExpected(Point point, double x, double y, Point expected) + { + Vector vector = new Vector(x, y); + Point result = Point.Add(point, vector); + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + } + + [Theory] + [MemberData(nameof(Add_TestData))] + public void OperatorAdd_InvokeVector_ReturnsExpected(Point point, double x, double y, Point expected) + { + Vector vector = new Vector(x, y); + Point result = point + vector; + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + } + + public static IEnumerable Equals_TestData() + { + // Normal size. + yield return new object?[] { new Point(1, 2), new Point(1, 2), true }; + yield return new object?[] { new Point(1, 2), new Point(2, 2), false }; + yield return new object?[] { new Point(1, 2), new Point(1, 3), false }; + yield return new object?[] { new Point(1, 2), new Point(double.NaN, 2), false }; + yield return new object?[] { new Point(1, 2), new Point(1, double.NaN), false }; + yield return new object?[] { new Point(1, 2), new Point(double.NaN, double.NaN), false }; + yield return new object?[] { new Point(1, 2), new Point(0, 0), false }; + yield return new object?[] { new Point(1, 2), new Point(), false }; + + // NaN x. + yield return new object[] { new Point(double.NaN, 2), new Point(double.NaN, 2), true }; + yield return new object[] { new Point(double.NaN, 2), new Point(2, 2), false }; + yield return new object[] { new Point(double.NaN, 2), new Point(double.NaN, double.NaN), false }; + + // NaN y. + yield return new object[] { new Point(1, double.NaN), new Point(1, double.NaN), true }; + yield return new object[] { new Point(1, double.NaN), new Point(1, 2), false }; + yield return new object[] { new Point(1, double.NaN), new Point(double.NaN, double.NaN), false }; + + // NaN x & y. + yield return new object[] { new Point(double.NaN, double.NaN), new Point(double.NaN, double.NaN), true }; + yield return new object[] { new Point(double.NaN, double.NaN), new Point(1, 2), false }; + yield return new object[] { new Point(double.NaN, double.NaN), new Point(double.NaN, 2), false }; + yield return new object[] { new Point(double.NaN, double.NaN), new Point(1, double.NaN), false }; + + // Zero. + yield return new object?[] { new Point(0, 0), new Point(), true }; + yield return new object?[] { new Point(0, 0), new Point(0, 0), true }; + yield return new object?[] { new Point(0, 0), new Point(1, 0), false }; + yield return new object?[] { new Point(0, 0), new Point(0, 1), false }; + + // Default. + yield return new object?[] { new Point(), new Point(), true }; + yield return new object?[] { new Point(), new Point(0, 0), true }; + yield return new object?[] { new Point(), new Point(1, 0), false }; + yield return new object?[] { new Point(), new Point(0, 1), false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void Equals_Object_ReturnsExpected(Point point, object o, bool expected) + { + Assert.Equal(expected, point.Equals(o)); + if (o is Point other) + { + Assert.Equal(expected, point.Equals(other)); + Assert.Equal(expected, other.Equals(point)); + Assert.Equal(expected, Point.Equals(point, other)); + Assert.Equal(expected, Point.Equals(other, point)); + Assert.Equal(expected, point.GetHashCode().Equals(other.GetHashCode())); + } + } + + public static IEnumerable EqualityOperator_TestData() + { + // Normal size. + yield return new object[] { new Point(1, 2), new Point(1, 2), true }; + yield return new object[] { new Point(1, 2), new Point(2, 2), false }; + yield return new object[] { new Point(1, 2), new Point(1, 3), false }; + yield return new object[] { new Point(1, 2), new Point(double.NaN, 2), false }; + yield return new object[] { new Point(1, 2), new Point(1, double.NaN), false }; + yield return new object[] { new Point(1, 2), new Point(double.NaN, double.NaN), false }; + yield return new object[] { new Point(1, 2), new Point(0, 0), false }; + yield return new object[] { new Point(1, 2), new Point(), false }; + + // NaN x. + yield return new object[] { new Point(double.NaN, 2), new Point(double.NaN, 2), false }; + yield return new object[] { new Point(double.NaN, 2), new Point(2, 2), false }; + yield return new object[] { new Point(double.NaN, 2), new Point(double.NaN, double.NaN), false }; + + // NaN y. + yield return new object[] { new Point(1, double.NaN), new Point(1, double.NaN), false }; + yield return new object[] { new Point(1, double.NaN), new Point(1, 2), false }; + yield return new object[] { new Point(1, double.NaN), new Point(double.NaN, double.NaN), false }; + + // NaN x & y. + yield return new object[] { new Point(double.NaN, double.NaN), new Point(double.NaN, double.NaN), false }; + yield return new object[] { new Point(double.NaN, double.NaN), new Point(1, 2), false }; + yield return new object[] { new Point(double.NaN, double.NaN), new Point(double.NaN, 2), false }; + yield return new object[] { new Point(double.NaN, double.NaN), new Point(1, double.NaN), false }; + + // Zero. + yield return new object[] { new Point(0, 0), new Point(), true }; + yield return new object[] { new Point(0, 0), new Point(0, 0), true }; + yield return new object[] { new Point(0, 0), new Point(1, 0), false }; + yield return new object[] { new Point(0, 0), new Point(0, 1), false }; + + // Default. + yield return new object[] { new Point(), new Point(), true }; + yield return new object[] { new Point(), new Point(0, 0), true }; + yield return new object[] { new Point(), new Point(1, 0), false }; + yield return new object[] { new Point(), new Point(0, 1), false }; + } + + [Theory] + [MemberData(nameof(EqualityOperator_TestData))] + public void EqualityOperator_Invoke_ReturnsExpected(Point point1, Point point2, bool expected) + { + Assert.Equal(expected, point1 == point2); + Assert.Equal(expected, point2 == point1); + Assert.Equal(!expected, point1 != point2); + Assert.Equal(!expected, point2 != point1); + } + + [Fact] + public void GetHashCode_InvokeDefault_ReturnsEqual() + { + var point = new Point(0, 0); + Assert.Equal(0, point.GetHashCode()); + Assert.Equal(point.GetHashCode(), point.GetHashCode()); + } + + [Fact] + public void GetHashCode_InvokeNormal_ReturnsEqual() + { + var point = new Point(1, 2); + Assert.NotEqual(0, point.GetHashCode()); + Assert.Equal(point.GetHashCode(), point.GetHashCode()); + } + + [Theory] + [MemberData(nameof(MatrixTests.Transform_Point_TestData), MemberType = typeof(MatrixTests))] + public void Multiply_InvokeMatrix_ReturnsExpected(Matrix matrix, Point point, Point expected) + { + Point result = Point.Multiply(point, matrix); + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + } + + [Theory] + [MemberData(nameof(MatrixTests.Transform_Point_TestData), MemberType = typeof(MatrixTests))] + public void OperatorMultiply_InvokeMatrix_ReturnsExpected(Matrix matrix, Point point, Point expected) + { + Point result = point * matrix; + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + } + + [Theory] + [MemberData(nameof(Add_TestData))] + public void Offset_Invoke_Success(Point point, double x, double y, Point expected) + { + point.Offset(x, y); + Assert.Equal(expected.X, point.X, precision: 5); + Assert.Equal(expected.Y, point.Y, precision: 5); + } + + public static IEnumerable Subtract_TestData() + { + yield return new object[] { new Point(1, 2), 1, 2, new Point(0, 0) }; + yield return new object[] { new Point(1, 2), 0.1, 0.2, new Point(0.9, 1.8) }; + yield return new object[] { new Point(1, 2), 0, 0, new Point(1, 2) }; + yield return new object[] { new Point(1, 2), -1, -2, new Point(2, 4) }; + + yield return new object[] { new Point(-1, -2), 1, 2, new Point(-2, -4) }; + yield return new object[] { new Point(-1, -2), 0.1, 0.2, new Point(-1.1, -2.2) }; + yield return new object[] { new Point(-1, -2), 0, 0, new Point(-1, -2) }; + yield return new object[] { new Point(-1, -2), -1, -2, new Point(0, 0) }; + + yield return new object[] { new Point(), 1, 2, new Point(-1, -2) }; + yield return new object[] { new Point(), 0, 0, new Point() }; + yield return new object[] { new Point(), -1, -2, new Point(1, 2) }; + } + + [Theory] + [MemberData(nameof(Subtract_TestData))] + public void Subtract_InvokePoint_ReturnsExpected(Point point, double x, double y, Point expected) + { + var other = new Point(x, y); + Vector result = Point.Subtract(point, other); + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + Assert.Equal(new Vector(-result.X, -result.Y), Point.Subtract(other, point)); + } + + [Theory] + [MemberData(nameof(Subtract_TestData))] + public void OperatorSubtract_InvokePoint_ReturnsExpected(Point point, double x, double y, Point expected) + { + var other = new Point(x, y); + Vector result = point - other; + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + Assert.Equal(new Vector(-result.X, -result.Y), other - point); + } + + [Theory] + [MemberData(nameof(Subtract_TestData))] + public void Subtract_InvokeVector_ReturnsExpected(Point point, double x, double y, Point expected) + { + Vector vector = new Vector(x, y); + Point result = Point.Subtract(point, vector); + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + } + + [Theory] + [MemberData(nameof(Subtract_TestData))] + public void OperatorSubtract_InvokeVector_ReturnsExpected(Point point, double x, double y, Point expected) + { + Vector vector = new Vector(x, y); + Point result = point - vector; + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + } + + public static IEnumerable Parse_TestData() + { + yield return new object[] { "0,0", new Point(0, 0) }; + yield return new object[] { "1,2", new Point(1, 2) }; + yield return new object[] { "1.1,2.2", new Point(1.1, 2.2) }; + yield return new object[] { " 1 , 2 ", new Point(1, 2) }; + yield return new object[] { "-1,-2", new Point(-1, -2) }; + } + + [Theory] + [MemberData(nameof(Parse_TestData))] + public void Parse_Invoke_Success(string source, Point expected) + { + Point result = Point.Parse(source); + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + } + + [Fact] + public void Parse_NullSource_ThrowsInvalidOperationException() + { + Assert.Throws(() => Point.Parse(null)); + } + + public static IEnumerable Parse_InvalidSource_TestData() + { + yield return new object[] { "" }; + yield return new object[] { " " }; + yield return new object[] { "," }; + yield return new object[] { "1" }; + yield return new object[] { "1," }; + yield return new object[] { "1,2," }; + yield return new object[] { "1,2,3" }; + yield return new object[] { "1,2,test" }; + yield return new object[] { "Empty," }; + yield return new object[] { "Identity," }; + } + + [Theory] + [MemberData(nameof(Parse_InvalidSource_TestData))] + public void Parse_InvalidSource_ThrowsInvalidOperationException(string source) + { + Assert.Throws(() => Point.Parse(source)); + } + + public static IEnumerable Parse_NotDouble_TestData() + { + yield return new object[] { "Empty" }; + yield return new object[] { " Empty " }; + yield return new object[] { "Empty,2" }; + yield return new object[] { "Identity" }; + yield return new object[] { " Identity " }; + yield return new object[] { "Identity,2" }; + yield return new object[] { "test" }; + yield return new object[] { "test,2" }; + yield return new object[] { "1,test" }; + yield return new object[] { "1,test,3" }; + yield return new object[] { "1;2" }; + yield return new object[] { "1.2.3" }; + yield return new object[] { """1"",""2""" }; + } + + [Theory] + [MemberData(nameof(Parse_NotDouble_TestData))] + public void Parse_NotDouble_ThrowsFormatException(string source) + { + Assert.Throws(() => Point.Parse(source)); + } + + public static IEnumerable ToString_TestData() + { + yield return new object[] { new Point(), "0,0" }; + yield return new object[] { new Point(0, 0), "0,0" }; + yield return new object[] { new Point(1, 2), "1,2" }; + yield return new object[] { new Point(1.1, 2.2), "1.1,2.2" }; + yield return new object[] { new Point(-1, -2), "-1,-2" }; + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_Invoke_ReturnsExpected(Point point, string expected) + { + Assert.Equal(expected, point.ToString()); + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_InvokeIFormatProviderInvariantCulture_ReturnsExpected(Point point, string expected) + { + Assert.Equal(expected, point.ToString(CultureInfo.InvariantCulture)); + } + + public static IEnumerable ToString_IFormatProviderCustom_TestData() + { + yield return new object[] { new Point(), "|", "0,0" }; + yield return new object[] { new Point(), "|_", "0,0" }; + yield return new object[] { new Point(), ",_", "0;0" }; + yield return new object[] { new Point(), ",", "0;0" }; + yield return new object[] { new Point(), ";", "0,0" }; + yield return new object[] { new Point(), " ", "0,0" }; + + yield return new object[] { new Point(0, 0), "|", "0,0" }; + yield return new object[] { new Point(0, 0), "|_", "0,0" }; + yield return new object[] { new Point(0, 0), ",_", "0;0" }; + yield return new object[] { new Point(0, 0), ",", "0;0" }; + yield return new object[] { new Point(0, 0), ";", "0,0" }; + yield return new object[] { new Point(0, 0), " ", "0,0" }; + + yield return new object[] { new Point(1, 2), "|", "1,2" }; + yield return new object[] { new Point(1, 2), "|_", "1,2" }; + yield return new object[] { new Point(1, 2), ",_", "1;2" }; + yield return new object[] { new Point(1, 2), ",", "1;2" }; + yield return new object[] { new Point(1, 2), ";", "1,2" }; + yield return new object[] { new Point(1, 2), " ", "1,2" }; + + yield return new object[] { new Point(1.1, 2.2), "|", "1|1,2|2" }; + yield return new object[] { new Point(1.1, 2.2), "|_", "1|_1,2|_2" }; + yield return new object[] { new Point(1.1, 2.2), ",_", "1,_1;2,_2" }; + yield return new object[] { new Point(1.1, 2.2), ",", "1,1;2,2" }; + yield return new object[] { new Point(1.1, 2.2), ";", "1;1,2;2" }; + yield return new object[] { new Point(1.1, 2.2), " ", "1 1,2 2" }; + + yield return new object[] { new Point(-1, -2), "|", "-1,-2" }; + yield return new object[] { new Point(-1, -2), "|_", "-1,-2" }; + yield return new object[] { new Point(-1, -2), ",_", "-1;-2" }; + yield return new object[] { new Point(-1, -2), ",", "-1;-2" }; + yield return new object[] { new Point(-1, -2), ";", "-1,-2" }; + yield return new object[] { new Point(-1, -2), " ", "-1,-2" }; + } + + [Theory] + [MemberData(nameof(ToString_IFormatProviderCustom_TestData))] + public void ToString_InvokeIFormatProviderCustom_ReturnsExpected(Point point, string numberDecimalSeparator, string expected) + { + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + Assert.Equal(expected, point.ToString(formatInfo)); + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_InvokeIFormattableInvariantCulture_ReturnsExpected(Point point, string expected) + { + IFormattable formattable = point; + + Assert.Equal(expected, formattable.ToString(null, null)); + Assert.Equal(expected, formattable.ToString(null, CultureInfo.InvariantCulture)); + } + + public static IEnumerable ToString_IFormattableCustomFormat_TestData() + { + yield return new object[] { "|", "1|23,2|35" }; + yield return new object[] { "|_", "1|_23,2|_35" }; + yield return new object[] { ",_", "1,_23;2,_35" }; + yield return new object[] { ",", "1,23;2,35" }; + yield return new object[] { ";", "1;23,2;35" }; + yield return new object[] { " ", "1 23,2 35" }; + } + + [Theory] + [MemberData(nameof(ToString_IFormattableCustomFormat_TestData))] + public void ToString_InvokeIFormattableCustomFormat_ReturnsExpected(string numberDecimalSeparator, string expected) + { + var point = new Point(1.23456, 2.34567); + IFormattable formattable = point; + + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + Assert.Equal(expected, formattable.ToString("F2", formatInfo)); + } + + [Fact] + public void OperatorConvertVector_InvokeEmpty_ReturnsExpected() + { + Vector result = (Vector)new Point(); + Assert.Equal(0, result.X); + Assert.Equal(0, result.Y); + } + + [Theory] + [InlineData(1, 2)] + [InlineData(1.1, 2.2)] + [InlineData(-1, -2)] + public void OperatorConvertVector_InvokeNormal_ReturnsExpected(double x, double y) + { + Vector result = (Vector)new Point(x, y); + Assert.Equal(x, result.X); + Assert.Equal(y, result.Y); + } + + [Fact] + public void OperatorConvertSize_InvokeEmpty_ReturnsExpected() + { + Size result = (Size)new Point(); + Assert.Equal(0, result.Width); + Assert.Equal(0, result.Height); + } + + [Theory] + [InlineData(1, 2, 1, 2)] + [InlineData(1.1, 2.2, 1.1, 2.2)] + [InlineData(-1, -2, 1, 2)] + public void OperatorConvertSize_InvokeNormal_ReturnsExpected(double x, double y, double expectedWidth, double expectedHeight) + { + Size result = (Size)new Point(x, y); + Assert.Equal(expectedWidth, result.Width); + Assert.Equal(expectedHeight, result.Height); + } + + [Fact] + public void TypeConverter_Get_ReturnsExpected() + { + Assert.IsType(TypeDescriptor.GetConverter(typeof(Point))); + } + + [Fact] + public void ValueSerializer_Get_ReturnsExpected() + { + Assert.IsType(ValueSerializer.GetSerializerFor(typeof(Point))); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/PropertyMetadataTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/PropertyMetadataTests.cs new file mode 100644 index 00000000000..e6cc37f8b03 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/PropertyMetadataTests.cs @@ -0,0 +1,558 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; + +namespace System.Windows.Tests; + +public class PropertyMetadataTests +{ + [Fact] + public void Ctor_Default() + { + var metadata = new SubPropertyMetadata(); + Assert.Null(metadata.CoerceValueCallback); + Assert.Null(metadata.DefaultValue); + Assert.False(metadata.IsSealed); + Assert.Null(metadata.PropertyChangedCallback); + } + + [Theory] + [InlineData(null)] + [InlineData("value")] + [InlineData(1)] + public void Ctor_Object(object? defaultValue) + { + var metadata = new SubPropertyMetadata(defaultValue!); + Assert.Null(metadata.CoerceValueCallback); + Assert.Equal(defaultValue, metadata.DefaultValue); + Assert.False(metadata.IsSealed); + Assert.Null(metadata.PropertyChangedCallback); + } + + [Fact] + public void Ctor_PropertyChangedCallback() + { + int propertyChangedCallbackCallCount = 0; + PropertyChangedCallback propertyChangedCallback = (d, e) => propertyChangedCallbackCallCount++; + var metadata = new SubPropertyMetadata(propertyChangedCallback); + Assert.Null(metadata.CoerceValueCallback); + Assert.Null(metadata.DefaultValue); + Assert.False(metadata.IsSealed); + Assert.Equal(propertyChangedCallback, metadata.PropertyChangedCallback); + Assert.Equal(0, propertyChangedCallbackCallCount); + } + + [Fact] + public void Ctor_NullPropertyChangedCallback() + { + var metadata = new SubPropertyMetadata((PropertyChangedCallback)null!); + Assert.Null(metadata.CoerceValueCallback); + Assert.Null(metadata.DefaultValue); + Assert.False(metadata.IsSealed); + Assert.Null(metadata.PropertyChangedCallback); + } + + [Theory] + [InlineData(null)] + [InlineData("value")] + [InlineData(1)] + public void Ctor_Object_PropertyChangedCallback(object? defaultValue) + { + int propertyChangedCallbackCallCount = 0; + PropertyChangedCallback propertyChangedCallback = (d, e) => propertyChangedCallbackCallCount++; + var metadata = new SubPropertyMetadata(defaultValue!, propertyChangedCallback); + Assert.Null(metadata.CoerceValueCallback); + Assert.Equal(defaultValue, metadata.DefaultValue); + Assert.False(metadata.IsSealed); + Assert.Equal(propertyChangedCallback, metadata.PropertyChangedCallback); + Assert.Equal(0, propertyChangedCallbackCallCount); + } + + [Theory] + [InlineData(null)] + [InlineData("value")] + [InlineData(1)] + public void Ctor_Object_NullPropertyChangedCallback(object? defaultValue) + { + var metadata = new SubPropertyMetadata(defaultValue!, (PropertyChangedCallback)null!); + Assert.Null(metadata.CoerceValueCallback); + Assert.False(metadata.IsSealed); + Assert.Equal(defaultValue, metadata.DefaultValue); + Assert.Null(metadata.PropertyChangedCallback); + } + + [Theory] + [InlineData(null)] + [InlineData("value")] + [InlineData(1)] + public void Ctor_Object_PropertyChangedCallback_CoerceValueCallback(object? defaultValue) + { + int propertyChangedCallbackCallCount = 0; + int coerceValueCallbackCallCount = 0; + PropertyChangedCallback propertyChangedCallback = (d, e) => propertyChangedCallbackCallCount++; + CoerceValueCallback coerceValueCallback = (d, v) => coerceValueCallbackCallCount++; + var metadata = new SubPropertyMetadata(defaultValue!, propertyChangedCallback, coerceValueCallback); + Assert.Equal(coerceValueCallback, metadata.CoerceValueCallback); + Assert.Equal(defaultValue, metadata.DefaultValue); + Assert.False(metadata.IsSealed); + Assert.Equal(propertyChangedCallback, metadata.PropertyChangedCallback); + Assert.Equal(0, propertyChangedCallbackCallCount); + Assert.Equal(0, coerceValueCallbackCallCount); + } + + [Theory] + [InlineData(null)] + [InlineData("value")] + [InlineData(1)] + public void Ctor_Object_NullPropertyChangedCallback_NullCoerceValueCallback(object? defaultValue) + { + var metadata = new SubPropertyMetadata(defaultValue!, (PropertyChangedCallback)null!, (CoerceValueCallback)null!); + Assert.Null(metadata.CoerceValueCallback); + Assert.Equal(defaultValue, metadata.DefaultValue); + Assert.False(metadata.IsSealed); + Assert.Null(metadata.PropertyChangedCallback); + } + + [Fact] + public void Ctor_UnsetDefaultValue_ThrowsArgumentException() + { + // TODO: add paramName. + Assert.Throws(() => new PropertyMetadata(DependencyProperty.UnsetValue)); + Assert.Throws(() => new PropertyMetadata(DependencyProperty.UnsetValue, null)); + Assert.Throws(() => new PropertyMetadata(DependencyProperty.UnsetValue, null, null)); + } + + [Fact] + public void CoerceValueCallback_Set_GetReturnsExpected() + { + var metadata = new PropertyMetadata(); + + // Set value. + int coerceValueCallbackCallCount = 0; + CoerceValueCallback coerceValueCallback = (d, e) => coerceValueCallbackCallCount++; + metadata.CoerceValueCallback = coerceValueCallback; + Assert.Equal(coerceValueCallback, metadata.CoerceValueCallback); + Assert.Equal(0, coerceValueCallbackCallCount); + + // Set same. + metadata.CoerceValueCallback = coerceValueCallback; + Assert.Equal(coerceValueCallback, metadata.CoerceValueCallback); + Assert.Equal(0, coerceValueCallbackCallCount); + + // Set null. + metadata.CoerceValueCallback = null; + Assert.Null(metadata.CoerceValueCallback); + Assert.Equal(0, coerceValueCallbackCallCount); + } + + [Fact] + public void CoerceValueCallback_SetSealed_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.Register(nameof(PropertyMetadataTests) + MethodBase.GetCurrentMethod()!.Name, typeof(int), typeof(DependencyObjectTests)); + PropertyMetadata metadata = property.DefaultMetadata; + Assert.Throws(() => metadata.CoerceValueCallback = (d, v) => v); + } + + [Theory] + [InlineData(null)] + [InlineData("value")] + [InlineData(1)] + public void DefaultValue_Set_GetReturnsExpected(object? value) + { + var metadata = new PropertyMetadata(); + + // Set value. + metadata.DefaultValue = value; + Assert.Equal(value, metadata.DefaultValue); + + // Set same. + metadata.DefaultValue = value; + Assert.Equal(value, metadata.DefaultValue); + } + + [Theory] + [InlineData(null)] + [InlineData("value")] + [InlineData(1)] + public void DefaultValue_SetDifferent_GetReturnsExpected(object? value) + { + var metadata = new PropertyMetadata + { + DefaultValue = new object() + }; + + // Set value. + metadata.DefaultValue = value; + Assert.Equal(value, metadata.DefaultValue); + + // Set same. + metadata.DefaultValue = value; + Assert.Equal(value, metadata.DefaultValue); + } + + [Fact] + public void DefaultValue_SetUnset_ThrowsArgumentException() + { + var metadata = new PropertyMetadata(); + // TODO: add paramName. + Assert.Throws(() => metadata.DefaultValue = DependencyProperty.UnsetValue); + } + + [Fact] + public void DefaultValue_SetSealed_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.Register(nameof(PropertyMetadataTests) + MethodBase.GetCurrentMethod()!.Name, typeof(int), typeof(DependencyObjectTests)); + PropertyMetadata metadata = property.DefaultMetadata; + Assert.Throws(() => metadata.DefaultValue = new object()); + } + + [Fact] + public void IsSealed_GetSealed_ReturnsTrue() + { + DependencyProperty property = DependencyProperty.RegisterAttached(nameof(PropertyMetadataTests) + MethodBase.GetCurrentMethod()!.Name, typeof(bool), typeof(DependencyObject), new SubPropertyMetadata()); + SubPropertyMetadata metadata = Assert.IsType(property.DefaultMetadata); + Assert.True(metadata.IsSealed); + } + + [Fact] + public void PropertyChangedCallback_Set_GetReturnsExpected() + { + var metadata = new PropertyMetadata(); + + // Set value. + int propertyChangedCallbackCallCount = 0; + PropertyChangedCallback propertyChangedCallback = (d, e) => propertyChangedCallbackCallCount++; + metadata.PropertyChangedCallback = propertyChangedCallback; + Assert.Equal(propertyChangedCallback, metadata.PropertyChangedCallback); + Assert.Equal(0, propertyChangedCallbackCallCount); + + // Set same. + metadata.PropertyChangedCallback = propertyChangedCallback; + Assert.Equal(propertyChangedCallback, metadata.PropertyChangedCallback); + Assert.Equal(0, propertyChangedCallbackCallCount); + + // Set null. + metadata.PropertyChangedCallback = null; + Assert.Null(metadata.PropertyChangedCallback); + Assert.Equal(0, propertyChangedCallbackCallCount); + } + + [Fact] + public void PropertyChangedCallback_SetSealed_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.Register(nameof(PropertyMetadataTests) + MethodBase.GetCurrentMethod()!.Name, typeof(int), typeof(DependencyObjectTests)); + PropertyMetadata metadata = property.DefaultMetadata; + Assert.Throws(() => metadata.PropertyChangedCallback = (d, e) => { }); + } + + [Fact] + public void Merge_InvokeUnchangedOnUnchanged_Success() + { + var metadata = new SubPropertyMetadata(); + var baseMetadata = new PropertyMetadata(); + + metadata.Merge(baseMetadata, null!); + Assert.Null(metadata.CoerceValueCallback); + Assert.Null(metadata.DefaultValue); + Assert.False(metadata.IsSealed); + Assert.Null(metadata.PropertyChangedCallback); + + // Make sure base metadata is unchanged. + Assert.Null(baseMetadata.CoerceValueCallback); + Assert.Null(baseMetadata.DefaultValue); + Assert.Null(baseMetadata.PropertyChangedCallback); + } + + [Fact] + public void Merge_InvokeCustomOnUnchanged_Success() + { + var metadata = new SubPropertyMetadata(); + var defaultValue = new object(); + int propertyChangedCallbackCallCount = 0; + int coerceValueCallbackCallCount = 0; + PropertyChangedCallback propertyChangedCallback = (d, e) => propertyChangedCallbackCallCount++; + CoerceValueCallback coerceValueCallback = (d, v) => coerceValueCallbackCallCount++; + var baseMetadata = new PropertyMetadata(defaultValue, propertyChangedCallback, coerceValueCallback); + + metadata.Merge(baseMetadata, null!); + Assert.Equal(coerceValueCallback, metadata.CoerceValueCallback); + Assert.Equal(defaultValue, metadata.DefaultValue); + Assert.False(metadata.IsSealed); + Assert.Equal(propertyChangedCallback, metadata.PropertyChangedCallback); + Assert.Equal(0, propertyChangedCallbackCallCount); + Assert.Equal(0, coerceValueCallbackCallCount); + + // Make sure base metadata is unchanged. + Assert.Equal(coerceValueCallback, baseMetadata.CoerceValueCallback); + Assert.Equal(defaultValue, baseMetadata.DefaultValue); + Assert.Equal(propertyChangedCallback, baseMetadata.PropertyChangedCallback); + } + + [Fact] + public void Merge_InvokeUnchangedOnCustom_Success() + { + var defaultValue = new object(); + int propertyChangedCallbackCallCount = 0; + int coerceValueCallbackCallCount = 0; + PropertyChangedCallback propertyChangedCallback = (d, e) => propertyChangedCallbackCallCount++; + CoerceValueCallback coerceValueCallback = (d, v) => coerceValueCallbackCallCount++; + var metadata = new SubPropertyMetadata(defaultValue, propertyChangedCallback, coerceValueCallback); + var baseMetadata = new PropertyMetadata(); + + metadata.Merge(baseMetadata, null!); + Assert.Equal(coerceValueCallback, metadata.CoerceValueCallback); + Assert.Equal(defaultValue, metadata.DefaultValue); + Assert.False(metadata.IsSealed); + Assert.Equal(propertyChangedCallback, metadata.PropertyChangedCallback); + Assert.Equal(0, propertyChangedCallbackCallCount); + Assert.Equal(0, coerceValueCallbackCallCount); + + // Make sure base metadata is unchanged. + Assert.Null(baseMetadata.CoerceValueCallback); + Assert.Null(baseMetadata.DefaultValue); + Assert.Null(baseMetadata.PropertyChangedCallback); + } + + [Fact] + public void Merge_InvokeCustomOnCustom_Success() + { + var defaultValue1 = new object(); + int propertyChangedCallbackCallCount1 = 0; + int coerceValueCallbackCallCount1 = 0; + PropertyChangedCallback propertyChangedCallback1 = (d, e) => propertyChangedCallbackCallCount1++; + CoerceValueCallback coerceValueCallback1 = (d, v) => coerceValueCallbackCallCount1++; + var metadata = new SubPropertyMetadata(defaultValue1, propertyChangedCallback1, coerceValueCallback1); + + var defaultValue2 = new object(); + int propertyChangedCallbackCallCount2 = 0; + int coerceValueCallbackCallCount2 = 0; + PropertyChangedCallback propertyChangedCallback2 = (d, e) => propertyChangedCallbackCallCount2++; + CoerceValueCallback coerceValueCallback2 = (d, v) => coerceValueCallbackCallCount2++; + var baseMetadata = new SubPropertyMetadata(defaultValue2, propertyChangedCallback2, coerceValueCallback2); + + metadata.Merge(baseMetadata, null!); + Assert.Equal(coerceValueCallback1, metadata.CoerceValueCallback); + Assert.Equal(defaultValue1, metadata.DefaultValue); + Assert.False(metadata.IsSealed); + Assert.NotNull(metadata.PropertyChangedCallback); + Assert.Same(metadata.PropertyChangedCallback, metadata.PropertyChangedCallback); + Assert.NotEqual(propertyChangedCallback1, metadata.PropertyChangedCallback); + Assert.NotEqual(propertyChangedCallback2, metadata.PropertyChangedCallback); + Assert.Equal(0, propertyChangedCallbackCallCount1); + Assert.Equal(0, coerceValueCallbackCallCount1); + Assert.Equal(0, propertyChangedCallbackCallCount2); + Assert.Equal(0, coerceValueCallbackCallCount2); + + // Make sure base metadata is unchanged. + Assert.Equal(coerceValueCallback2, baseMetadata.CoerceValueCallback); + Assert.Equal(defaultValue2, baseMetadata.DefaultValue); + Assert.Equal(propertyChangedCallback2, baseMetadata.PropertyChangedCallback); + } + + [Fact] + public void Merge_InvokeSelfUnchanged_Success() + { + var metadata = new SubPropertyMetadata(); + metadata.Merge(metadata, null!); + Assert.Null(metadata.CoerceValueCallback); + Assert.Null(metadata.DefaultValue); + Assert.False(metadata.IsSealed); + Assert.Null(metadata.PropertyChangedCallback); + } + + [Fact] + public void Merge_InvokeSelfChanged_Success() + { + var defaultValue = new object(); + int propertyChangedCallbackCallCount = 0; + PropertyChangedCallback propertyChangedCallback = (d, e) => propertyChangedCallbackCallCount++; + int coerceValueCallbackCallCount = 0; + CoerceValueCallback coerceValueCallback = (d, v) => coerceValueCallbackCallCount++; + var metadata = new SubPropertyMetadata(defaultValue, propertyChangedCallback, coerceValueCallback); + + metadata.Merge(metadata, null!); + Assert.Same(coerceValueCallback, metadata.CoerceValueCallback); + Assert.Same(defaultValue, metadata.DefaultValue); + Assert.False(metadata.IsSealed); + Assert.NotNull(metadata.PropertyChangedCallback); + Assert.NotSame(propertyChangedCallback, metadata.PropertyChangedCallback); + } + + [Fact] + public void Merge_InvokeBaseHasPropertyChangedCallback_AddsBaseHandlers() + { + var metadata = new SubPropertyMetadata(); + var events = new List(); + var baseMetadata = new PropertyMetadata(); + baseMetadata.PropertyChangedCallback += (d, e) => events.Add("Event3"); + baseMetadata.PropertyChangedCallback += (d, e) => events.Add("Event4"); + + metadata.Merge(baseMetadata, null!); + Assert.Equal(baseMetadata.PropertyChangedCallback, metadata.PropertyChangedCallback); + + var obj = new DependencyObject(); + var e = new DependencyPropertyChangedEventArgs(); + metadata.PropertyChangedCallback.Invoke(obj, e); + Assert.Equal(new[] { "Event3", "Event4" }, events); + } + + [Fact] + public void Merge_InvokeCurrentHasPropertyChangedCallback_KeepsCurrentHandlers() + { + var events = new List(); + var metadata = new SubPropertyMetadata(); + metadata.PropertyChangedCallback += (d, e) => events.Add("Event1"); + metadata.PropertyChangedCallback += (d, e) => events.Add("Event2"); + var baseMetadata = new PropertyMetadata(); + + metadata.Merge(baseMetadata, null!); + Assert.NotEqual(baseMetadata.PropertyChangedCallback, metadata.PropertyChangedCallback); + + var obj = new DependencyObject(); + var e = new DependencyPropertyChangedEventArgs(); + metadata.PropertyChangedCallback.Invoke(obj, e); + Assert.Equal(new[] { "Event1", "Event2" }, events); + } + + [Fact] + public void Merge_InvokeCurrentAndBaseHavePropertyChangedCallback_AddsBaseHandlersAtStart() + { + var events = new List(); + var metadata = new SubPropertyMetadata(); + metadata.PropertyChangedCallback += (d, e) => events.Add("Event1"); + metadata.PropertyChangedCallback += (d, e) => events.Add("Event2"); + var baseMetadata = new PropertyMetadata(); + baseMetadata.PropertyChangedCallback += (d, e) => events.Add("Event3"); + baseMetadata.PropertyChangedCallback += (d, e) => events.Add("Event4"); + + metadata.Merge(baseMetadata, null!); + Assert.NotEqual(baseMetadata.PropertyChangedCallback, metadata.PropertyChangedCallback); + + var obj = new DependencyObject(); + var e = new DependencyPropertyChangedEventArgs(); + metadata.PropertyChangedCallback.Invoke(obj, e); + Assert.Equal(new[] { "Event3", "Event4", "Event1", "Event2" }, events); + } + + [Fact] + public void Merge_InvokeBaseHasCoerceValueCallback_AddsBaseHandlers() + { + var events = new List(); + var metadata = new SubPropertyMetadata(); + var baseMetadata = new PropertyMetadata(); + baseMetadata.CoerceValueCallback += (d, e) => { events.Add("Event3"); return 1; }; + baseMetadata.CoerceValueCallback += (d, e) => { events.Add("Event4"); return 1; }; + + metadata.Merge(baseMetadata, null!); + Assert.Equal(baseMetadata.CoerceValueCallback, metadata.CoerceValueCallback); + + var obj = new DependencyObject(); + var e = new DependencyPropertyChangedEventArgs(); + metadata.CoerceValueCallback.Invoke(obj, e); + Assert.Equal(new[] { "Event3", "Event4" }, events); + } + + [Fact] + public void Merge_InvokeCurrentHasCoerceValueCallback_KeepsCurrentHandlers() + { + var events = new List(); + var metadata = new SubPropertyMetadata(); + metadata.CoerceValueCallback += (d, e) => { events.Add("Event1"); return 1; }; + metadata.CoerceValueCallback += (d, e) => { events.Add("Event2"); return 1; }; + var baseMetadata = new PropertyMetadata(); + + metadata.Merge(baseMetadata, null!); + Assert.NotEqual(baseMetadata.CoerceValueCallback, metadata.CoerceValueCallback); + + var obj = new DependencyObject(); + var e = new DependencyPropertyChangedEventArgs(); + metadata.CoerceValueCallback.Invoke(obj, e); + Assert.Equal(new[] { "Event1", "Event2" }, events); + } + + [Fact] + public void Merge_InvokeCurrentAndBaseHaveCoerceValueCallback_DoesNotAddBaseHandlers() + { + var events = new List(); + var metadata = new SubPropertyMetadata(); + metadata.CoerceValueCallback += (d, e) => { events.Add("Event1"); return 1; }; + metadata.CoerceValueCallback += (d, e) => { events.Add("Event2"); return 1; }; + var baseMetadata = new PropertyMetadata(); + baseMetadata.CoerceValueCallback += (d, e) => { events.Add("Event3"); return 1; }; + baseMetadata.CoerceValueCallback += (d, e) => { events.Add("Event4"); return 1; }; + + metadata.Merge(baseMetadata, null!); + Assert.NotEqual(baseMetadata.CoerceValueCallback, metadata.CoerceValueCallback); + + var obj = new DependencyObject(); + var e = new DependencyPropertyChangedEventArgs(); + metadata.CoerceValueCallback.Invoke(obj, e); + Assert.Equal(new[] { "Event1", "Event2" }, events); + } + + [Fact] + public void Merge_NullBaseMetadata_ThrowsArgumentNullException() + { + var metadata = new SubPropertyMetadata(); + Assert.Throws("baseMetadata", () => metadata.Merge(null!, null!)); + } + + [Fact] + public void Merge_InvokeSealed_ThrowsInvalidOperationException() + { + DependencyProperty property = DependencyProperty.RegisterAttached(nameof(PropertyMetadataTests) + MethodBase.GetCurrentMethod()!.Name, typeof(int), typeof(DependencyObject), new SubPropertyMetadata()); + SubPropertyMetadata metadata = Assert.IsType(property.DefaultMetadata); + Assert.Throws(() => metadata.Merge(new PropertyMetadata(), null!)); + } + + private static DependencyProperty s_property = DependencyProperty.Register(nameof(PropertyMetadataTests), typeof(string), typeof(DependencyObject)); + + public static IEnumerable OnApply_TestData() + { + yield return new object?[] { s_property, typeof(bool) }; + yield return new object?[] { s_property, null }; + yield return new object?[] { null, typeof(bool) }; + yield return new object?[] { null, null }; + } + + [Theory] + [MemberData(nameof(OnApply_TestData))] + public void OnApply_Invoke_Nop(DependencyProperty dp, Type targetType) + { + var metadata = new SubPropertyMetadata(); + metadata.OnApply(dp, targetType); + } + + private class SubPropertyMetadata : PropertyMetadata + { + public SubPropertyMetadata() : base() + { + } + + public SubPropertyMetadata(object defaultValue) : base(defaultValue) + { + } + + public SubPropertyMetadata(PropertyChangedCallback propertyChangedCallback) : base(propertyChangedCallback) + { + } + + public SubPropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback) : base(defaultValue, propertyChangedCallback) + { + } + + public SubPropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback, CoerceValueCallback coerceValueCallback) : base(defaultValue, propertyChangedCallback, coerceValueCallback) + { + } + + public new bool IsSealed => base.IsSealed; + + public new void Merge(PropertyMetadata baseMetadata, DependencyProperty dp) + => base.Merge(baseMetadata, dp); + + public new void OnApply(DependencyProperty dp, Type targetType) + => base.OnApply(dp, targetType); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/RectConverterTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/RectConverterTests.cs new file mode 100644 index 00000000000..835dd4d5089 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/RectConverterTests.cs @@ -0,0 +1,198 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.ComponentModel.Design.Serialization; +using System.Globalization; + +namespace System.Windows.Tests; + +public class RectConverterTests +{ + [Theory] + [InlineData(null, false)] + [InlineData(typeof(object), false)] + [InlineData(typeof(string), true)] + [InlineData(typeof(InstanceDescriptor), false)] + [InlineData(typeof(Rect), false)] + public void CanConvertTo_Invoke_ReturnsExpected(Type? destinationType, bool expected) + { + var converter = new RectConverter(); + Assert.Equal(expected, converter.CanConvertTo(null, destinationType)); + Assert.Equal(expected, converter.CanConvertTo(new CustomTypeDescriptorContext(), destinationType)); + } + + [Theory] + [MemberData(nameof(RectTests.ToString_TestData), MemberType = typeof(RectTests))] + public void ConvertTo_InvokeRectToString_ReturnsExpected(Rect matrix, string expected) + { + var converter = new RectConverter(); + Assert.Equal(expected, converter.ConvertTo(matrix, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), null, matrix, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, matrix, typeof(string))); + } + + [Theory] + [MemberData(nameof(RectTests.ToString_IFormatProviderCustom_TestData), MemberType = typeof(RectTests))] + public void ConvertTo_InvokeRectToStringCustomCulture_ReturnsExpected(Rect matrix, string numberDecimalSeparator, string expected) + { + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + var converter = new RectConverter(); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), culture, matrix, typeof(string))); + } + + [Theory] + [InlineData(null, "")] + [InlineData("", "")] + [InlineData("value", "value")] + [InlineData(1, "1")] + public void ConvertTo_InvokeNotRectToString_ReturnsExpected(object? value, string expected) + { + var converter = new RectConverter(); + Assert.Equal(expected, converter.ConvertTo(value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + } + + public static IEnumerable ConvertTo_CantConvert_TestData() + { + yield return new object?[] { null, typeof(object) }; + yield return new object?[] { string.Empty, typeof(object) }; + yield return new object?[] { "value", typeof(object) }; + yield return new object?[] { new object(), typeof(object) }; + yield return new object?[] { new Rect(), typeof(object) }; + + yield return new object?[] { null, typeof(Rect) }; + yield return new object?[] { string.Empty, typeof(Rect) }; + yield return new object?[] { "value", typeof(Rect) }; + yield return new object?[] { new object(), typeof(Rect) }; + yield return new object?[] { new Rect(), typeof(Rect) }; + } + + [Theory] + [MemberData(nameof(ConvertTo_CantConvert_TestData))] + public void ConvertTo_CantConvert_ThrowsNotSupportedException(object value, Type destinationType) + { + var converter = new RectConverter(); + Assert.Throws(() => converter.ConvertTo(value, destinationType)); + Assert.Throws(() => converter.ConvertTo(null, null, value, destinationType)); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, destinationType)); + } + + public static IEnumerable ConvertTo_NullDestinationType_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + yield return new object?[] { new object() }; + yield return new object?[] { new Rect() }; + } + + [Theory] + [MemberData(nameof(ConvertTo_NullDestinationType_TestData))] + public void ConvertTo_NullDestinationType_ThrowsArgumentNullException(object value) + { + var converter = new RectConverter(); + Assert.Throws("destinationType", () => converter.ConvertTo(value, null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(null, null, new Rect(), null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, new Rect(), null!)); + } + + [Theory] + [InlineData(null, false)] + [InlineData(typeof(object), false)] + [InlineData(typeof(string), true)] + [InlineData(typeof(InstanceDescriptor), true)] + [InlineData(typeof(Rect), false)] + public void CanConvertFrom_Invoke_ReturnsExpected(Type? sourceType, bool expected) + { + var converter = new RectConverter(); + Assert.Equal(expected, converter.CanConvertFrom(sourceType!)); + Assert.Equal(expected, converter.CanConvertFrom(null, sourceType)); + Assert.Equal(expected, converter.CanConvertFrom(new CustomTypeDescriptorContext(), sourceType)); + } + + [Theory] + [MemberData(nameof(RectTests.Parse_TestData), MemberType = typeof(RectTests))] + public void ConvertFrom_InvokeStringValue_ReturnsExpected(object value, Rect expected) + { + var converter = new RectConverter(); + Assert.Equal(expected, converter.ConvertFrom(value)); + Assert.Equal(expected, converter.ConvertFrom(null, null, value)); + Assert.Equal(expected, converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Fact] + public void ConvertFrom_NullValue_ThrowsNotSupportedException() + { + var converter = new RectConverter(); + Assert.Throws(() => converter.ConvertFrom(null!)); + Assert.Throws(() => converter.ConvertFrom(null, null, null)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, null)); + } + + [Theory] + [MemberData(nameof(RectTests.Parse_InvalidSource_TestData), MemberType = typeof(RectTests))] + public void ConvertFrom_InvalidStringValue_ThrowsInvalidOperationException(object value) + { + var converter = new RectConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Theory] + [MemberData(nameof(RectTests.Parse_NotDouble_TestData), MemberType = typeof(RectTests))] + public void ConvertFrom_NotDoubleStringValue_ThrowsFormatException(object value) + { + var converter = new RectConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Theory] + [MemberData(nameof(RectTests.Parse_Negative_TestData), MemberType = typeof(RectTests))] + public void ConvertFrom_Negative_ThrowsArgumentException(object value) + { + var converter = new RectConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + public static IEnumerable ConvertFrom_CantConvert_TestData() + { + yield return new object[] { new object() }; + yield return new object[] { new Rect() }; + } + + [Theory] + [MemberData(nameof(ConvertFrom_CantConvert_TestData))] + public void ConvertFrom_CantConvert_ThrowsNotSupportedException(object value) + { + var converter = new RectConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + private class CustomTypeDescriptorContext : ITypeDescriptorContext + { + public IContainer Container => throw new NotImplementedException(); + + public object Instance => throw new NotImplementedException(); + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/RectTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/RectTests.cs new file mode 100644 index 00000000000..6d071b6defe --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/RectTests.cs @@ -0,0 +1,3327 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Globalization; +using System.Windows.Converters; +using System.Windows.Markup; +using System.Windows.Media; +using System.Windows.Media.Tests; + +namespace System.Windows.Tests; + +public class RectTests +{ + [Fact] + public void Ctor_Default() + { + var rect = new Rect(); + Assert.Equal(0, rect.X); + Assert.Equal(0, rect.Y); + Assert.Equal(0, rect.Width); + Assert.Equal(0, rect.Height); + Assert.False(rect.IsEmpty); + } + + public static IEnumerable Ctor_Size_TestData() + { + yield return new object[] { new Size(1, 2) }; + yield return new object[] { new Size(1, 0) }; + yield return new object[] { new Size(0, 2) }; + yield return new object[] { new Size(0, 0) }; + yield return new object[] { new Size(double.NaN, double.NaN) }; + yield return new object[] { new Size(double.NaN, 2) }; + yield return new object[] { new Size(1, double.NaN) }; + yield return new object[] { new Size() }; + } + + [Theory] + [MemberData(nameof(Ctor_Size_TestData))] + public void Ctor_Size(Size size) + { + var rect = new Rect(size); + Assert.Equal(0, rect.X); + Assert.Equal(0, rect.Y); + Assert.Equal(size.Width, rect.Width); + Assert.Equal(size.Height, rect.Height); + Assert.Equal(0, rect.Location.X); + Assert.Equal(0, rect.Location.Y); + Assert.Equal(size.Width, rect.Size.Width); + Assert.Equal(size.Height, rect.Size.Height); + Assert.Equal(0, rect.Top); + Assert.Equal(0, rect.TopLeft.X); + Assert.Equal(0, rect.TopLeft.Y); + Assert.Equal(size.Width, rect.TopRight.X); + Assert.Equal(0, rect.TopRight.Y); + Assert.Equal(size.Height, rect.Bottom); + Assert.Equal(0, rect.BottomLeft.X); + Assert.Equal(size.Height, rect.BottomLeft.Y); + Assert.Equal(size.Width, rect.BottomRight.X); + Assert.Equal(size.Height, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + } + + [Fact] + public void Ctor_SizeEmpty() + { + var rect = new Rect(Size.Empty); + Assert.Equal(double.PositiveInfinity, rect.X); + Assert.Equal(double.PositiveInfinity, rect.Y); + Assert.Equal(double.NegativeInfinity, rect.Width); + Assert.Equal(double.NegativeInfinity, rect.Height); + Assert.Equal(double.PositiveInfinity, rect.Location.X); + Assert.Equal(double.PositiveInfinity, rect.Location.Y); + Assert.Equal(double.NegativeInfinity, rect.Size.Width); + Assert.Equal(double.NegativeInfinity, rect.Size.Height); + Assert.Equal(double.PositiveInfinity, rect.Top); + Assert.Equal(double.PositiveInfinity, rect.TopLeft.X); + Assert.Equal(double.PositiveInfinity, rect.TopLeft.Y); + Assert.Equal(double.NegativeInfinity, rect.TopRight.X); + Assert.Equal(double.PositiveInfinity, rect.TopRight.Y); + Assert.Equal(double.NegativeInfinity, rect.Bottom); + Assert.Equal(double.PositiveInfinity, rect.BottomLeft.X); + Assert.Equal(double.NegativeInfinity, rect.BottomLeft.Y); + Assert.Equal(double.NegativeInfinity, rect.BottomRight.X); + Assert.Equal(double.NegativeInfinity, rect.BottomRight.Y); + Assert.True(rect.IsEmpty); + } + + [Theory] + [InlineData(double.NegativeZero, double.NegativeZero, double.NegativeZero, double.NegativeZero)] + [InlineData(double.NegativeInfinity, double.NegativeInfinity, 3, 4)] + [InlineData(double.MinValue, double.MinValue, 3, 4)] + [InlineData(0, 0, 0, 0)] + [InlineData(0.1, 0.2, 0.3, 0.4)] + [InlineData(-1, -2, 3, 4)] + [InlineData(1, 2, 3, 4)] + [InlineData(double.MaxValue, double.MaxValue, double.MaxValue, double.MaxValue)] + [InlineData(double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity)] + [InlineData(double.NaN, double.NaN, double.NaN, double.NaN)] + public void Ctor_Double_Double_Double_Double(double x, double y, double width, double height) + { + var rect = new Rect(x, y, width, height); + Assert.Equal(x, rect.X); + Assert.Equal(y, rect.Y); + Assert.Equal(width, rect.Width); + Assert.Equal(height, rect.Height); + Assert.Equal(x, rect.Location.X); + Assert.Equal(y, rect.Location.Y); + Assert.Equal(width, rect.Size.Width); + Assert.Equal(height, rect.Size.Height); + Assert.Equal(y, rect.Top); + Assert.Equal(x, rect.TopLeft.X); + Assert.Equal(y, rect.TopLeft.Y); + Assert.Equal(x + width, rect.TopRight.X); + Assert.Equal(y, rect.TopRight.Y); + Assert.Equal(y + height, rect.Bottom); + Assert.Equal(x, rect.BottomLeft.X); + Assert.Equal(y + height, rect.BottomLeft.Y); + Assert.Equal(x + width, rect.BottomRight.X); + Assert.Equal(y + height, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + } + + [Theory] + [InlineData(double.NegativeInfinity)] + [InlineData(double.MinValue)] + [InlineData(-1)] + public void Ctor_NegativeWidth_ThrowsArgumentException(double width) + { + // TODO: should have paramName + Assert.Throws(() => new Rect(0, 0, width, 0)); + } + + [Theory] + [InlineData(double.NegativeInfinity)] + [InlineData(double.MinValue)] + [InlineData(-1)] + public void Ctor_NegativeHeight_ThrowsArgumentException(double height) + { + // TODO: should have paramName + Assert.Throws(() => new Rect(0, 0, 0, height)); + } + + public static IEnumerable Ctor_Point_Point_TestData() + { + // Normal. + yield return new object[] { new Point(1, 2), new Point(1, 2), 1, 2, 0, 0 }; + yield return new object[] { new Point(1, 2), new Point(3, 4), 1, 2, 2, 2 }; + yield return new object[] { new Point(1.5, 2.5), new Point(3.5, 4.5), 1.5, 2.5, 2, 2 }; + yield return new object[] { new Point(-1, -2), new Point(3, 4), -1, -2, 4, 6 }; + yield return new object[] { new Point(1, 2), new Point(-3, -4), -3, -4, 4, 6 }; + yield return new object[] { new Point(1, 2), new Point(0, 0), 0, 0, 1, 2 }; + yield return new object[] { new Point(1, 2), new Point(), 0, 0, 1, 2 }; + + // Infinity. + yield return new object[] { new Point(double.PositiveInfinity, double.PositiveInfinity), new Point(1, 2), 1, 2, double.PositiveInfinity, double.PositiveInfinity }; + yield return new object[] { new Point(1, 2), new Point(double.PositiveInfinity, double.PositiveInfinity), 1, 2, double.PositiveInfinity, double.PositiveInfinity }; + yield return new object[] { new Point(double.NegativeInfinity, double.NegativeInfinity), new Point(1, 2), double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity }; + yield return new object[] { new Point(1, 2), new Point(double.NegativeInfinity, double.NegativeInfinity), double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity }; + yield return new object[] { new Point(double.NegativeInfinity, double.NegativeInfinity), new Point(double.NegativeInfinity, double.NegativeInfinity), double.NegativeInfinity, double.NegativeInfinity, double.NaN, double.NaN }; + + // NaN. + yield return new object[] { new Point(double.NaN, double.NaN), new Point(1, 2), double.NaN, double.NaN, double.NaN, double.NaN }; + yield return new object[] { new Point(1, 2), new Point(double.NaN, double.NaN), double.NaN, double.NaN, double.NaN, double.NaN }; + yield return new object[] { new Point(double.NaN, double.NaN), new Point(double.NaN, double.NaN), double.NaN, double.NaN, double.NaN, double.NaN }; + + // Zero. + yield return new object[] { new Point(0, 0), new Point(0, 0), 0, 0, 0, 0 }; + yield return new object[] { new Point(0, 0), new Point(), 0, 0, 0, 0 }; + yield return new object[] { new Point(0, 0), new Point(1, 2), 0, 0, 1, 2 }; + yield return new object[] { new Point(0, 0), new Point(-1, -2), -1, -2, 1, 2 }; + + // Default. + yield return new object[] { new Point(), new Point(0, 0), 0, 0, 0, 0 }; + yield return new object[] { new Point(), new Point(), 0, 0, 0, 0 }; + yield return new object[] { new Point(), new Point(1, 2), 0, 0, 1, 2 }; + yield return new object[] { new Point(), new Point(-1, -2), -1, -2, 1, 2 }; + } + + [Theory] + [MemberData(nameof(Ctor_Point_Point_TestData))] + public void Ctor_Point_Point(Point point1, Point point2, double expectedX, double expectedY, double expectedWidth, double expectedHeight) + { + var rect = new Rect(point1, point2); + Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), rect); + Assert.Equal(expectedX, rect.X); + Assert.Equal(expectedY, rect.Y); + Assert.Equal(expectedWidth, rect.Width); + Assert.Equal(expectedHeight, rect.Height); + Assert.Equal(expectedX, rect.Location.X); + Assert.Equal(expectedY, rect.Location.Y); + Assert.Equal(expectedWidth, rect.Size.Width); + Assert.Equal(expectedHeight, rect.Size.Height); + Assert.Equal(expectedY, rect.Top); + Assert.Equal(expectedX, rect.TopLeft.X); + Assert.Equal(expectedY, rect.TopLeft.Y); + Assert.Equal(expectedX + expectedWidth, rect.TopRight.X); + Assert.Equal(expectedY, rect.TopRight.Y); + Assert.Equal(expectedY + expectedHeight, rect.Bottom); + Assert.Equal(expectedX, rect.BottomLeft.X); + Assert.Equal(expectedY + expectedHeight, rect.BottomLeft.Y); + Assert.Equal(expectedX + expectedWidth, rect.BottomRight.X); + Assert.Equal(expectedY + expectedHeight, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + } + + public static IEnumerable Ctor_Point_Size_TestData() + { + // Normal. + yield return new object[] { new Point(1, 2), new Size(1, 2), 1, 2, 1, 2 }; + yield return new object[] { new Point(1.25, 2.25), new Size(1.5, 2.5), 1.25, 2.25, 1.5, 2.5 }; + yield return new object[] { new Point(-1, -2), new Size(1, 2), -1, -2, 1, 2 }; + + // Infinite. + yield return new object[] { new Point(double.PositiveInfinity, double.PositiveInfinity), new Size(double.PositiveInfinity, double.PositiveInfinity), double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity }; + yield return new object[] { new Point(double.PositiveInfinity, double.PositiveInfinity), new Size(1, 2), double.PositiveInfinity, double.PositiveInfinity, 1, 2 }; + yield return new object[] { new Point(1, 2), new Size(double.PositiveInfinity, double.PositiveInfinity), 1, 2, double.PositiveInfinity, double.PositiveInfinity }; + yield return new object[] { new Point(double.NegativeInfinity, double.NegativeInfinity), new Size(double.PositiveInfinity, double.PositiveInfinity), double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity }; + yield return new object[] { new Point(double.NegativeInfinity, double.NegativeInfinity), new Size(1, 2), double.NegativeInfinity, double.NegativeInfinity, 1, 2 }; + + // NaN. + yield return new object[] { new Point(double.NaN, double.NaN), new Size(double.NaN, double.NaN), double.NaN, double.NaN, double.NaN, double.NaN }; + yield return new object[] { new Point(double.NaN, double.NaN), new Size(1, 2), double.NaN, double.NaN, 1, 2 }; + yield return new object[] { new Point(1, 2), new Size(double.NaN, double.NaN), 1, 2, double.NaN, double.NaN }; + + // Zero. + yield return new object[] { new Point(0, 0), new Size(1, 2), 0, 0, 1, 2 }; + yield return new object[] { new Point(0, 0), new Size(0, 0), 0, 0, 0, 0 }; + yield return new object[] { new Point(0, 0), new Size(), 0, 0, 0, 0 }; + + // Default. + yield return new object[] { new Point(), new Size(1, 2), 0, 0, 1, 2 }; + yield return new object[] { new Point(), new Size(0, 0), 0, 0, 0, 0 }; + yield return new object[] { new Point(), new Size(), 0, 0, 0, 0 }; + } + + [Theory] + [MemberData(nameof(Ctor_Point_Size_TestData))] + public void Ctor_Point_Size(Point point, Size size, double expectedX, double expectedY, double expectedWidth, double expectedHeight) + { + var rect = new Rect(point, size); + Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), rect); + Assert.Equal(expectedX, rect.X); + Assert.Equal(expectedY, rect.Y); + Assert.Equal(expectedWidth, rect.Width); + Assert.Equal(expectedHeight, rect.Height); + Assert.Equal(expectedX, rect.Location.X); + Assert.Equal(expectedY, rect.Location.Y); + Assert.Equal(expectedWidth, rect.Size.Width); + Assert.Equal(expectedHeight, rect.Size.Height); + Assert.Equal(expectedY, rect.Top); + Assert.Equal(expectedX, rect.TopLeft.X); + Assert.Equal(expectedY, rect.TopLeft.Y); + Assert.Equal(expectedX + expectedWidth, rect.TopRight.X); + Assert.Equal(expectedY, rect.TopRight.Y); + Assert.Equal(expectedY + expectedHeight, rect.Bottom); + Assert.Equal(expectedX, rect.BottomLeft.X); + Assert.Equal(expectedY + expectedHeight, rect.BottomLeft.Y); + Assert.Equal(expectedX + expectedWidth, rect.BottomRight.X); + Assert.Equal(expectedY + expectedHeight, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + } + + public static IEnumerable Ctor_Point_Size_Empty_TestData() + { + yield return new object[] { new Point(1, 2) }; + yield return new object[] { new Point(0, 0) }; + yield return new object[] { new Point() }; + } + + [Theory] + [MemberData(nameof(Ctor_Point_Size_Empty_TestData))] + public void Ctor_Point_Size_Empty(Point point) + { + var rect = new Rect(point, Size.Empty); + Assert.Equal(Rect.Empty, rect); + Assert.Equal(double.PositiveInfinity, rect.X); + Assert.Equal(double.PositiveInfinity, rect.Y); + Assert.Equal(double.NegativeInfinity, rect.Width); + Assert.Equal(double.NegativeInfinity, rect.Height); + Assert.Equal(double.PositiveInfinity, rect.Location.X); + Assert.Equal(double.PositiveInfinity, rect.Location.Y); + Assert.Equal(double.NegativeInfinity, rect.Size.Width); + Assert.Equal(double.NegativeInfinity, rect.Size.Height); + Assert.Equal(double.PositiveInfinity, rect.Top); + Assert.Equal(double.PositiveInfinity, rect.TopLeft.X); + Assert.Equal(double.PositiveInfinity, rect.TopLeft.Y); + Assert.Equal(double.NegativeInfinity, rect.TopRight.X); + Assert.Equal(double.PositiveInfinity, rect.TopRight.Y); + Assert.Equal(double.NegativeInfinity, rect.Bottom); + Assert.Equal(double.PositiveInfinity, rect.BottomLeft.X); + Assert.Equal(double.NegativeInfinity, rect.BottomLeft.Y); + Assert.Equal(double.NegativeInfinity, rect.BottomRight.X); + Assert.Equal(double.NegativeInfinity, rect.BottomRight.Y); + Assert.True(rect.IsEmpty); + } + + public static IEnumerable Ctor_Point_Vector_TestData() + { + // Normal. + yield return new object[] { new Point(1, 2), new Vector(3, 4), 1, 2, 3, 4 }; + yield return new object[] { new Point(1.25, 2.25), new Vector(1.5, 2.5), 1.25, 2.25, 1.5, 2.5 }; + yield return new object[] { new Point(1.25, 2.25), new Vector(-1.5, -2.5), -0.25, -0.25, 1.5, 2.5 }; + yield return new object[] { new Point(1, 2), new Vector(0, 0), 1, 2, 0, 0 }; + yield return new object[] { new Point(1, 2), new Vector(), 1, 2, 0, 0 }; + + // Infinite. + yield return new object[] { new Point(double.PositiveInfinity, double.PositiveInfinity), new Vector(double.PositiveInfinity, double.PositiveInfinity), double.PositiveInfinity, double.PositiveInfinity, double.NaN, double.NaN }; + yield return new object[] { new Point(double.PositiveInfinity, double.PositiveInfinity), new Vector(1, 2), double.PositiveInfinity, double.PositiveInfinity, double.NaN, double.NaN }; + yield return new object[] { new Point(1, 2), new Vector(double.PositiveInfinity, double.PositiveInfinity), 1, 2, double.PositiveInfinity, double.PositiveInfinity }; + yield return new object[] { new Point(double.NegativeInfinity, double.NegativeInfinity), new Vector(1, 2), double.NegativeInfinity, double.NegativeInfinity, double.NaN, double.NaN }; + yield return new object[] { new Point(1, 2), new Vector(double.NegativeInfinity, double.NegativeInfinity), double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity }; + yield return new object[] { new Point(double.NegativeInfinity, double.NegativeInfinity), new Vector(double.NegativeInfinity, double.NegativeInfinity), double.NegativeInfinity, double.NegativeInfinity, double.NaN, double.NaN }; + + // NaN. + yield return new object[] { new Point(double.NaN, double.NaN), new Vector(double.NaN, double.NaN), double.NaN, double.NaN, double.NaN, double.NaN }; + yield return new object[] { new Point(double.NaN, double.NaN), new Vector(1, 2), double.NaN, double.NaN, double.NaN, double.NaN }; + yield return new object[] { new Point(1, 2), new Vector(double.NaN, double.NaN), double.NaN, double.NaN, double.NaN, double.NaN }; + + // Zero. + yield return new object[] { new Point(0, 0), new Vector(1, 2), 0, 0, 1, 2 }; + yield return new object[] { new Point(0, 0), new Vector(0, 0), 0, 0, 0, 0 }; + yield return new object[] { new Point(0, 0), new Vector(), 0, 0, 0, 0 }; + + // Default. + yield return new object[] { new Point(), new Vector(1, 2), 0, 0, 1, 2 }; + yield return new object[] { new Point(), new Vector(0, 0), 0, 0, 0, 0 }; + yield return new object[] { new Point(), new Vector(), 0, 0, 0, 0 }; + } + + [Theory] + [MemberData(nameof(Ctor_Point_Vector_TestData))] + public void Ctor_Point_Vector(Point point, Vector vector, double expectedX, double expectedY, double expectedWidth, double expectedHeight) + { + var rect = new Rect(point, vector); + Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), rect); + Assert.Equal(expectedX, rect.X); + Assert.Equal(expectedY, rect.Y); + Assert.Equal(expectedWidth, rect.Width); + Assert.Equal(expectedHeight, rect.Height); + Assert.Equal(expectedX, rect.Location.X); + Assert.Equal(expectedY, rect.Location.Y); + Assert.Equal(expectedWidth, rect.Size.Width); + Assert.Equal(expectedHeight, rect.Size.Height); + Assert.Equal(expectedY, rect.Top); + Assert.Equal(expectedX, rect.TopLeft.X); + Assert.Equal(expectedY, rect.TopLeft.Y); + Assert.Equal(expectedX + expectedWidth, rect.TopRight.X); + Assert.Equal(expectedY, rect.TopRight.Y); + Assert.Equal(expectedY + expectedHeight, rect.Bottom); + Assert.Equal(expectedX, rect.BottomLeft.X); + Assert.Equal(expectedY + expectedHeight, rect.BottomLeft.Y); + Assert.Equal(expectedX + expectedWidth, rect.BottomRight.X); + Assert.Equal(expectedY + expectedHeight, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + } + + [Fact] + public void Empty_Get_ReturnsExpected() + { + Rect rect = Rect.Empty; + Assert.Equal(double.PositiveInfinity, rect.X); + Assert.Equal(double.PositiveInfinity, rect.Y); + Assert.Equal(double.NegativeInfinity, rect.Width); + Assert.Equal(double.NegativeInfinity, rect.Height); + Assert.Equal(double.PositiveInfinity, rect.Location.X); + Assert.Equal(double.PositiveInfinity, rect.Location.Y); + Assert.Equal(double.NegativeInfinity, rect.Size.Width); + Assert.Equal(double.NegativeInfinity, rect.Size.Height); + Assert.Equal(double.PositiveInfinity, rect.Top); + Assert.Equal(double.PositiveInfinity, rect.TopLeft.X); + Assert.Equal(double.PositiveInfinity, rect.TopLeft.Y); + Assert.Equal(double.NegativeInfinity, rect.TopRight.X); + Assert.Equal(double.PositiveInfinity, rect.TopRight.Y); + Assert.Equal(double.NegativeInfinity, rect.Bottom); + Assert.Equal(double.PositiveInfinity, rect.BottomLeft.X); + Assert.Equal(double.NegativeInfinity, rect.BottomLeft.Y); + Assert.Equal(double.NegativeInfinity, rect.BottomRight.X); + Assert.Equal(double.NegativeInfinity, rect.BottomRight.Y); + Assert.True(rect.IsEmpty); + } + + public static IEnumerable Location_Set_TestData() + { + yield return new object[] { new Point(1, 2) }; + yield return new object[] { new Point(1, 0) }; + yield return new object[] { new Point(0, 2) }; + yield return new object[] { new Point(0, 0) }; + yield return new object[] { new Point(double.NaN, double.NaN) }; + yield return new object[] { new Point(0, double.NaN) }; + yield return new object[] { new Point(double.NaN, 0) }; + } + + [Theory] + [MemberData(nameof(Location_Set_TestData))] + public void Location_Set_GetReturnsExpected(Point value) + { + var rect = new Rect(1, 2, 3, 4); + + // Set. + rect.Location = value; + Assert.Equal(value.X, rect.X); + Assert.Equal(value.Y, rect.Y); + Assert.Equal(3, rect.Width); + Assert.Equal(4, rect.Height); + Assert.Equal(value.X, rect.Location.X); + Assert.Equal(value.Y, rect.Location.Y); + Assert.Equal(3, rect.Size.Width); + Assert.Equal(4, rect.Size.Height); + Assert.Equal(value.Y, rect.Top); + Assert.Equal(value.X, rect.TopLeft.X); + Assert.Equal(value.Y, rect.TopLeft.Y); + Assert.Equal(value.X + 3, rect.TopRight.X); + Assert.Equal(value.Y, rect.TopRight.Y); + Assert.Equal(value.Y + 4, rect.Bottom); + Assert.Equal(value.X, rect.BottomLeft.X); + Assert.Equal(value.Y + 4, rect.BottomLeft.Y); + Assert.Equal(value.X + 3, rect.BottomRight.X); + Assert.Equal(value.Y + 4, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + + // Set same. + rect.Location = value; + Assert.Equal(value.X, rect.X); + Assert.Equal(value.Y, rect.Y); + Assert.Equal(3, rect.Width); + Assert.Equal(4, rect.Height); + Assert.Equal(value.X, rect.Location.X); + Assert.Equal(value.Y, rect.Location.Y); + Assert.Equal(3, rect.Size.Width); + Assert.Equal(4, rect.Size.Height); + Assert.Equal(value.Y, rect.Top); + Assert.Equal(value.X, rect.TopLeft.X); + Assert.Equal(value.Y, rect.TopLeft.Y); + Assert.Equal(value.X + 3, rect.TopRight.X); + Assert.Equal(value.Y, rect.TopRight.Y); + Assert.Equal(value.Y + 4, rect.Bottom); + Assert.Equal(value.X, rect.BottomLeft.X); + Assert.Equal(value.Y + 4, rect.BottomLeft.Y); + Assert.Equal(value.X + 3, rect.BottomRight.X); + Assert.Equal(value.Y + 4, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + } + + [Fact] + public void Location_SetIsEmpty_ThrowsInvalidOperationException() + { + Rect rect = Rect.Empty; + Assert.Throws(() => rect.Location = rect.Location); + Assert.Throws(() => rect.Location = new Point(1, 2)); + } + + public static IEnumerable Size_Set_TestData() + { + yield return new object[] { new Size(1, 2) }; + yield return new object[] { new Size(1, 0) }; + yield return new object[] { new Size(0, 2) }; + yield return new object[] { new Size(0, 0) }; + yield return new object[] { new Size(double.NaN, double.NaN) }; + yield return new object[] { new Size(double.NaN, 2) }; + yield return new object[] { new Size(1, double.NaN) }; + yield return new object[] { new Size() }; + } + + [Theory] + [MemberData(nameof(Size_Set_TestData))] + public void Size_Set_GetReturnsExpected(Size value) + { + var rect = new Rect(1, 2, 3, 4); + + // Set. + rect.Size = value; + Assert.Equal(1, rect.X); + Assert.Equal(2, rect.Y); + Assert.Equal(value.Width, rect.Width); + Assert.Equal(value.Height, rect.Height); + Assert.Equal(1, rect.Location.X); + Assert.Equal(2, rect.Location.Y); + Assert.Equal(value.Width, rect.Size.Width); + Assert.Equal(value.Height, rect.Size.Height); + Assert.Equal(2, rect.Top); + Assert.Equal(1, rect.TopLeft.X); + Assert.Equal(2, rect.TopLeft.Y); + Assert.Equal(1 + value.Width, rect.TopRight.X); + Assert.Equal(2, rect.TopRight.Y); + Assert.Equal(2 + value.Height, rect.Bottom); + Assert.Equal(1, rect.BottomLeft.X); + Assert.Equal(2 + value.Height, rect.BottomLeft.Y); + Assert.Equal(1 + value.Width, rect.BottomRight.X); + Assert.Equal(2 + value.Height, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + + // Set same. + rect.Size = value; + Assert.Equal(1, rect.X); + Assert.Equal(2, rect.Y); + Assert.Equal(value.Width, rect.Width); + Assert.Equal(value.Height, rect.Height); + Assert.Equal(1, rect.Location.X); + Assert.Equal(2, rect.Location.Y); + Assert.Equal(value.Width, rect.Size.Width); + Assert.Equal(value.Height, rect.Size.Height); + Assert.Equal(2, rect.Top); + Assert.Equal(1, rect.TopLeft.X); + Assert.Equal(2, rect.TopLeft.Y); + Assert.Equal(1 + value.Width, rect.TopRight.X); + Assert.Equal(2, rect.TopRight.Y); + Assert.Equal(2 + value.Height, rect.Bottom); + Assert.Equal(1, rect.BottomLeft.X); + Assert.Equal(2 + value.Height, rect.BottomLeft.Y); + Assert.Equal(1 + value.Width, rect.BottomRight.X); + Assert.Equal(2 + value.Height, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + } + + [Fact] + public void Size_SetEmpty_Success() + { + var rect = new Rect(1, 2, 3, 4); + + // Set. + rect.Size = Size.Empty; + Assert.Equal(double.PositiveInfinity, rect.X); + Assert.Equal(double.PositiveInfinity, rect.Y); + Assert.Equal(double.NegativeInfinity, rect.Width); + Assert.Equal(double.NegativeInfinity, rect.Height); + Assert.Equal(double.PositiveInfinity, rect.Location.X); + Assert.Equal(double.PositiveInfinity, rect.Location.Y); + Assert.Equal(double.NegativeInfinity, rect.Size.Width); + Assert.Equal(double.NegativeInfinity, rect.Size.Height); + Assert.Equal(double.PositiveInfinity, rect.Top); + Assert.Equal(double.PositiveInfinity, rect.TopLeft.X); + Assert.Equal(double.PositiveInfinity, rect.TopLeft.Y); + Assert.Equal(double.NegativeInfinity, rect.TopRight.X); + Assert.Equal(double.PositiveInfinity, rect.TopRight.Y); + Assert.Equal(double.NegativeInfinity, rect.Bottom); + Assert.Equal(double.PositiveInfinity, rect.BottomLeft.X); + Assert.Equal(double.NegativeInfinity, rect.BottomLeft.Y); + Assert.Equal(double.NegativeInfinity, rect.BottomRight.X); + Assert.Equal(double.NegativeInfinity, rect.BottomRight.Y); + Assert.True(rect.IsEmpty); + } + + [Fact] + public void Size_SetIsEmpty_ThrowsInvalidOperationException() + { + Rect rect = Rect.Empty; + Assert.Throws(() => rect.Size = new Size(1, 2)); + + // Setting to Size.Empty should work. + rect.Size = rect.Size; + Assert.Equal(double.PositiveInfinity, rect.X); + Assert.Equal(double.PositiveInfinity, rect.Y); + Assert.Equal(double.NegativeInfinity, rect.Width); + Assert.Equal(double.NegativeInfinity, rect.Height); + Assert.Equal(double.PositiveInfinity, rect.Location.X); + Assert.Equal(double.PositiveInfinity, rect.Location.Y); + Assert.Equal(double.NegativeInfinity, rect.Size.Width); + Assert.Equal(double.NegativeInfinity, rect.Size.Height); + Assert.Equal(double.PositiveInfinity, rect.Top); + Assert.Equal(double.PositiveInfinity, rect.TopLeft.X); + Assert.Equal(double.PositiveInfinity, rect.TopLeft.Y); + Assert.Equal(double.NegativeInfinity, rect.TopRight.X); + Assert.Equal(double.PositiveInfinity, rect.TopRight.Y); + Assert.Equal(double.NegativeInfinity, rect.Bottom); + Assert.Equal(double.PositiveInfinity, rect.BottomLeft.X); + Assert.Equal(double.NegativeInfinity, rect.BottomLeft.Y); + Assert.Equal(double.NegativeInfinity, rect.BottomRight.X); + Assert.Equal(double.NegativeInfinity, rect.BottomRight.Y); + Assert.True(rect.IsEmpty); + } + + public static IEnumerable X_Set_TestData() + { + yield return new object[] { double.NegativeInfinity }; + yield return new object[] { double.MinValue }; + yield return new object[] { -1 }; + yield return new object[] { double.NegativeZero }; + yield return new object[] { 0 }; + yield return new object[] { 0.1 }; + yield return new object[] { 1 }; + yield return new object[] { double.MaxValue }; + yield return new object[] { double.PositiveInfinity }; + } + + [Theory] + [MemberData(nameof(X_Set_TestData))] + public void X_Set_GetReturnsExpected(double value) + { + var rect = new Rect(1, 2, 3, 4); + + // Set. + rect.X = value; + Assert.Equal(value, rect.X); + Assert.Equal(2, rect.Y); + Assert.Equal(3, rect.Width); + Assert.Equal(4, rect.Height); + Assert.Equal(value, rect.Location.X); + Assert.Equal(2, rect.Location.Y); + Assert.Equal(3, rect.Size.Width); + Assert.Equal(4, rect.Size.Height); + Assert.Equal(2, rect.Top); + Assert.Equal(value, rect.TopLeft.X); + Assert.Equal(2, rect.TopLeft.Y); + Assert.Equal(value + 3, rect.TopRight.X); + Assert.Equal(2, rect.TopRight.Y); + Assert.Equal(6, rect.Bottom); + Assert.Equal(value, rect.BottomLeft.X); + Assert.Equal(6, rect.BottomLeft.Y); + Assert.Equal(value + 3, rect.BottomRight.X); + Assert.Equal(6, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + + // Set same. + rect.X = value; + Assert.Equal(value, rect.X); + Assert.Equal(2, rect.Y); + Assert.Equal(3, rect.Width); + Assert.Equal(4, rect.Height); + Assert.Equal(value, rect.Location.X); + Assert.Equal(2, rect.Location.Y); + Assert.Equal(3, rect.Size.Width); + Assert.Equal(4, rect.Size.Height); + Assert.Equal(2, rect.Top); + Assert.Equal(value, rect.TopLeft.X); + Assert.Equal(2, rect.TopLeft.Y); + Assert.Equal(value + 3, rect.TopRight.X); + Assert.Equal(2, rect.TopRight.Y); + Assert.Equal(6, rect.Bottom); + Assert.Equal(value, rect.BottomLeft.X); + Assert.Equal(6, rect.BottomLeft.Y); + Assert.Equal(value + 3, rect.BottomRight.X); + Assert.Equal(6, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + } + + [Fact] + public void X_SetIsEmpty_ThrowsInvalidOperationException() + { + Rect rect = Rect.Empty; + Assert.Throws(() => rect.X = rect.X); + Assert.Throws(() => rect.X = 1); + } + + public static IEnumerable Y_Set_TestData() + { + yield return new object[] { double.NegativeInfinity }; + yield return new object[] { double.MinValue }; + yield return new object[] { -1 }; + yield return new object[] { double.NegativeZero }; + yield return new object[] { 0 }; + yield return new object[] { 0.1 }; + yield return new object[] { 1 }; + yield return new object[] { double.MaxValue }; + yield return new object[] { double.PositiveInfinity }; + } + + [Theory] + [MemberData(nameof(Y_Set_TestData))] + public void Y_Set_GetReturnsExpected(double value) + { + var rect = new Rect(1, 2, 3, 4); + + // Set. + rect.Y = value; + Assert.Equal(1, rect.X); + Assert.Equal(value, rect.Y); + Assert.Equal(3, rect.Width); + Assert.Equal(4, rect.Height); + Assert.Equal(1, rect.Location.X); + Assert.Equal(value, rect.Location.Y); + Assert.Equal(3, rect.Size.Width); + Assert.Equal(4, rect.Size.Height); + Assert.Equal(value, rect.Top); + Assert.Equal(1, rect.TopLeft.X); + Assert.Equal(value, rect.TopLeft.Y); + Assert.Equal(4, rect.TopRight.X); + Assert.Equal(value, rect.TopRight.Y); + Assert.Equal(value + 4, rect.Bottom); + Assert.Equal(1, rect.BottomLeft.X); + Assert.Equal(value + 4, rect.BottomLeft.Y); + Assert.Equal(4, rect.BottomRight.X); + Assert.Equal(value + 4, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + + // Set same. + rect.Y = value; + Assert.Equal(1, rect.X); + Assert.Equal(value, rect.Y); + Assert.Equal(3, rect.Width); + Assert.Equal(4, rect.Height); + Assert.Equal(1, rect.Location.X); + Assert.Equal(value, rect.Location.Y); + Assert.Equal(3, rect.Size.Width); + Assert.Equal(4, rect.Size.Height); + Assert.Equal(value, rect.Top); + Assert.Equal(1, rect.TopLeft.X); + Assert.Equal(value, rect.TopLeft.Y); + Assert.Equal(4, rect.TopRight.X); + Assert.Equal(value, rect.TopRight.Y); + Assert.Equal(value + 4, rect.Bottom); + Assert.Equal(1, rect.BottomLeft.X); + Assert.Equal(value + 4, rect.BottomLeft.Y); + Assert.Equal(4, rect.BottomRight.X); + Assert.Equal(value + 4, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + } + + [Fact] + public void Y_SetIsEmpty_ThrowsInvalidOperationException() + { + Rect rect = Rect.Empty; + Assert.Throws(() => rect.Y = rect.Y); + Assert.Throws(() => rect.Y = 1); + } + + public static IEnumerable Width_Set_TestData() + { + yield return new object[] { double.NegativeZero }; + yield return new object[] { 0 }; + yield return new object[] { 0.2 }; + yield return new object[] { 1 }; + yield return new object[] { double.MaxValue }; + yield return new object[] { double.PositiveInfinity }; + } + + [Theory] + [MemberData(nameof(Width_Set_TestData))] + public void Width_Set_GetReturnsExpected(double value) + { + var rect = new Rect(1, 2, 3, 4); + + // Set. + rect.Width = value; + Assert.Equal(1, rect.X); + Assert.Equal(2, rect.Y); + Assert.Equal(value, rect.Width); + Assert.Equal(4, rect.Height); + Assert.Equal(1, rect.Location.X); + Assert.Equal(2, rect.Location.Y); + Assert.Equal(value, rect.Size.Width); + Assert.Equal(4, rect.Size.Height); + Assert.Equal(2, rect.Top); + Assert.Equal(1, rect.TopLeft.X); + Assert.Equal(2, rect.TopLeft.Y); + Assert.Equal(value + 1, rect.TopRight.X); + Assert.Equal(2, rect.TopRight.Y); + Assert.Equal(6, rect.Bottom); + Assert.Equal(1, rect.BottomLeft.X); + Assert.Equal(6, rect.BottomLeft.Y); + Assert.Equal(value + 1, rect.BottomRight.X); + Assert.Equal(6, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + + // Set same. + rect.Width = value; + Assert.Equal(1, rect.X); + Assert.Equal(2, rect.Y); + Assert.Equal(value, rect.Width); + Assert.Equal(4, rect.Height); + Assert.Equal(1, rect.Location.X); + Assert.Equal(2, rect.Location.Y); + Assert.Equal(value, rect.Size.Width); + Assert.Equal(4, rect.Size.Height); + Assert.Equal(2, rect.Top); + Assert.Equal(1, rect.TopLeft.X); + Assert.Equal(2, rect.TopLeft.Y); + Assert.Equal(value + 1, rect.TopRight.X); + Assert.Equal(2, rect.TopRight.Y); + Assert.Equal(6, rect.Bottom); + Assert.Equal(1, rect.BottomLeft.X); + Assert.Equal(6, rect.BottomLeft.Y); + Assert.Equal(value + 1, rect.BottomRight.X); + Assert.Equal(6, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + } + + [Fact] + public void Width_SetIsEmpty_ThrowsInvalidOperationException() + { + Rect rect = Rect.Empty; + Assert.Throws(() => rect.Width = rect.Width); + Assert.Throws(() => rect.Width = 1); + Assert.Throws(() => rect.Width = -1); + } + + [Theory] + [InlineData(double.NegativeInfinity)] + [InlineData(double.MinValue)] + [InlineData(-1)] + public void Width_SetNegative_ThrowsArgumentException(double width) + { + var rect = new Rect(1, 2, 3, 4); + // TODO: should have paramName + Assert.Throws(() => rect.Width = width); + } + + public static IEnumerable Height_Set_TestData() + { + yield return new object[] { double.NegativeZero }; + yield return new object[] { 0 }; + yield return new object[] { 0.2 }; + yield return new object[] { 1 }; + yield return new object[] { double.MaxValue }; + yield return new object[] { double.PositiveInfinity }; + } + + [Theory] + [MemberData(nameof(Height_Set_TestData))] + public void Height_Set_GetReturnsExpected(double value) + { + var rect = new Rect(1, 2, 3, 4); + + // Set. + rect.Height = value; + Assert.Equal(1, rect.X); + Assert.Equal(2, rect.Y); + Assert.Equal(3, rect.Width); + Assert.Equal(value, rect.Height); + Assert.Equal(1, rect.Location.X); + Assert.Equal(2, rect.Location.Y); + Assert.Equal(3, rect.Size.Width); + Assert.Equal(value, rect.Size.Height); + Assert.Equal(2, rect.Top); + Assert.Equal(1, rect.TopLeft.X); + Assert.Equal(2, rect.TopLeft.Y); + Assert.Equal(4, rect.TopRight.X); + Assert.Equal(2, rect.TopRight.Y); + Assert.Equal(value + 2, rect.Bottom); + Assert.Equal(1, rect.BottomLeft.X); + Assert.Equal(value + 2, rect.BottomLeft.Y); + Assert.Equal(4, rect.BottomRight.X); + Assert.Equal(value + 2, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + + // Set same. + rect.Height = value; + Assert.Equal(1, rect.X); + Assert.Equal(2, rect.Y); + Assert.Equal(3, rect.Width); + Assert.Equal(value, rect.Height); + Assert.Equal(1, rect.Location.X); + Assert.Equal(2, rect.Location.Y); + Assert.Equal(3, rect.Size.Width); + Assert.Equal(value, rect.Size.Height); + Assert.Equal(2, rect.Top); + Assert.Equal(1, rect.TopLeft.X); + Assert.Equal(2, rect.TopLeft.Y); + Assert.Equal(4, rect.TopRight.X); + Assert.Equal(2, rect.TopRight.Y); + Assert.Equal(value + 2, rect.Bottom); + Assert.Equal(1, rect.BottomLeft.X); + Assert.Equal(value + 2, rect.BottomLeft.Y); + Assert.Equal(4, rect.BottomRight.X); + Assert.Equal(value + 2, rect.BottomRight.Y); + Assert.False(rect.IsEmpty); + } + + [Fact] + public void Height_SetIsEmpty_ThrowsInvalidOperationException() + { + Rect rect = Rect.Empty; + Assert.Throws(() => rect.Height = rect.Height); + Assert.Throws(() => rect.Height = 1); + Assert.Throws(() => rect.Height = -1); + } + + [Theory] + [InlineData(double.NegativeInfinity)] + [InlineData(double.MinValue)] + [InlineData(-1)] + public void Height_SetNegative_ThrowsArgumentException(double height) + { + var rect = new Rect(1, 2, 3, 4); + // TODO: should have paramName + Assert.Throws(() => rect.Height = height); + } + + public static IEnumerable Contains_Point_TestData() + { + // Normal. + yield return new object[] { new Rect(1, 2, 3, 4), new Point(), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(1, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(1, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(1, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(1, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(1, 6), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(1, 7), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(2, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(2, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(2, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(2, 6), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(2, 7), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(3, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(3, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(3, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(3, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(3, 6), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(3, 7), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(4, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(4, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(4, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(4, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(4, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(4, 6), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(4, 7), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(5, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(5, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(5, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(5, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(5, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(5, 6), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(5, 7), false }; + + // Empty width. + yield return new object[] { new Rect(1, 2, 0, 4), new Point(), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(0, 0), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(0, 1), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(0, 2), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(0, 3), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(0, 4), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(0, 5), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(0, 6), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(0, 7), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(1, 1), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(1, 2), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(1, 3), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(1, 4), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(1, 5), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(1, 6), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(1, 6), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(1, 7), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(2, 1), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(2, 2), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(2, 3), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(2, 4), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(2, 5), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(2, 6), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(2, 6), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Point(2, 7), false }; + + // Empty height. + yield return new object[] { new Rect(1, 2, 3, 0), new Point(), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(0, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(0, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(0, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(0, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(0, 6), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(0, 7), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(1, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(1, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(1, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(1, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(1, 6), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(1, 7), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(2, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(2, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(2, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(2, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(2, 6), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(2, 7), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(3, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(3, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(3, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(3, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(3, 6), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(3, 7), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(4, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(4, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(4, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(4, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(4, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(4, 6), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(4, 7), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(5, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(5, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(5, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(5, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(5, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(5, 6), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Point(5, 7), false }; + + // Empty width & height. + yield return new object[] { new Rect(1, 2, 0, 0), new Point(), false }; + yield return new object[] { new Rect(1, 2, 0, 0), new Point(0, 0), false }; + yield return new object[] { new Rect(1, 2, 0, 0), new Point(0, 1), false }; + yield return new object[] { new Rect(1, 2, 0, 0), new Point(0, 2), false }; + yield return new object[] { new Rect(1, 2, 0, 0), new Point(1, 1), false }; + yield return new object[] { new Rect(1, 2, 0, 0), new Point(1, 2), true }; + yield return new object[] { new Rect(1, 2, 0, 0), new Point(1, 3), false }; + yield return new object[] { new Rect(1, 2, 0, 0), new Point(2, 1), false }; + yield return new object[] { new Rect(1, 2, 0, 0), new Point(2, 2), false }; + yield return new object[] { new Rect(1, 2, 0, 0), new Point(2, 3), false }; + + // Zero. + yield return new object[] { new Rect(0, 0, 0, 0), new Point(), true }; + yield return new object[] { new Rect(0, 0, 0, 0), new Point(0, 0), true }; + yield return new object[] { new Rect(0, 0, 0, 0), new Point(1, 0), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Point(0, 1), false }; + + // Default. + yield return new object[] { new Rect(), new Point(), true }; + yield return new object[] { new Rect(), new Point(0, 0), true }; + yield return new object[] { new Rect(), new Point(1, 0), false }; + yield return new object[] { new Rect(), new Point(0, 1), false }; + + // Infinite bounds. + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), new Point(0, 0), true }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), new Point(1, 2), true }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), new Point(double.NegativeInfinity, double.NegativeInfinity), true }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), new Point(double.MinValue, double.MinValue), true }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), new Point(double.MaxValue, double.MaxValue), true }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), new Point(double.PositiveInfinity, double.PositiveInfinity), false }; + + yield return new object[] { new Rect(double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity), new Point(double.PositiveInfinity, double.PositiveInfinity), false }; + yield return new object[] { new Rect(double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity), new Point(double.MaxValue, double.MaxValue), false }; + + // Infinite width/height. + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Point(0, 0), false }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Point(1, 2), true }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Point(2, 3), true }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Point(double.PositiveInfinity, double.PositiveInfinity), false }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Point(double.NegativeInfinity, double.NegativeInfinity), false }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Point(double.NaN, double.NaN), false }; + + // NaN + yield return new object[] { new Rect(double.NaN, 2, 3, 4), new Point(1, 2), false }; + yield return new object[] { new Rect(double.NaN, 2, 3, 4), new Point(double.NaN, 2), false }; + yield return new object[] { new Rect(1, double.NaN, 3, 4), new Point(1, 2), false }; + yield return new object[] { new Rect(1, double.NaN, 3, 4), new Point(1, double.NaN), false }; + yield return new object[] { new Rect(1, 2, double.NaN, 4), new Point(1, 2), false }; + yield return new object[] { new Rect(1, 2, double.NaN, 4), new Point(1, double.NaN), false }; + yield return new object[] { new Rect(1, 2, 3, double.NaN), new Point(1, 2), false }; + yield return new object[] { new Rect(1, 2, 3, double.NaN), new Point(1, double.NaN), false }; + + // Empty. + yield return new object[] { Rect.Empty, new Point(), false }; + yield return new object[] { Rect.Empty, new Point(0, 0), false }; + yield return new object[] { Rect.Empty, new Point(1, 2), false }; + yield return new object[] { Rect.Empty, new Point(double.PositiveInfinity, double.PositiveInfinity), false }; + yield return new object[] { Rect.Empty, new Point(double.NaN, double.NaN), false }; + } + + [Theory] + [MemberData(nameof(Contains_Point_TestData))] + public void Contains_InvokePoint_ReturnsExpected(Rect rect, Point point, bool expected) + { + Assert.Equal(expected, rect.Contains(point)); + } + + [Theory] + [MemberData(nameof(Contains_Point_TestData))] + public void Contains_InvokeDoubleDouble_ReturnsExpected(Rect rect, Point point, bool expected) + { + Assert.Equal(expected, rect.Contains(point.X, point.Y)); + } + + public static IEnumerable Contains_Rect_TestData() + { + // Normal - from top left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 4, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 4, 5), false }; + + // Normal - from middle left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 4, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 4, 5), false }; + + // Normal - from bottom left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 4, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 4, 5), false }; + + // Normal - from bottom middle. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 4, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 4, 5), false }; + + // Normal - from bottom right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 4, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 4, 5), false }; + + // Normal - from middle right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 4, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 4, 5), false }; + + // Normal - from top right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 4, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 4, 5), false }; + + // Normal - from middle. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 2), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 4, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 4, 5), false }; + + // Normal - from outside top left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 1, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 1, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 1, 0, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 1, 2, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 1, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 0, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 0, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 0, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 0, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 0, 0, 0), false }; + + // Normal - from outside middle left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 0, 0), false }; + + // Normal - from outside bottom left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 0, 0), false }; + + // Normal - from outside bottom middle. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 0, 0), false }; + + // Normal - from outside bottom right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 0, 0), false }; + + // From outside middle right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 0, 0), false }; + + // From outside top right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 0, 0), false }; + + // Normal - empty. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(), false }; + yield return new object[] { new Rect(1, 2, 3, 4), Rect.Empty, false }; + + // Empty width. + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 5), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 4), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 3), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 2), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 5), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 4), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 3), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 2), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 1), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 1, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(0, 1, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(0, 0, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(0, 0, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(), false }; + yield return new object[] { new Rect(1, 2, 0, 4), Rect.Empty, false }; + + // Empty height. + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 4, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 4, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 3, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 3, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 2, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 0, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(0, 1, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(0, 0, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(), false }; + yield return new object[] { new Rect(1, 2, 3, 0), Rect.Empty, false }; + + // Zero. + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(), true }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 0), true }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 0, 0, 0), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 1, 0, 0), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 1, 0), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 1), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 1, 1), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 2, 3, 4), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 2, 3, 0), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 2, 0, 4), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 2, 0, 0), false }; + yield return new object[] { new Rect(0, 0, 0, 0), Rect.Empty, false }; + + // Default. + yield return new object[] { new Rect(), new Rect(), true }; + yield return new object[] { new Rect(), new Rect(0, 0, 0, 0), true }; + yield return new object[] { new Rect(), new Rect(1, 0, 0, 0), false }; + yield return new object[] { new Rect(), new Rect(0, 1, 0, 0), false }; + yield return new object[] { new Rect(), new Rect(0, 0, 1, 0), false }; + yield return new object[] { new Rect(), new Rect(0, 0, 0, 1), false }; + yield return new object[] { new Rect(), new Rect(0, 0, 1, 1), false }; + yield return new object[] { new Rect(), new Rect(1, 2, 3, 4), false }; + yield return new object[] { new Rect(), new Rect(1, 2, 3, 0), false }; + yield return new object[] { new Rect(), new Rect(1, 2, 0, 4), false }; + yield return new object[] { new Rect(), new Rect(1, 2, 0, 0), false }; + yield return new object[] { new Rect(), Rect.Empty, false }; + + // Empty. + yield return new object[] { Rect.Empty, Rect.Empty, false }; + yield return new object[] { Rect.Empty, new Rect(), false }; + yield return new object[] { Rect.Empty, new Rect(0, 0, 0, 0), false }; + yield return new object[] { Rect.Empty, new Rect(1, 0, 0, 0), false }; + yield return new object[] { Rect.Empty, new Rect(0, 1, 0, 0), false }; + yield return new object[] { Rect.Empty, new Rect(0, 0, 1, 0), false }; + yield return new object[] { Rect.Empty, new Rect(0, 0, 0, 1), false }; + yield return new object[] { Rect.Empty, new Rect(1, 2, 3, 4), false }; + yield return new object[] { Rect.Empty, new Rect(1, 2, 3, 0), false }; + yield return new object[] { Rect.Empty, new Rect(1, 2, 0, 4), false }; + yield return new object[] { Rect.Empty, new Rect(1, 2, 0, 0), false }; + } + + [Theory] + [MemberData(nameof(Contains_Rect_TestData))] + public void Contains_InvokeRect_ReturnsExpected(Rect rect, Rect other, bool expected) + { + Assert.Equal(expected, rect.Contains(other)); + } + + public static IEnumerable Equals_TestData() + { + // Normal. + yield return new object?[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 4), true, true }; + yield return new object?[] { new Rect(1, 2, 3, 4), new Rect(2, 2, 3, 4), false, false }; + yield return new object?[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 4), false, false }; + yield return new object?[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 4, 4), false, false }; + yield return new object?[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 5), false, false }; + yield return new object?[] { new Rect(1, 2, 3, 4), new Rect(double.NaN, 2, 3, 4), false, false }; + yield return new object?[] { new Rect(1, 2, 3, 4), new Rect(1, double.NaN, 3, 4), false, false }; + yield return new object?[] { new Rect(1, 2, 3, 4), new Rect(1, 2, double.NaN, 4), false, false }; + yield return new object?[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, double.NaN), false, false }; + yield return new object?[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 0, 0), false, false }; + yield return new object?[] { new Rect(1, 2, 3, 4), new Rect(), false, false }; + yield return new object?[] { new Rect(1, 2, 3, 4), Rect.Empty, false, false }; + + // NaN x. + yield return new object?[] { new Rect(double.NaN, 2, 3, 4), new Rect(double.NaN, 2, 3, 4), true, true }; + yield return new object?[] { new Rect(double.NaN, 2, 3, 4), new Rect(2, 2, 3, 4), false, false }; + yield return new object?[] { new Rect(double.NaN, 2, 3, 4), new Rect(double.NaN, double.NaN, 3, 4), false, false }; + yield return new object?[] { new Rect(double.NaN, 2, 3, 4), new Rect(double.NaN, 2, double.NaN, 4), false, false }; + yield return new object?[] { new Rect(double.NaN, 2, 3, 4), new Rect(double.NaN, 2, 3, double.NaN), false, false }; + + // NaN y. + yield return new object?[] { new Rect(1, double.NaN, 3, 4), new Rect(1, double.NaN, 3, 4), true, true }; + yield return new object?[] { new Rect(1, double.NaN, 3, 4), new Rect(1, 3, 3, 4), false, false }; + yield return new object?[] { new Rect(1, double.NaN, 3, 4), new Rect(double.NaN, double.NaN, 3, 4), false, false }; + yield return new object?[] { new Rect(1, double.NaN, 3, 4), new Rect(1, double.NaN, double.NaN, 4), false, false }; + yield return new object?[] { new Rect(1, double.NaN, 3, 4), new Rect(1, double.NaN, 3, double.NaN), false, false }; + + // NaN width. + yield return new object?[] { new Rect(1, 2, double.NaN, 4), new Rect(1, 2, double.NaN, 4), true, true }; + yield return new object?[] { new Rect(1, 2, double.NaN, 4), new Rect(1, 2, 4, 4), false, false }; + yield return new object?[] { new Rect(1, 2, double.NaN, 4), new Rect(double.NaN, 2, double.NaN, 4), false, false }; + yield return new object?[] { new Rect(1, 2, double.NaN, 4), new Rect(1, double.NaN, double.NaN, 4), false, false }; + yield return new object?[] { new Rect(1, 2, double.NaN, 4), new Rect(1, 2, double.NaN, double.NaN), false, false }; + + // NaN height. + yield return new object?[] { new Rect(1, 2, 3, double.NaN), new Rect(1, 2, 3, double.NaN), true, true }; + yield return new object?[] { new Rect(1, 2, 3, double.NaN), new Rect(1, 2, 3, 4), false, false }; + yield return new object?[] { new Rect(1, 2, 3, double.NaN), new Rect(double.NaN, 2, 3, double.NaN), false, false }; + yield return new object?[] { new Rect(1, 2, 3, double.NaN), new Rect(1, double.NaN, 3, double.NaN), false, false }; + yield return new object?[] { new Rect(1, 2, 3, double.NaN), new Rect(1, 2, double.NaN, double.NaN), false, false }; + + // NaN x, y, width, height. + yield return new object?[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), new Rect(double.NaN, double.NaN, double.NaN, double.NaN), true, true }; + yield return new object?[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), new Rect(1, 2, 3, 4), false, false }; + yield return new object?[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), new Rect(1, double.NaN, double.NaN, double.NaN), false, false }; + yield return new object?[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), new Rect(double.NaN, 2, double.NaN, double.NaN), false, false }; + yield return new object?[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), new Rect(double.NaN, double.NaN, 3, double.NaN), false, false }; + yield return new object?[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), new Rect(double.NaN, double.NaN, double.NaN, 4), false, false }; + + // Zero. + yield return new object?[] { new Rect(0, 0, 0, 0), new Rect(), true, true }; + yield return new object?[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 0), true, true }; + yield return new object?[] { new Rect(0, 0, 0, 0), new Rect(1, 0, 0, 0), false, false }; + yield return new object?[] { new Rect(0, 0, 0, 0), new Rect(0, 1, 0, 0), false, false }; + yield return new object?[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 1, 0), false, false }; + yield return new object?[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 1), false, false }; + yield return new object?[] { new Rect(0, 0, 0, 0), Rect.Empty, false, true }; + + // Default. + yield return new object?[] { new Rect(), new Rect(), true, true }; + yield return new object?[] { new Rect(), new Rect(0, 0, 0, 0), true, true }; + yield return new object?[] { new Rect(), new Rect(1, 0, 0, 0), false, false }; + yield return new object?[] { new Rect(), new Rect(0, 1, 0, 0), false, false }; + yield return new object?[] { new Rect(), new Rect(0, 0, 1, 0), false, false }; + yield return new object?[] { new Rect(), new Rect(0, 0, 0, 1), false, false }; + yield return new object?[] { new Rect(), Rect.Empty, false, true }; + + // Empty. + yield return new object?[] { Rect.Empty, Rect.Empty, true, true }; + yield return new object?[] { Rect.Empty, new Rect(1, 2, 3, 4), false, false }; + yield return new object?[] { Rect.Empty, new Rect(0, 0, 0, 0), false, true }; + yield return new object?[] { Rect.Empty, new Rect(), false, true }; + + // Other. + yield return new object?[] { Rect.Empty, new object(), false, false }; + yield return new object?[] { Rect.Empty, null, false, false }; + yield return new object?[] { new Rect(), new object(), false, false }; + yield return new object?[] { new Rect(), null, false, false }; + yield return new object?[] { new Rect(0, 0, 0, 0), new object(), false, false }; + yield return new object?[] { new Rect(0, 0, 0, 0), null, false, false }; + yield return new object?[] { new Rect(1, 2, 3, 4), new object(), false, false }; + yield return new object?[] { new Rect(1, 2, 3, 4), null, false, false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void Equals_Object_ReturnsExpected(Rect rect, object o, bool expected, bool expectedHashCode) + { + Assert.Equal(expected, rect.Equals(o)); + if (o is Rect value) + { + Assert.Equal(expected, rect.Equals(value)); + Assert.Equal(expected, value.Equals(rect)); + Assert.Equal(expected, Rect.Equals(rect, value)); + Assert.Equal(expected, Rect.Equals(value, rect)); + Assert.Equal(expectedHashCode, rect.GetHashCode().Equals(value.GetHashCode())); + } + } + + public static IEnumerable EqualityOperator_TestData() + { + // Normal. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 2, 3, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 4, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(double.NaN, 2, 3, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, double.NaN, 3, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, double.NaN, 4), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, double.NaN), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(), false }; + yield return new object[] { new Rect(1, 2, 3, 4), Rect.Empty, false }; + + // NaN x. + yield return new object[] { new Rect(double.NaN, 2, 3, 4), new Rect(double.NaN, 2, 3, 4), false }; + yield return new object[] { new Rect(double.NaN, 2, 3, 4), new Rect(2, 2, 3, 4), false }; + yield return new object[] { new Rect(double.NaN, 2, 3, 4), new Rect(double.NaN, double.NaN, 3, 4), false }; + yield return new object[] { new Rect(double.NaN, 2, 3, 4), new Rect(double.NaN, 2, double.NaN, 4), false }; + yield return new object[] { new Rect(double.NaN, 2, 3, 4), new Rect(double.NaN, 2, 3, double.NaN), false }; + + // NaN y. + yield return new object[] { new Rect(1, double.NaN, 3, 4), new Rect(1, double.NaN, 3, 4), false }; + yield return new object[] { new Rect(1, double.NaN, 3, 4), new Rect(1, 3, 3, 4), false }; + yield return new object[] { new Rect(1, double.NaN, 3, 4), new Rect(double.NaN, double.NaN, 3, 4), false }; + yield return new object[] { new Rect(1, double.NaN, 3, 4), new Rect(1, double.NaN, double.NaN, 4), false }; + yield return new object[] { new Rect(1, double.NaN, 3, 4), new Rect(1, double.NaN, 3, double.NaN), false }; + + // NaN width. + yield return new object[] { new Rect(1, 2, double.NaN, 4), new Rect(1, 2, double.NaN, 4), false }; + yield return new object[] { new Rect(1, 2, double.NaN, 4), new Rect(1, 2, 4, 4), false }; + yield return new object[] { new Rect(1, 2, double.NaN, 4), new Rect(double.NaN, 2, double.NaN, 4), false }; + yield return new object[] { new Rect(1, 2, double.NaN, 4), new Rect(1, double.NaN, double.NaN, 4), false }; + yield return new object[] { new Rect(1, 2, double.NaN, 4), new Rect(1, 2, double.NaN, double.NaN), false }; + + // NaN height. + yield return new object[] { new Rect(1, 2, 3, double.NaN), new Rect(1, 2, 3, double.NaN), false }; + yield return new object[] { new Rect(1, 2, 3, double.NaN), new Rect(1, 2, 3, 4), false }; + yield return new object[] { new Rect(1, 2, 3, double.NaN), new Rect(double.NaN, 2, 3, double.NaN), false }; + yield return new object[] { new Rect(1, 2, 3, double.NaN), new Rect(1, double.NaN, 3, double.NaN), false }; + yield return new object[] { new Rect(1, 2, 3, double.NaN), new Rect(1, 2, double.NaN, double.NaN), false }; + + // NaN x, y, width, height. + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), new Rect(double.NaN, double.NaN, double.NaN, double.NaN), false }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), new Rect(1, 2, 3, 4), false }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), new Rect(1, double.NaN, double.NaN, double.NaN), false }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), new Rect(double.NaN, 2, double.NaN, double.NaN), false }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), new Rect(double.NaN, double.NaN, 3, double.NaN), false }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), new Rect(double.NaN, double.NaN, double.NaN, 4), false }; + + // Zero. + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(), true }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 0), true }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 0, 0, 0), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 1, 0, 0), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 1, 0), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 1), false }; + yield return new object[] { new Rect(0, 0, 0, 0), Rect.Empty, false }; + + // Default. + yield return new object[] { new Rect(), new Rect(), true }; + yield return new object[] { new Rect(), new Rect(0, 0, 0, 0), true }; + yield return new object[] { new Rect(), new Rect(1, 0, 0, 0), false }; + yield return new object[] { new Rect(), new Rect(0, 1, 0, 0), false }; + yield return new object[] { new Rect(), new Rect(0, 0, 1, 0), false }; + yield return new object[] { new Rect(), new Rect(0, 0, 0, 1), false }; + yield return new object[] { new Rect(), Rect.Empty, false }; + + // Empty. + yield return new object[] { Rect.Empty, Rect.Empty, true }; + yield return new object[] { Rect.Empty, new Rect(1, 2, 3, 4), false }; + yield return new object[] { Rect.Empty, new Rect(0, 0, 0, 0), false }; + yield return new object[] { Rect.Empty, new Rect(), false }; + } + + [Theory] + [MemberData(nameof(EqualityOperator_TestData))] + public void EqualityOperator_Invoke_ReturnsExpected(Rect rect1, Rect rect2, bool expected) + { + Assert.Equal(expected, rect1 == rect2); + Assert.Equal(expected, rect2 == rect1); + Assert.Equal(!expected, rect1 != rect2); + Assert.Equal(!expected, rect2 != rect1); + } + + [Fact] + public void GetHashCode_InvokeDefault_ReturnsEqual() + { + var rect = new Rect(); + Assert.Equal(0, rect.GetHashCode()); + Assert.Equal(rect.GetHashCode(), rect.GetHashCode()); + } + + [Fact] + public void GetHashCode_InvokeEmpty_ReturnsEqual() + { + Rect rect = Rect.Empty; + Assert.Equal(0, rect.GetHashCode()); + Assert.Equal(rect.GetHashCode(), rect.GetHashCode()); + } + + [Fact] + public void GetHashCode_InvokeNormal_ReturnsEqual() + { + var rect = new Rect(1, 2, 3, 4); + Assert.NotEqual(0, rect.GetHashCode()); + Assert.Equal(rect.GetHashCode(), rect.GetHashCode()); + } + + public static IEnumerable Inflate_TestData() + { + // Normal. + yield return new object[] { new Rect(1, 2, 3, 4), 1, 2, new Rect(0, 0, 5, 8) }; + yield return new object[] { new Rect(1, 2, 3, 4), 1.5, 2.5, new Rect(-0.5, -0.5, 6, 9) }; + yield return new object[] { new Rect(1, 2, 3, 4), 0.5, 0.25, new Rect(0.5,1.75,4,4.5) }; + yield return new object[] { new Rect(1, 2, 3, 4), 0, 0, new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), -1, 0, new Rect(2, 2, 1, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), -1, 2, new Rect(2, 0, 1, 8) }; + yield return new object[] { new Rect(1, 2, 3, 4), 0, -2, new Rect(1, 4, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), 2, -2, new Rect(-1, 4, 7, 0) }; + + // Large. + yield return new object[] { new Rect(double.MaxValue, double.MaxValue, 3, 4), 1, 2, new Rect(double.MaxValue, double.MaxValue, 5, 8) }; + yield return new object[] { new Rect(double.MaxValue, double.MaxValue, 3, 4), 0, 0, new Rect(double.MaxValue, double.MaxValue, 3, 4) }; + yield return new object[] { new Rect(double.MaxValue, double.MaxValue, 3, 4), double.MaxValue, double.MaxValue, new Rect(0, 0, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(double.MinValue, double.MinValue, 3, 4), -1, -2, new Rect(double.MinValue, double.MinValue, 1, 0) }; + yield return new object[] { new Rect(double.MinValue, double.MinValue, 3, 4), 0, 0, new Rect(double.MinValue, double.MinValue, 3, 4) }; + yield return new object[] { new Rect(double.MinValue, double.MinValue, 3, 4), double.MinValue, double.MinValue, Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), double.MaxValue, double.MaxValue, new Rect(double.MinValue, double.MinValue, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(0, 0, 3, 4), double.MaxValue, double.MaxValue, new Rect(double.MinValue, double.MinValue, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(-1, -2, 3, 4), double.MinValue, double.MinValue, Rect.Empty }; + yield return new object[] { new Rect(0, 0, 3, 4), double.MinValue, double.MinValue, Rect.Empty }; + + // Infinite. + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), 0, 0, new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), 5, 6, new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), -5, -6, new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), double.PositiveInfinity, double.PositiveInfinity, new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), double.NegativeInfinity, double.NegativeInfinity, Rect.Empty }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), 0, 0, new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), 5, 6, new Rect(-4, -4, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), -5, -6, new Rect(6 ,8, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), double.PositiveInfinity, double.PositiveInfinity, new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), double.NegativeInfinity, double.NegativeInfinity, Rect.Empty }; + + // NaN. + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), 0, 0, Rect.Empty }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), 5, 6, Rect.Empty }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), -5, -6, Rect.Empty }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), double.PositiveInfinity, double.PositiveInfinity, Rect.Empty }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), double.NegativeInfinity, double.NegativeInfinity, Rect.Empty }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), double.NaN, double.NaN, Rect.Empty }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), 0, 0, Rect.Empty }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), 5, 6, Rect.Empty }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), -5, -6, Rect.Empty }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), double.PositiveInfinity, double.PositiveInfinity, Rect.Empty }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), double.NegativeInfinity, double.NegativeInfinity, Rect.Empty }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), double.NaN, double.NaN, Rect.Empty }; + + // Zero. + yield return new object[] { new Rect(0, 0, 0, 0), 1, 2, new Rect(-1, -2, 2, 4) }; + yield return new object[] { new Rect(0, 0, 0, 0), -1, 0, Rect.Empty }; + yield return new object[] { new Rect(0, 0, 0, 0), 0, -2, Rect.Empty }; + yield return new object[] { new Rect(0, 0, 0, 0), -1, -2, Rect.Empty }; + yield return new object[] { new Rect(0, 0, 0, 0), 0, 0, new Rect() }; + + // Default. + yield return new object[] { new Rect(), 1, 2, new Rect(-1, -2, 2, 4) }; + yield return new object[] { new Rect(), -1, 0, Rect.Empty }; + yield return new object[] { new Rect(), 0, -2, Rect.Empty }; + yield return new object[] { new Rect(), -1, -2, Rect.Empty }; + yield return new object[] { new Rect(), 0, 0, new Rect() }; + } + + [Theory] + [MemberData(nameof(Inflate_TestData))] + public void Inflate_Invoke_ReturnsExpected(Rect rect, double width, double height, Rect expected) + { + Rect copy = rect; + + Assert.Equal(expected, Rect.Inflate(rect, width, height)); + Assert.Equal(copy, rect); + + if (width >= 0 && height >= 0) + { + Assert.Equal(expected, Rect.Inflate(rect, new Size(width, height))); + Assert.Equal(copy, rect); + } + + rect.Inflate(width, height); + Assert.Equal(expected, rect); + + if (width >= 0 && height >= 0) + { + rect = copy; + rect.Inflate(new Size(width, height)); + Assert.Equal(expected, rect); + } + } + + [Fact] + public void Inflate_Empty_ThrowsInvalidOperationException() + { + Assert.Throws(() => Rect.Empty.Inflate(0, 0)); + Assert.Throws(() => Rect.Empty.Inflate(new Size(0, 0))); + Assert.Throws(() => Rect.Inflate(Rect.Empty, 0, 0)); + Assert.Throws(() => Rect.Inflate(Rect.Empty, new Size(0, 0))); + } + + public static IEnumerable Intersect_Rect_TestData() + { + // Normal - from top left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 5), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 3), new Rect(1, 2, 3, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 2), new Rect(1, 2, 3, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 1), new Rect(1, 2, 3, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 0), new Rect(1, 2, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 5), new Rect(1, 2, 2, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 4), new Rect(1, 2, 2, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 3), new Rect(1, 2, 2, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 2), new Rect(1, 2, 2, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 1), new Rect(1, 2, 2, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 0), new Rect(1, 2, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 5), new Rect(1, 2, 1, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 4), new Rect(1, 2, 1, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 3), new Rect(1, 2, 1, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 2), new Rect(1, 2, 1, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 1), new Rect(1, 2, 1, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 0), new Rect(1, 2, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 5), new Rect(1, 2, 0, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 3), new Rect(1, 2, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 2), new Rect(1, 2, 0, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 1), new Rect(1, 2, 0, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 0), new Rect(1, 2, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 4, 4), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 5), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 4, 5), new Rect(1, 2, 3, 4) }; + + // Normal - from middle left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 5), new Rect(1, 3, 3, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 4), new Rect(1, 3, 3, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 3), new Rect(1, 3, 3, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 2), new Rect(1, 3, 3, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 1), new Rect(1, 3, 3, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 0), new Rect(1, 3, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 5), new Rect(1, 3, 2, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 4), new Rect(1, 3, 2, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 3), new Rect(1, 3, 2, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 2), new Rect(1, 3, 2, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 1), new Rect(1, 3, 2, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 0), new Rect(1, 3, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 5), new Rect(1, 3, 1, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 4), new Rect(1, 3, 1, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 3), new Rect(1, 3, 1, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 2), new Rect(1, 3, 1, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 1), new Rect(1, 3, 1, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 0), new Rect(1, 3, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 5), new Rect(1, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 4), new Rect(1, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 3), new Rect(1, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 2), new Rect(1, 3, 0, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 1), new Rect(1, 3, 0, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 0), new Rect(1, 3, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 4, 4), new Rect(1, 3, 3, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 5), new Rect(1, 3, 3, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 4, 5), new Rect(1, 3, 3, 3) }; + + // Normal - from bottom left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 5), new Rect(1, 5, 3, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 4), new Rect(1, 5, 3, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 3), new Rect(1, 5, 3, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 2), new Rect(1, 5, 3, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 1), new Rect(1, 5, 3, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 0), new Rect(1, 5, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 5), new Rect(1, 5, 2, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 4), new Rect(1, 5, 2, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 3), new Rect(1, 5, 2, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 2), new Rect(1, 5, 2, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 1), new Rect(1, 5, 2, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 0), new Rect(1, 5, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 5), new Rect(1, 5, 1, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 4), new Rect(1, 5, 1, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 3), new Rect(1, 5, 1, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 2), new Rect(1, 5, 1, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 1), new Rect(1, 5, 1, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 0), new Rect(1, 5, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 5), new Rect(1, 5, 0, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 4), new Rect(1, 5, 0, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 3), new Rect(1, 5, 0, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 2), new Rect(1, 5, 0, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 1), new Rect(1, 5, 0, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 0), new Rect(1, 5, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 4, 4), new Rect(1, 5, 3, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 5), new Rect(1, 5, 3, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 4, 5), new Rect(1, 5, 3, 1) }; + + // Normal - from bottom middle. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 5), new Rect(2, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 4), new Rect(2, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 3), new Rect(2, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 2), new Rect(2, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 1), new Rect(2, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 0), new Rect(2, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 5), new Rect(2, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 4), new Rect(2, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 3), new Rect(2, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 2), new Rect(2, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 1), new Rect(2, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 0), new Rect(2, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 5), new Rect(2, 6, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 4), new Rect(2, 6, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 3), new Rect(2, 6, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 2), new Rect(2, 6, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 1), new Rect(2, 6, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 0), new Rect(2, 6, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 5), new Rect(2, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 4), new Rect(2, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 3), new Rect(2, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 2), new Rect(2, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 1), new Rect(2, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 0), new Rect(2, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 4, 4), new Rect(2, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 5), new Rect(2, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 4, 5), new Rect(2, 6, 2, 0) }; + + // Normal - from bottom right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 5), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 4), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 3), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 2), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 1), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 0), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 5), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 4), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 3), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 2), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 1), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 0), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 5), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 4), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 3), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 2), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 1), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 0), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 5), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 4), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 3), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 2), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 1), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 0), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 4, 4), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 5), new Rect(4, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 4, 5), new Rect(4, 6, 0, 0) }; + + // Normal - from middle right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 5), new Rect(4, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 4), new Rect(4, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 3), new Rect(4, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 2), new Rect(4, 3, 0, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 1), new Rect(4, 3, 0, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 0), new Rect(4, 3, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 5), new Rect(4, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 4), new Rect(4, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 3), new Rect(4, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 2), new Rect(4, 3, 0, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 1), new Rect(4, 3, 0, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 0), new Rect(4, 3, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 5), new Rect(4, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 4), new Rect(4, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 3), new Rect(4, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 2), new Rect(4, 3, 0, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 1), new Rect(4, 3, 0, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 0), new Rect(4, 3, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 5), new Rect(4, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 4), new Rect(4, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 3), new Rect(4, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 2), new Rect(4, 3, 0, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 1), new Rect(4, 3, 0, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 0), new Rect(4, 3, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 4, 4), new Rect(4, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 5), new Rect(4, 3, 0, 3) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 4, 5), new Rect(4, 3, 0, 3) }; + + // Normal - from top right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 5), new Rect(1, 6, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 4), new Rect(1, 6, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 3), new Rect(1, 6, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 2), new Rect(1, 6, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 1), new Rect(1, 6, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 0), new Rect(1, 6, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 5), new Rect(1, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 4), new Rect(1, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 3), new Rect(1, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 2), new Rect(1, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 1), new Rect(1, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 0), new Rect(1, 6, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 5), new Rect(1, 6, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 4), new Rect(1, 6, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 3), new Rect(1, 6, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 2), new Rect(1, 6, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 1), new Rect(1, 6, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 0), new Rect(1, 6, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 5), new Rect(1, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 4), new Rect(1, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 3), new Rect(1, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 2), new Rect(1, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 1), new Rect(1, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 0), new Rect(1, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 4, 4), new Rect(1, 6, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 5), new Rect(1, 6, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 4, 5), new Rect(1, 6, 3, 0) }; + + // Normal - from middle. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 5), new Rect(2, 4, 2, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 4), new Rect(2, 4, 2, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 3), new Rect(2, 4, 2, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 2), new Rect(2, 4, 2, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 1), new Rect(2, 4, 2, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 0), new Rect(2, 4, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 5), new Rect(2, 4, 2, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 4), new Rect(2, 4, 2, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 3), new Rect(2, 4, 2, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 2), new Rect(2, 4, 2, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 1), new Rect(2, 4, 2, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 0), new Rect(2, 4, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 5), new Rect(2, 4, 1, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 4), new Rect(2, 4, 1, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 3), new Rect(2, 4, 1, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 2), new Rect(2, 4, 1, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 1), new Rect(2, 4, 1, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 0), new Rect(2, 4, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 5), new Rect(2, 4, 0, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 4), new Rect(2, 4, 0, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 3), new Rect(2, 4, 0, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 2), new Rect(2, 4, 0, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 1), new Rect(2, 4, 0, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 0), new Rect(2, 4, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 4, 4), new Rect(2, 4, 2, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 5), new Rect(2, 4, 2, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 4, 5), new Rect(2, 4, 2, 2) }; + + // Normal - from outside top left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 2, 3), new Rect(1, 2, 1, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 1, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 0.5, 0.5), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 0, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 1, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 1, 2, 3), new Rect(1, 2, 1, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 1, 1, 1), new Rect(1, 2, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 1, 0, 3), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 1, 2, 1), new Rect(1, 2, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 1, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 0, 2, 3), new Rect(1, 2, 2, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 0, 1, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 0, 1, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 0, 0, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 0, 0, 0), Rect.Empty }; + + // Normal - from outside middle left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 2, 3), new Rect(1, 4, 1, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 1, 1), new Rect(1, 4, 0, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 0.5, 0.5), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 0, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 1, 0), new Rect(1, 4, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 0, 0), Rect.Empty }; + + // Normal - from outside bottom left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 2, 3), new Rect(1, 6, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 1, 1), new Rect(1, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 0.5, 0.5), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 0, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 1, 0), new Rect(1, 6, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 2, 3), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 1, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 0.5, 0.5), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 0, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 1, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 2, 3), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 1, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 0.5, 0.5), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 0, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 1, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 0, 0), Rect.Empty }; + + // Normal - from outside bottom middle. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 2, 3), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 1, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 0.5, 0.5), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 0, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 0, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 0, 0), Rect.Empty }; + + // Normal - from outside bottom right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 2, 3), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 1, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 0.5, 0.5), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 1, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 0, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 2, 3), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 1, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 0.5, 0.5), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 0, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 1, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 2, 3), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 1, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 0.5, 0.5), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 0, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 1, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 0, 0), Rect.Empty }; + + // From outside middle right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 2, 3), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 1, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 0.5, 0.5), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 0, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 1, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 0, 0), Rect.Empty }; + + // From outside top right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 2, 3), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 1, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 0.5, 0.5), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 0, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 1, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 2, 3), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 1, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 0.5, 0.5), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 0, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 1, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 2, 3), new Rect(4, 2, 0, 1) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 1, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 0.5, 0.5), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 1, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 0, 0), Rect.Empty }; + + // Normal - empty. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 4), Rect.Empty, Rect.Empty }; + + // Empty width. + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 5), new Rect(1, 2, 0, 4) }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 4), new Rect(1, 2, 0, 4) }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 3), new Rect(1, 2, 0, 3) }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 2), new Rect(1, 2, 0, 2) }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 1), new Rect(1, 2, 0, 1) }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 0), new Rect(1, 2, 0, 0) }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 5), new Rect(1, 2, 0, 4) }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 4) }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 3), new Rect(1, 2, 0, 3) }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 2), new Rect(1, 2, 0, 2) }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 1), new Rect(1, 2, 0, 1) }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 0), new Rect(1, 2, 0, 0) }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 1, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(0, 1, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(0, 0, 0, 1), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(0, 0, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 0, 4), Rect.Empty, Rect.Empty }; + + // Empty height. + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 4, 1), new Rect(1, 2, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 4, 0), new Rect(1, 2, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 3, 1), new Rect(1, 2, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 3, 0), new Rect(1, 2, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 2, 1), new Rect(1, 2, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 2, 0), new Rect(1, 2, 2, 0) }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 1, 1), new Rect(1, 2, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 1, 0), new Rect(1, 2, 1, 0) }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 0, 0), new Rect(1, 2, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 0, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(0, 1, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(0, 0, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(), Rect.Empty }; + yield return new object[] { new Rect(1, 2, 3, 0), Rect.Empty, Rect.Empty }; + + // Zero. + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 0, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 1, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 1, 0), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 1), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 1, 1), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 2, 3, 4), Rect.Empty }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 2, 3, 0), Rect.Empty }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 2, 0, 4), Rect.Empty }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 2, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(0, 0, 0, 0), Rect.Empty, Rect.Empty }; + + // Default. + yield return new object[] { new Rect(), new Rect(), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(), new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(), new Rect(1, 0, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(), new Rect(0, 1, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(), new Rect(0, 0, 1, 0), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(), new Rect(0, 0, 0, 1), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(), new Rect(0, 0, 1, 1), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(), new Rect(1, 2, 3, 4), Rect.Empty }; + yield return new object[] { new Rect(), new Rect(1, 2, 3, 0), Rect.Empty }; + yield return new object[] { new Rect(), new Rect(1, 2, 0, 4), Rect.Empty }; + yield return new object[] { new Rect(), new Rect(1, 2, 0, 0), Rect.Empty }; + yield return new object[] { new Rect(), Rect.Empty, Rect.Empty }; + + // Empty. + yield return new object[] { Rect.Empty, Rect.Empty, Rect.Empty }; + yield return new object[] { Rect.Empty, new Rect(), Rect.Empty }; + yield return new object[] { Rect.Empty, new Rect(0, 0, 0, 0), Rect.Empty }; + yield return new object[] { Rect.Empty, new Rect(1, 0, 0, 0), Rect.Empty }; + yield return new object[] { Rect.Empty, new Rect(0, 1, 0, 0), Rect.Empty }; + yield return new object[] { Rect.Empty, new Rect(0, 0, 1, 0), Rect.Empty }; + yield return new object[] { Rect.Empty, new Rect(0, 0, 0, 1), Rect.Empty }; + yield return new object[] { Rect.Empty, new Rect(1, 2, 3, 4), Rect.Empty }; + yield return new object[] { Rect.Empty, new Rect(1, 2, 3, 0), Rect.Empty }; + yield return new object[] { Rect.Empty, new Rect(1, 2, 0, 4), Rect.Empty }; + yield return new object[] { Rect.Empty, new Rect(1, 2, 0, 0), Rect.Empty }; + } + + [Theory] + [MemberData(nameof(Intersect_Rect_TestData))] + public void Intersect_InvokeRect_ReturnsExpected(Rect rect1, Rect rect2, Rect expected) + { + Rect copy1 = rect1; + Rect copy2 = rect2; + + Assert.Equal(expected, Rect.Intersect(rect1, rect2)); + Assert.Equal(copy1, rect1); + Assert.Equal(copy2, rect2); + + rect1.Intersect(rect2); + Assert.Equal(expected, rect1); + Assert.Equal(copy2, rect2); + } + + public static IEnumerable IntersectsWith_Rect_TestData() + { + // Normal - from top left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 4, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 4, 5), true }; + + // Normal - from middle left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 4, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 3, 4, 5), true }; + + // Normal - from bottom left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 4, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 5, 4, 5), true }; + + // Normal - from bottom middle. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 4, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 6, 4, 5), true }; + + // Normal - from bottom right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 4, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 6, 4, 5), true }; + + // Normal - from middle right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 4, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 3, 4, 5), true }; + + // Normal - from top right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 4, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 6, 4, 5), true }; + + // Normal - from middle. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 2), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 4, 4), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 3, 5), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 4, 4, 5), true }; + + // Normal - from outside top left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 1, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 1, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 1, 0, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 1, 2, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 1, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 0, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 0, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 0, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 0, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 0, 0, 0), false }; + + // Normal - from outside middle left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 4, 0, 0), false }; + + // Normal - from outside bottom left. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 6, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 7, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 7, 0, 0), false }; + + // Normal - from outside bottom middle. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 7, 0, 0), false }; + + // Normal - from outside bottom right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 7, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 7, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 5, 0, 0), false }; + + // From outside middle right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 3, 0, 0), false }; + + // From outside top right. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 1, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 2, 3), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(5, 0, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 2, 3), true }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 1, 1), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 0.5, 0.5), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 1, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(4, 0, 0, 0), false }; + + // Normal - empty. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(), false }; + yield return new object[] { new Rect(1, 2, 3, 4), Rect.Empty, false }; + + // Empty width. + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 5), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 4), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 3), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 2), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 5), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 4), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 3), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 2), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 1), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(1, 1, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(0, 1, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(0, 0, 0, 1), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(0, 0, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 0, 4), new Rect(), false }; + yield return new object[] { new Rect(1, 2, 0, 4), Rect.Empty, false }; + + // Empty height. + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 4, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 4, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 3, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 3, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 2, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 2, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 1, 1), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 1, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 2, 0, 0), true }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(1, 0, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(0, 1, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(0, 0, 0, 0), false }; + yield return new object[] { new Rect(1, 2, 3, 0), new Rect(), false }; + yield return new object[] { new Rect(1, 2, 3, 0), Rect.Empty, false }; + + // Zero. + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(), true }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 0), true }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 0, 0, 0), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 1, 0, 0), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 1, 0), true }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 1), true }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 1, 1), true }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 2, 3, 4), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 2, 3, 0), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 2, 0, 4), false }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 2, 0, 0), false }; + yield return new object[] { new Rect(0, 0, 0, 0), Rect.Empty, false }; + + // Default. + yield return new object[] { new Rect(), new Rect(), true }; + yield return new object[] { new Rect(), new Rect(0, 0, 0, 0), true }; + yield return new object[] { new Rect(), new Rect(1, 0, 0, 0), false }; + yield return new object[] { new Rect(), new Rect(0, 1, 0, 0), false }; + yield return new object[] { new Rect(), new Rect(0, 0, 1, 0), true }; + yield return new object[] { new Rect(), new Rect(0, 0, 0, 1), true }; + yield return new object[] { new Rect(), new Rect(0, 0, 1, 1), true }; + yield return new object[] { new Rect(), new Rect(1, 2, 3, 4), false }; + yield return new object[] { new Rect(), new Rect(1, 2, 3, 0), false }; + yield return new object[] { new Rect(), new Rect(1, 2, 0, 4), false }; + yield return new object[] { new Rect(), new Rect(1, 2, 0, 0), false }; + yield return new object[] { new Rect(), Rect.Empty, false }; + + // Empty. + yield return new object[] { Rect.Empty, Rect.Empty, false }; + yield return new object[] { Rect.Empty, new Rect(), false }; + yield return new object[] { Rect.Empty, new Rect(0, 0, 0, 0), false }; + yield return new object[] { Rect.Empty, new Rect(1, 0, 0, 0), false }; + yield return new object[] { Rect.Empty, new Rect(0, 1, 0, 0), false }; + yield return new object[] { Rect.Empty, new Rect(0, 0, 1, 0), false }; + yield return new object[] { Rect.Empty, new Rect(0, 0, 0, 1), false }; + yield return new object[] { Rect.Empty, new Rect(1, 2, 3, 4), false }; + yield return new object[] { Rect.Empty, new Rect(1, 2, 3, 0), false }; + yield return new object[] { Rect.Empty, new Rect(1, 2, 0, 4), false }; + yield return new object[] { Rect.Empty, new Rect(1, 2, 0, 0), false }; + } + + [Theory] + [MemberData(nameof(IntersectsWith_Rect_TestData))] + public void IntersectsWith_InvokeRect_ReturnsExpected(Rect rect, Rect other, bool expected) + { + Assert.Equal(expected, rect.IntersectsWith(other)); + Assert.Equal(expected, other.IntersectsWith(rect)); + } + + public static IEnumerable Offset_TestData() + { + // Normal. + yield return new object[] { new Rect(1, 2, 3, 4), 0, 0, new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), 5, 6, new Rect(6, 8, 3, 4) }; + yield return new object[] { new Rect(1.25, 2.25, 3, 4), 5.5, 6.5, new Rect(6.75, 8.75, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), 5, 0, new Rect(6, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), 0, 6, new Rect(1, 8, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), -5, -6, new Rect(-4, -4, 3, 4) }; + + // Large. + yield return new object[] { new Rect(double.MaxValue, double.MaxValue, 3, 4), 1, 2, new Rect(double.MaxValue, double.MaxValue, 3, 4) }; + yield return new object[] { new Rect(double.MaxValue, double.MaxValue, 3, 4), 0, 0, new Rect(double.MaxValue, double.MaxValue, 3, 4) }; + yield return new object[] { new Rect(double.MaxValue, double.MaxValue, 3, 4), double.MaxValue, double.MaxValue, new Rect(double.PositiveInfinity, double.PositiveInfinity, 3, 4) }; + yield return new object[] { new Rect(double.MinValue, double.MinValue, 3, 4), -1, -2, new Rect(double.MinValue, double.MinValue, 3, 4) }; + yield return new object[] { new Rect(double.MinValue, double.MinValue, 3, 4), 0, 0, new Rect(double.MinValue, double.MinValue, 3, 4) }; + yield return new object[] { new Rect(double.MinValue, double.MinValue, 3, 4), double.MinValue, double.MinValue, new Rect(double.NegativeInfinity, double.NegativeInfinity, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), double.MaxValue, double.MaxValue, new Rect(double.MaxValue, double.MaxValue, 3, 4) }; + yield return new object[] { new Rect(0, 0, 3, 4), double.MaxValue, double.MaxValue, new Rect(double.MaxValue, double.MaxValue, 3, 4) }; + yield return new object[] { new Rect(-1, -2, 3, 4), double.MinValue, double.MinValue, new Rect(double.MinValue, double.MinValue, 3, 4) }; + yield return new object[] { new Rect(0, 0, 3, 4), double.MinValue, double.MinValue, new Rect(double.MinValue, double.MinValue, 3, 4) }; + + // Infinite. + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), 0, 0, new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), 5, 6, new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), -5, -6, new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), double.PositiveInfinity, double.PositiveInfinity, new Rect(double.NaN, double.NaN, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), double.NegativeInfinity, double.NegativeInfinity, new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), 0, 0, new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), 5, 6, new Rect(6, 8, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), -5, -6, new Rect(-4, -4, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), double.PositiveInfinity, double.PositiveInfinity, new Rect(double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), double.NegativeInfinity, double.NegativeInfinity, new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + + // NaN. + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), 0, 0, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), 5, 6, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), -5, -6, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), double.PositiveInfinity, double.PositiveInfinity, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), double.NegativeInfinity, double.NegativeInfinity, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), double.NaN, double.NaN, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), 0, 0, new Rect(1, 2, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), 5, 6, new Rect(6, 8, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), -5, -6, new Rect(-4, -4, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), double.PositiveInfinity, double.PositiveInfinity, new Rect(double.PositiveInfinity, double.PositiveInfinity, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), double.NegativeInfinity, double.NegativeInfinity, new Rect(double.NegativeInfinity, double.NegativeInfinity, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), double.NaN, double.NaN, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + + // Zero. + yield return new object[] { new Rect(0, 0, 0, 0), 0, 0, new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), 1, 2, new Rect(1, 2, 0, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), -1, -2, new Rect(-1, -2, 0, 0) }; + } + + [Theory] + [MemberData(nameof(Offset_TestData))] + public void Offset_Invoke_ReturnsExpected(Rect rect, double offsetX, double offsetY, Rect expected) + { + Rect copy = rect; + + Assert.Equal(expected, Rect.Offset(rect, offsetX, offsetY)); + Assert.Equal(copy, rect); + + Assert.Equal(expected, Rect.Offset(rect, new Vector(offsetX, offsetY))); + Assert.Equal(copy, rect); + + rect.Offset(offsetX, offsetY); + Assert.Equal(expected, rect); + + rect = copy; + rect.Offset(new Vector(offsetX, offsetY)); + Assert.Equal(expected, rect); + } + + [Fact] + public void Offset_Empty_ThrowsInvalidOperationException() + { + Assert.Throws(() => Rect.Empty.Offset(0, 0)); + Assert.Throws(() => Rect.Empty.Offset(new Vector(0, 0))); + Assert.Throws(() => Rect.Offset(Rect.Empty, 0, 0)); + Assert.Throws(() => Rect.Offset(Rect.Empty, new Vector(0, 0))); + } + + public static IEnumerable Parse_TestData() + { + yield return new object[] { "Empty", Rect.Empty }; + yield return new object[] { " Empty ", Rect.Empty }; + yield return new object[] { "0,0,0,0", new Rect() }; + yield return new object[] { "1,2,3,4", new Rect(1, 2, 3, 4) }; + yield return new object[] { "1.1,2.2,3.3,4.4", new Rect(1.1, 2.2, 3.3, 4.4) }; + yield return new object[] { " 1 , 2 , 3, 4", new Rect(1, 2, 3, 4) }; + yield return new object[] { "-1,-2,3,4", new Rect(-1, -2, 3, 4) }; + } + + [Theory] + [MemberData(nameof(Parse_TestData))] + public void Parse_Invoke_Success(string source, Rect expected) + { + Rect result = Rect.Parse(source); + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + Assert.Equal(expected.Width, result.Width, precision: 5); + Assert.Equal(expected.Height, result.Height, precision: 5); + } + + [Fact] + public void Parse_NullSource_ThrowsInvalidOperationException() + { + Assert.Throws(() => Rect.Parse(null)); + } + + public static IEnumerable Parse_InvalidSource_TestData() + { + yield return new object[] { "" }; + yield return new object[] { " " }; + yield return new object[] { "," }; + yield return new object[] { "1" }; + yield return new object[] { "1," }; + yield return new object[] { "1,2" }; + yield return new object[] { "1,2," }; + yield return new object[] { "1,2,3" }; + yield return new object[] { "1,2,3," }; + yield return new object[] { "1,2,3,4," }; + yield return new object[] { "1,2,3,4,5" }; + yield return new object[] { "1,2,4,5,test" }; + yield return new object[] { "Empty," }; + yield return new object[] { "Empty,2,3,4" }; + yield return new object[] { "Identity," }; + } + + [Theory] + [MemberData(nameof(Parse_InvalidSource_TestData))] + public void Parse_InvalidSource_ThrowsInvalidOperationException(string source) + { + Assert.Throws(() => Rect.Parse(source)); + } + + public static IEnumerable Parse_NotDouble_TestData() + { + yield return new object[] { "Identity" }; + yield return new object[] { " Identity " }; + yield return new object[] { "Identity,2,3,4" }; + yield return new object[] { "test" }; + yield return new object[] { "test,2,3,4" }; + yield return new object[] { "1,test,3,4" }; + yield return new object[] { "1,2,test,4" }; + yield return new object[] { "1,2,3,test" }; + yield return new object[] { "1;2;3;4" }; + yield return new object[] { """1"",""2"",""3"",""4""" }; + } + + [Theory] + [MemberData(nameof(Parse_NotDouble_TestData))] + public void Parse_NotDouble_ThrowsFormatException(string source) + { + Assert.Throws(() => Rect.Parse(source)); + } + + public static IEnumerable Parse_Negative_TestData() + { + yield return new object[] { "1,2,-3,4" }; + yield return new object[] { "1,2,3,-4" }; + } + + [Theory] + [MemberData(nameof(Parse_Negative_TestData))] + public void Parse_Negative_ThrowsArgumentException(string source) + { + Assert.Throws(() => Rect.Parse(source)); + } + + public static IEnumerable Scale_TestData() + { + // Normal. + yield return new object[] { new Rect(1, 2, 3, 4), 2, 3, new Rect(2, 6, 6, 12) }; + yield return new object[] { new Rect(1, 2, 3, 4), 2, 1, new Rect(2, 2, 6, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), 1, 3, new Rect(1, 6, 3, 12) }; + yield return new object[] { new Rect(1, 2, 3, 4), 1, 1, new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), 0.5, 0.5, new Rect(0.5, 1, 1.5, 2) }; + yield return new object[] { new Rect(1, 2, 3, 4), 1, 0, new Rect(1, 0, 3, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), 0, 1, new Rect(0, 2, 0, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), 0, 0, new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), -1, 1, new Rect(-4, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), 1, -1, new Rect(1, -6, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), -1, -2, new Rect(-4, -12, 3, 8) }; + yield return new object[] { new Rect(1, 2, 3, 4), -2, -3, new Rect(-8, -18, 6, 12) }; + + // Large. + yield return new object[] { new Rect(double.MaxValue, double.MaxValue, 3, 4), 1, 2, new Rect(double.MaxValue, double.PositiveInfinity, 3, 8) }; + yield return new object[] { new Rect(double.MaxValue, double.MaxValue, 3, 4), 0, 0, new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(double.MaxValue, double.MaxValue, 3, 4), double.MaxValue, double.MaxValue, new Rect(double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(double.MinValue, double.MinValue, 3, 4), -1, -2, new Rect(double.MaxValue, double.PositiveInfinity, 3, 8) }; + yield return new object[] { new Rect(double.MinValue, double.MinValue, 3, 4), 0, 0, new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(double.MinValue, double.MinValue, 3, 4), double.MinValue, double.MinValue, new Rect(double.NaN, double.NaN, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, 3, 4), double.MaxValue, double.MaxValue, new Rect(double.MaxValue, double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(0, 0, 3, 4), double.MaxValue, double.MaxValue, new Rect(0, 0, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(-1, -2, 3, 4), double.MinValue, double.MinValue, new Rect(double.NegativeInfinity, double.NaN, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(0, 0, 3, 4), double.MinValue, double.MinValue, new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + + // Infinite. + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), 0, 0, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), 5, 6, new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), -5, -6, new Rect(double.NaN, double.NaN, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), double.PositiveInfinity, double.PositiveInfinity, new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(double.NegativeInfinity, double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity), double.NegativeInfinity, double.NegativeInfinity, new Rect(double.NaN, double.NaN, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), 0, 0, new Rect(0, 0, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), 5, 6, new Rect(5, 12, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), -5, -6, new Rect(double.NegativeInfinity ,double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), double.PositiveInfinity, double.PositiveInfinity, new Rect(double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), double.NegativeInfinity, double.NegativeInfinity, new Rect(double.NegativeInfinity,double.NegativeInfinity, double.PositiveInfinity, double.PositiveInfinity) }; + + // NaN. + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), 0, 0, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), 5, 6, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), -5, -6, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), double.PositiveInfinity, double.PositiveInfinity, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), double.NegativeInfinity, double.NegativeInfinity, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(double.NaN, double.NaN, double.NaN, double.NaN), double.NaN, double.NaN, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), 0, 0, new Rect(0, 0, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), 5, 6, new Rect(5, 12, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), -5, -6, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), double.PositiveInfinity, double.PositiveInfinity, new Rect(double.PositiveInfinity, double.PositiveInfinity, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), double.NegativeInfinity, double.NegativeInfinity, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), double.NaN, double.NaN, new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + + // Zero. + yield return new object[] { new Rect(0, 0, 0, 0), 2, 3, new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), 1, 1, new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), 0.5, 0.5, new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), 0, 0, new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), -1, -2, new Rect(0, 0, 0, 0) }; + + // Default. + yield return new object[] { new Rect(), 2, 3, new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(), 1, 1, new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(), 0.5, 0.5, new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(), 0, 0, new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(), -1, -2, new Rect(0, 0, 0, 0) }; + + // Empty. + yield return new object[] { Rect.Empty, 2, 3, Rect.Empty }; + yield return new object[] { Rect.Empty, 1, 1, Rect.Empty }; + yield return new object[] { Rect.Empty, 0.5, 0.5, Rect.Empty }; + yield return new object[] { Rect.Empty, 0, 0, Rect.Empty }; + yield return new object[] { Rect.Empty, -1, -2, Rect.Empty }; + } + + [Theory] + [MemberData(nameof(Scale_TestData))] + public void Scale_Invoke_ReturnsExpected(Rect rect, double scaleX, double scaleY, Rect expected) + { + rect.Scale(scaleX, scaleY); + Assert.Equal(expected, rect); + } + + public static IEnumerable ToString_TestData() + { + yield return new object[] { Rect.Empty, "Empty" }; + yield return new object[] { new Rect(), "0,0,0,0" }; + yield return new object[] { new Rect(0, 0, 0, 0), "0,0,0,0" }; + yield return new object[] { new Rect(1, 2, 3, 4), "1,2,3,4" }; + yield return new object[] { new Rect(1.1, 2.2, 3.3, 4.4), "1.1,2.2,3.3,4.4" }; + yield return new object[] { new Rect(-1, -2, 3, 4), "-1,-2,3,4" }; + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_Invoke_ReturnsExpected(Rect rect, string expected) + { + Assert.Equal(expected, rect.ToString()); + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_InvokeIFormatProviderInvariantCulture_ReturnsExpected(Rect rect, string expected) + { + Assert.Equal(expected, rect.ToString(CultureInfo.InvariantCulture)); + } + + public static IEnumerable ToString_IFormatProviderCustom_TestData() + { + yield return new object[] { Rect.Empty, "|", "Empty" }; + yield return new object[] { Rect.Empty, "|_", "Empty" }; + yield return new object[] { Rect.Empty, ",_", "Empty" }; + yield return new object[] { Rect.Empty, ",", "Empty" }; + yield return new object[] { Rect.Empty, ";", "Empty" }; + yield return new object[] { Rect.Empty, " ", "Empty" }; + + yield return new object[] { new Rect(), "|", "0,0,0,0" }; + yield return new object[] { new Rect(), "|_", "0,0,0,0" }; + yield return new object[] { new Rect(), ",_", "0;0;0;0" }; + yield return new object[] { new Rect(), ",", "0;0;0;0" }; + yield return new object[] { new Rect(), ";", "0,0,0,0" }; + yield return new object[] { new Rect(), " ", "0,0,0,0" }; + + yield return new object[] { new Rect(0, 0, 0, 0), "|", "0,0,0,0" }; + yield return new object[] { new Rect(0, 0, 0, 0), "|_", "0,0,0,0" }; + yield return new object[] { new Rect(0, 0, 0, 0), ",_", "0;0;0;0" }; + yield return new object[] { new Rect(0, 0, 0, 0), ",", "0;0;0;0" }; + yield return new object[] { new Rect(0, 0, 0, 0), ";", "0,0,0,0" }; + yield return new object[] { new Rect(0, 0, 0, 0), " ", "0,0,0,0" }; + + yield return new object[] { new Rect(1, 2, 3, 4), "|", "1,2,3,4" }; + yield return new object[] { new Rect(1, 2, 3, 4), "|_", "1,2,3,4" }; + yield return new object[] { new Rect(1, 2, 3, 4), ",_", "1;2;3;4" }; + yield return new object[] { new Rect(1, 2, 3, 4), ",", "1;2;3;4" }; + yield return new object[] { new Rect(1, 2, 3, 4), ";", "1,2,3,4" }; + yield return new object[] { new Rect(1, 2, 3, 4), " ", "1,2,3,4" }; + + yield return new object[] { new Rect(1.1, 2.2, 3.3, 4.4), "|", "1|1,2|2,3|3,4|4" }; + yield return new object[] { new Rect(1.1, 2.2, 3.3, 4.4), "|_", "1|_1,2|_2,3|_3,4|_4" }; + yield return new object[] { new Rect(1.1, 2.2, 3.3, 4.4), ",_", "1,_1;2,_2;3,_3;4,_4" }; + yield return new object[] { new Rect(1.1, 2.2, 3.3, 4.4), ",", "1,1;2,2;3,3;4,4" }; + yield return new object[] { new Rect(1.1, 2.2, 3.3, 4.4), ";", "1;1,2;2,3;3,4;4" }; + yield return new object[] { new Rect(1.1, 2.2, 3.3, 4.4), " ", "1 1,2 2,3 3,4 4" }; + + yield return new object[] { new Rect(-1, -2, 3, 4), "|", "-1,-2,3,4" }; + yield return new object[] { new Rect(-1, -2, 3, 4), "|_", "-1,-2,3,4" }; + yield return new object[] { new Rect(-1, -2, 3, 4), ",_", "-1;-2;3;4" }; + yield return new object[] { new Rect(-1, -2, 3, 4), ",", "-1;-2;3;4" }; + yield return new object[] { new Rect(-1, -2, 3, 4), ";", "-1,-2,3,4" }; + yield return new object[] { new Rect(-1, -2, 3, 4), " ", "-1,-2,3,4" }; + } + + [Theory] + [MemberData(nameof(ToString_IFormatProviderCustom_TestData))] + public void ToString_InvokeIFormatProviderCustom_ReturnsExpected(Rect rect, string numberDecimalSeparator, string expected) + { + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + Assert.Equal(expected, rect.ToString(formatInfo)); + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_InvokeIFormattableInvariantCulture_ReturnsExpected(Rect rect, string expected) + { + IFormattable formattable = rect; + Assert.Equal(expected, formattable.ToString(null, null)); + Assert.Equal(expected, formattable.ToString(null, CultureInfo.InvariantCulture)); + } + + [Theory] + [InlineData("|", "1|23,2|35,3|46,4|57")] + [InlineData("|_", "1|_23,2|_35,3|_46,4|_57")] + [InlineData(",_", "1,_23;2,_35;3,_46;4,_57")] + [InlineData(",", "1,23;2,35;3,46;4,57")] + [InlineData(";", "1;23,2;35,3;46,4;57")] + [InlineData(" ", "1 23,2 35,3 46,4 57")] + public void ToString_InvokeIFormattableCustomFormat_ReturnsExpected(string numberDecimalSeparator, string expected) + { + var rect = new Rect(1.23456, 2.34567, 3.45678, 4.56789); + IFormattable formattable = rect; + + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + Assert.Equal(expected, formattable.ToString("F2", formatInfo)); + } + + public static IEnumerable Transform_TestData() + { + // Identity. + foreach (Matrix matrix in MatrixTests.IdentityMatrices()) + { + yield return new object[] { new Rect(1, 2, 3, 4), matrix, new Rect(1, 2, 3, 4) }; + } + + // Scale. + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(2, 0, 0, 1, 0, 0), new Rect(2, 2, 6, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(1, 0, 0, 2, 0, 0), new Rect(1, 4, 3, 8) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(0, 0, 0, 3, 0, 0), new Rect(0, 6, 0, 12) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(2, 0, 0, 0, 0, 0), new Rect(2, 0, 6, 0) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(2, 0, 0, 3, 0, 0), new Rect(2, 6, 6, 12) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(-2, 0, 0, -3, 0, 0), new Rect(-8, -18, 6, 12) }; + + // Skew. + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(1, 2, 0, 1, 0, 0), new Rect(1, 4, 3, 10) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(1, 0, 3, 1, 0, 0), new Rect(7, 2, 15, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(1, 2, 3, 1, 0, 0), new Rect(7, 4, 15, 10) }; + + // Translate. + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(1, 0, 0, 1, 2, 0), new Rect(3, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(1, 0, 0, 1, 0, 3), new Rect(1, 5, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(1, 0, 0, 1, 2, 3), new Rect(3, 5, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(1, 0, 0, 1, -2, 0), new Rect(-1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(1, 0, 0, 1, 0, -3), new Rect(1, -1, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(1, 0, 0, 1, -2, -3), new Rect(-1, -1, 3, 4) }; + + // Translate + Scale. + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(2, 0, 0, 3, 4, 5), new Rect(6, 11, 6, 12) }; + + // Translate + Skew. + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(1, 2, 3, 1, 4, 5), new Rect(11, 9, 15, 10) }; + + // Skew + Scale. + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(2, 3, 4, 5, 0, 0), new Rect(10, 13, 22, 29) }; + + // Complex. + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(2, 3, 4, 5, 6, 7), new Rect(16, 20, 22, 29) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Matrix(0, 0, 0, 0, 0, 0), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(1.1, 2.2, 2.3, 3.3), new Matrix(2, 3, 4, 5, 6, 7), new Rect(17, 21.3, 17.8, 23.4) }; + + // Other cases. + yield return new object[] { new Rect(-1, -2, 1, 2), Matrix.Identity, new Rect(-1, -2, 1, 2) }; + yield return new object[] { new Rect(-1, -2, 1, 2), new Matrix(), new Rect(-1, -2, 1, 2) }; + yield return new object[] { new Rect(-1, -2, 1, 2), new Matrix(1, 0, 0, 1, 0, 0), new Rect(-1, -2, 1, 2) }; + yield return new object[] { new Rect(-1, -2, 1, 2), new Matrix(2, 3, 4, 5, 6, 7), new Rect(-4, -6, 10, 13) }; + + // Zero. + yield return new object[] { new Rect(0, 0, 0, 0), Matrix.Identity, new Rect() }; + yield return new object[] { new Rect(0, 0, 0, 0), new Matrix(), new Rect() }; + yield return new object[] { new Rect(0, 0, 0, 0), new Matrix(1, 0, 0, 1, 0, 0), new Rect() }; + yield return new object[] { new Rect(0, 0, 0, 0), new Matrix(2, 3, 4, 5, 6, 7), new Rect(6, 7, 0, 0) }; + + // Default. + yield return new object[] { new Rect(), Matrix.Identity, new Rect() }; + yield return new object[] { new Rect(), new Matrix(), new Rect() }; + yield return new object[] { new Rect(), new Matrix(1, 0, 0, 1, 0, 0), new Rect() }; + yield return new object[] { new Rect(), new Matrix(2, 3, 4, 5, 6, 7), new Rect(6, 7, 0, 0) }; + + // Empty. + yield return new object[] { Rect.Empty, Matrix.Identity, Rect.Empty }; + yield return new object[] { Rect.Empty, new Matrix(), Rect.Empty }; + yield return new object[] { Rect.Empty, new Matrix(1, 0, 0, 1, 0, 0), Rect.Empty }; + yield return new object[] { Rect.Empty, new Matrix(2, 3, 4, 5, 6, 7), Rect.Empty }; + } + + [Theory] + [MemberData(nameof(Transform_TestData))] + public void Transform_Invoke_ReturnsExpected(Rect rect, Matrix matrix, Rect expected) + { + Rect copy = rect; + + Helpers.AssertEqualRounded(expected, Rect.Transform(rect, matrix), precision: 5); + Assert.Equal(copy, rect); + + rect.Transform(matrix); + Helpers.AssertEqualRounded(expected, rect, precision: 5); + } + + public static IEnumerable Union_Point_TestData() + { + // Normal. + yield return new object[] { new Rect(1, 2, 3, 4), new Point(1, 2), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(1, 6), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(4, 2), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(4, 6), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(2, 3), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(2, 3), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(-1, -2), new Rect(-1, -2, 5, 8) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(0, 0), new Rect(0, 0, 4, 6) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(5, 7), new Rect(1, 2, 4, 5) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(), new Rect(0, 0, 4, 6) }; + + // Infinite width. + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, 4), new Point(1, 2), new Rect(1, 2, double.PositiveInfinity, 4) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, 4), new Point(double.MaxValue, 2), new Rect(1, 2, double.PositiveInfinity, 4) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, 4), new Point(double.PositiveInfinity, 2), new Rect(1, 2, double.PositiveInfinity, 4) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, 4), new Point(double.NaN, 2), new Rect(double.NaN, 2, double.PositiveInfinity, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(double.PositiveInfinity, 2), new Rect(1, 2, double.NaN, 4) }; + + // Infinite height. + yield return new object[] { new Rect(1, 2, 3, double.PositiveInfinity), new Point(1, 2), new Rect(1, 2, 3, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, 3, double.PositiveInfinity), new Point(1, double.MaxValue), new Rect(1, 2, 3, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, 3, double.PositiveInfinity), new Point(1, double.PositiveInfinity), new Rect(1, 2, 3, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, 3, double.PositiveInfinity), new Point(1, double.NaN), new Rect(1, double.NaN, 3, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(1, double.PositiveInfinity), new Rect(1, 2, 3, double.NaN) }; + + // Infinite width & height. + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Point(1, 2), new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Point(double.MaxValue, double.MaxValue), new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Point(double.PositiveInfinity, double.PositiveInfinity), new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Point(double.NaN, double.NaN), new Rect(double.NaN, double.NaN, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(double.PositiveInfinity, double.PositiveInfinity), new Rect(1, 2, double.NaN, double.NaN) }; + + // NaN width. + yield return new object[] { new Rect(1, 2, double.NaN, 4), new Point(1, 2), new Rect(1, 2, double.NaN, 4) }; + yield return new object[] { new Rect(1, 2, double.NaN, 4), new Point(double.MaxValue, 2), new Rect(1, 2, double.NaN, 4) }; + yield return new object[] { new Rect(1, 2, double.NaN, 4), new Point(double.PositiveInfinity, 2), new Rect(1, 2, double.NaN, 4) }; + yield return new object[] { new Rect(1, 2, double.NaN, 4), new Point(double.NaN, 2), new Rect(double.NaN, 2, double.NaN, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(double.NaN, 2), new Rect(double.NaN, 2, double.NaN, 4) }; + + // NaN height. + yield return new object[] { new Rect(1, 2, 3, double.NaN), new Point(1, 2), new Rect(1, 2, 3, double.NaN) }; + yield return new object[] { new Rect(1, 2, 3, double.NaN), new Point(1, double.MaxValue), new Rect(1, 2, 3, double.NaN) }; + yield return new object[] { new Rect(1, 2, 3, double.NaN), new Point(1, double.PositiveInfinity), new Rect(1, 2, 3, double.NaN) }; + yield return new object[] { new Rect(1, 2, 3, double.NaN), new Point(1, double.NaN), new Rect(1, double.NaN, 3, double.NaN) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(1, double.NaN), new Rect(1, double.NaN, 3, double.NaN) }; + + // NaN width & height. + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), new Point(1, 2), new Rect(1, 2, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), new Point(double.MaxValue, double.MaxValue), new Rect(1, 2, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), new Point(double.PositiveInfinity, double.PositiveInfinity), new Rect(1, 2, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), new Point(double.NaN, double.NaN), new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Point(double.NaN, double.NaN), new Rect(double.NaN, double.NaN, double.NaN, double.NaN) }; + + // Zero. + yield return new object[] { new Rect(0, 0, 0, 0), new Point(1, 2), new Rect(0, 0, 1, 2) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Point(-1, -2), new Rect(-1, -2, 1, 2) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Point(0, 0), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Point(), new Rect(0, 0, 0, 0) }; + + // Default. + yield return new object[] { new Rect(), new Point(1, 2), new Rect(0, 0, 1, 2) }; + yield return new object[] { new Rect(), new Point(-1, -2), new Rect(-1, -2, 1, 2) }; + yield return new object[] { new Rect(), new Point(0, 0), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(), new Point(), new Rect(0, 0, 0, 0) }; + + // Empty. + yield return new object[] {Rect.Empty, new Point(1, 2), new Rect(1, 2, 0, 0) }; + yield return new object[] {Rect.Empty, new Point(-1, -2), new Rect(-1, -2, 0, 0) }; + yield return new object[] {Rect.Empty, new Point(0, 0), new Rect(0, 0, 0, 0) }; + yield return new object[] {Rect.Empty, new Point(), new Rect(0, 0, 0, 0) }; + } + + [Theory] + [MemberData(nameof(Union_Point_TestData))] + public void Union_InvokePoint_ReturnsExpected(Rect rect, Point point, Rect expected) + { + Rect copy = rect; + + Assert.Equal(expected, Rect.Union(rect, point)); + Assert.Equal(copy, rect); + + rect.Union(point); + Assert.Equal(expected, rect); + } + + public static IEnumerable Union_Rect_TestData() + { + // Normal. + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 4), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 0), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 0, 0), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 3, 1, 2), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 3, 2, 3), new Rect(1, 2, 3, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(2, 3, 3, 4), new Rect(1, 2, 4, 5) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(-1, -2, 1, 2), new Rect(-1, -2, 5, 8) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(-1, -2, 2, 4), new Rect(-1, -2, 5, 8) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(-1, -2, 7, 9), new Rect(-1, -2, 7, 9) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(0, 0, 0, 0), new Rect(0, 0, 4, 6) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(), new Rect(0, 0, 4, 6) }; + yield return new object[] { new Rect(1, 2, 3, 4), Rect.Empty, new Rect(1, 2, 3, 4) }; + + // Infinite width. + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, 4), new Rect(1, 2, 3, 4), new Rect(1, 2, double.PositiveInfinity, 4) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, 4), new Rect(1, 2, double.MaxValue, 4), new Rect(1, 2, double.PositiveInfinity, 4) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, 4), new Rect(1, 2, double.PositiveInfinity, 4), new Rect(1, 2, double.PositiveInfinity, 4) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, 4), new Rect(1, 2, double.NaN, 4), new Rect(1, 2, double.PositiveInfinity, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, double.PositiveInfinity, 4), new Rect(1, 2, double.PositiveInfinity, 4) }; + + // Infinite height. + yield return new object[] { new Rect(1, 2, 3, double.PositiveInfinity), new Rect(1, 2, 3, 4), new Rect(1, 2, 3, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, 3, double.PositiveInfinity), new Rect(1, 2, 3, double.MaxValue), new Rect(1, 2, 3, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, 3, double.PositiveInfinity), new Rect(1, 2, 3, double.PositiveInfinity), new Rect(1, 2, 3, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, 3, double.PositiveInfinity), new Rect(1, 2, 3, double.NaN), new Rect(1, 2, 3, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, double.PositiveInfinity), new Rect(1, 2, 3, double.PositiveInfinity) }; + + // Infinite width & height. + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Rect(1, 2, 3, 4), new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Rect(1, 2, double.MaxValue, double.MaxValue), new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Rect(1, 2, double.NaN, double.NaN), new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity) }; + + // NaN width. + yield return new object[] { new Rect(1, 2, double.NaN, 4), new Rect(1, 2, 3, 4), new Rect(1, 2, double.NaN, 4) }; + yield return new object[] { new Rect(1, 2, double.NaN, 4), new Rect(1, 2, double.MaxValue, 4), new Rect(1, 2, double.NaN, 4) }; + yield return new object[] { new Rect(1, 2, double.NaN, 4), new Rect(1, 2, double.PositiveInfinity, 4), new Rect(1, 2, double.PositiveInfinity, 4) }; + yield return new object[] { new Rect(1, 2, double.NaN, 4), new Rect(1, 2, double.NaN, 4), new Rect(1, 2, double.NaN, 4) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, double.NaN, 4), new Rect(1, 2, double.NaN, 4) }; + + // NaN height. + yield return new object[] { new Rect(1, 2, 3, double.NaN), new Rect(1, 2, 3, 4), new Rect(1, 2, 3, double.NaN) }; + yield return new object[] { new Rect(1, 2, 3, double.NaN), new Rect(1, 2, 3, double.MaxValue), new Rect(1, 2, 3, double.NaN) }; + yield return new object[] { new Rect(1, 2, 3, double.NaN), new Rect(1, 2, 3, double.PositiveInfinity), new Rect(1, 2, 3, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, 3, double.NaN), new Rect(1, 2, 3, double.NaN), new Rect(1, 2, 3, double.NaN) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, 3, double.NaN), new Rect(1, 2, 3, double.NaN) }; + + // NaN width & height. + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), new Rect(1, 2, 3, 4), new Rect(1, 2, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), new Rect(1, 2, double.MaxValue, double.MaxValue), new Rect(1, 2, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity), new Rect(1, 2, double.PositiveInfinity, double.PositiveInfinity) }; + yield return new object[] { new Rect(1, 2, double.NaN, double.NaN), new Rect(1, 2, double.NaN, double.NaN), new Rect(1, 2, double.NaN, double.NaN) }; + yield return new object[] { new Rect(1, 2, 3, 4), new Rect(1, 2, double.NaN, double.NaN), new Rect(1, 2, double.NaN, double.NaN) }; + + // Zero. + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 2, 3, 4), new Rect(0, 0, 4, 6) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 2, 0, 4), new Rect(0, 0, 1, 6) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 2, 3, 0), new Rect(0, 0, 4, 2) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(1, 0, 0, 0), new Rect(0, 0, 1, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 1, 0, 0), new Rect(0, 0, 0, 1) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 1, 0), new Rect(0, 0, 1, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 1), new Rect(0, 0, 0, 1) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), new Rect(), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(0, 0, 0, 0), Rect.Empty, new Rect(0, 0, 0, 0) }; + + // Default. + yield return new object[] { new Rect(), new Rect(1, 2, 3, 4), new Rect(0, 0, 4, 6) }; + yield return new object[] { new Rect(), new Rect(1, 2, 0, 4), new Rect(0, 0, 1, 6) }; + yield return new object[] { new Rect(), new Rect(1, 2, 3, 0), new Rect(0, 0, 4, 2) }; + yield return new object[] { new Rect(), new Rect(1, 0, 0, 0), new Rect(0, 0, 1, 0) }; + yield return new object[] { new Rect(), new Rect(0, 1, 0, 0), new Rect(0, 0, 0, 1) }; + yield return new object[] { new Rect(), new Rect(0, 0, 1, 0), new Rect(0, 0, 1, 0) }; + yield return new object[] { new Rect(), new Rect(0, 0, 0, 1), new Rect(0, 0, 0, 1) }; + yield return new object[] { new Rect(), new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(), new Rect(), new Rect(0, 0, 0, 0) }; + yield return new object[] { new Rect(), Rect.Empty, new Rect(0, 0, 0, 0) }; + + // Empty. + yield return new object[] { Rect.Empty, new Rect(1, 2, 3, 4), new Rect(1, 2, 3, 4) }; + yield return new object[] { Rect.Empty, new Rect(1, 2, 0, 4), new Rect(1, 2, 0, 4) }; + yield return new object[] { Rect.Empty, new Rect(1, 2, 3, 0), new Rect(1, 2, 3, 0) }; + yield return new object[] { Rect.Empty, new Rect(1, 0, 0, 0), new Rect(1, 0, 0, 0) }; + yield return new object[] { Rect.Empty, new Rect(0, 1, 0, 0), new Rect(0, 1, 0, 0) }; + yield return new object[] { Rect.Empty, new Rect(0, 0, 1, 0), new Rect(0, 0, 1, 0) }; + yield return new object[] { Rect.Empty, new Rect(0, 0, 0, 1), new Rect(0, 0, 0, 1) }; + yield return new object[] { Rect.Empty, new Rect(0, 0, 0, 0), new Rect(0, 0, 0, 0) }; + yield return new object[] { Rect.Empty, new Rect(), new Rect(0, 0, 0, 0) }; + yield return new object[] { Rect.Empty, Rect.Empty, Rect.Empty }; + } + + [Theory] + [MemberData(nameof(Union_Rect_TestData))] + public void Union_InvokeRect_ReturnsExpected(Rect rect1, Rect rect2, Rect expected) + { + Rect copy1 = rect1; + Rect copy2 = rect2; + + Assert.Equal(expected, Rect.Union(rect1, rect2)); + Assert.Equal(copy1, rect1); + Assert.Equal(copy2, rect2); + + Assert.Equal(expected, Rect.Union(rect2, rect1)); + Assert.Equal(copy1, rect1); + Assert.Equal(copy2, rect2); + + rect1.Union(rect2); + Assert.Equal(expected, rect1); + Assert.Equal(copy2, rect2); + } + + [Fact] + public void TypeConverter_Get_ReturnsExpected() + { + Assert.IsType(TypeDescriptor.GetConverter(typeof(Rect))); + } + + [Fact] + public void ValueSerializer_Get_ReturnsExpected() + { + Assert.IsType(ValueSerializer.GetSerializerFor(typeof(Rect))); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/SizeConverterTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/SizeConverterTests.cs new file mode 100644 index 00000000000..0afbb79c95e --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/SizeConverterTests.cs @@ -0,0 +1,198 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.ComponentModel.Design.Serialization; +using System.Globalization; + +namespace System.Windows.Tests; + +public class SizeConverterTests +{ + [Theory] + [InlineData(null, false)] + [InlineData(typeof(object), false)] + [InlineData(typeof(string), true)] + [InlineData(typeof(InstanceDescriptor), false)] + [InlineData(typeof(Size), false)] + public void CanConvertTo_Invoke_ReturnsExpected(Type? destinationType, bool expected) + { + var converter = new SizeConverter(); + Assert.Equal(expected, converter.CanConvertTo(null, destinationType)); + Assert.Equal(expected, converter.CanConvertTo(new CustomTypeDescriptorContext(), destinationType)); + } + + [Theory] + [MemberData(nameof(SizeTests.ToString_TestData), MemberType = typeof(SizeTests))] + public void ConvertTo_InvokeSizeToString_ReturnsExpected(Size matrix, string expected) + { + var converter = new SizeConverter(); + Assert.Equal(expected, converter.ConvertTo(matrix, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), null, matrix, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, matrix, typeof(string))); + } + + [Theory] + [MemberData(nameof(SizeTests.ToString_IFormatProviderCustom_TestData), MemberType = typeof(SizeTests))] + public void ConvertTo_InvokeSizeToStringCustomCulture_ReturnsExpected(Size matrix, string numberDecimalSeparator, string expected) + { + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + var converter = new SizeConverter(); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), culture, matrix, typeof(string))); + } + + [Theory] + [InlineData(null, "")] + [InlineData("", "")] + [InlineData("value", "value")] + [InlineData(1, "1")] + public void ConvertTo_InvokeNotSizeToString_ReturnsExpected(object? value, string expected) + { + var converter = new SizeConverter(); + Assert.Equal(expected, converter.ConvertTo(value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + } + + public static IEnumerable ConvertTo_CantConvert_TestData() + { + yield return new object?[] { null, typeof(object) }; + yield return new object?[] { string.Empty, typeof(object) }; + yield return new object?[] { "value", typeof(object) }; + yield return new object?[] { new object(), typeof(object) }; + yield return new object?[] { new Size(), typeof(object) }; + + yield return new object?[] { null, typeof(Size) }; + yield return new object?[] { string.Empty, typeof(Size) }; + yield return new object?[] { "value", typeof(Size) }; + yield return new object?[] { new object(), typeof(Size) }; + yield return new object?[] { new Size(), typeof(Size) }; + } + + [Theory] + [MemberData(nameof(ConvertTo_CantConvert_TestData))] + public void ConvertTo_CantConvert_ThrowsNotSupportedException(object value, Type destinationType) + { + var converter = new SizeConverter(); + Assert.Throws(() => converter.ConvertTo(value, destinationType)); + Assert.Throws(() => converter.ConvertTo(null, null, value, destinationType)); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, destinationType)); + } + + public static IEnumerable ConvertTo_NullDestinationType_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + yield return new object?[] { new object() }; + yield return new object?[] { new Size() }; + } + + [Theory] + [MemberData(nameof(ConvertTo_NullDestinationType_TestData))] + public void ConvertTo_NullDestinationType_ThrowsArgumentNullException(object value) + { + var converter = new SizeConverter(); + Assert.Throws("destinationType", () => converter.ConvertTo(value, null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(null, null, new Size(), null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, new Size(), null!)); + } + + [Theory] + [InlineData(null, false)] + [InlineData(typeof(object), false)] + [InlineData(typeof(string), true)] + [InlineData(typeof(InstanceDescriptor), true)] + [InlineData(typeof(Size), false)] + public void CanConvertFrom_Invoke_ReturnsExpected(Type? sourceType, bool expected) + { + var converter = new SizeConverter(); + Assert.Equal(expected, converter.CanConvertFrom(sourceType!)); + Assert.Equal(expected, converter.CanConvertFrom(null, sourceType)); + Assert.Equal(expected, converter.CanConvertFrom(new CustomTypeDescriptorContext(), sourceType)); + } + + [Theory] + [MemberData(nameof(SizeTests.Parse_TestData), MemberType = typeof(SizeTests))] + public void ConvertFrom_InvokeStringValue_ReturnsExpected(object value, Size expected) + { + var converter = new SizeConverter(); + Assert.Equal(expected, converter.ConvertFrom(value)); + Assert.Equal(expected, converter.ConvertFrom(null, null, value)); + Assert.Equal(expected, converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Fact] + public void ConvertFrom_NullValue_ThrowsNotSupportedException() + { + var converter = new SizeConverter(); + Assert.Throws(() => converter.ConvertFrom(null!)); + Assert.Throws(() => converter.ConvertFrom(null, null, null)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, null)); + } + + [Theory] + [MemberData(nameof(SizeTests.Parse_InvalidSource_TestData), MemberType = typeof(SizeTests))] + public void ConvertFrom_InvalidStringValue_ThrowsInvalidOperationException(object value) + { + var converter = new SizeConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Theory] + [MemberData(nameof(SizeTests.Parse_NotDouble_TestData), MemberType = typeof(SizeTests))] + public void ConvertFrom_NotDoubleStringValue_ThrowsFormatException(object value) + { + var converter = new SizeConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Theory] + [MemberData(nameof(SizeTests.Parse_Negative_TestData), MemberType = typeof(SizeTests))] + public void ConvertFrom_Negative_ThrowsArgumentException(object value) + { + var converter = new SizeConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + public static IEnumerable ConvertFrom_CantConvert_TestData() + { + yield return new object[] { new object() }; + yield return new object[] { new Size() }; + } + + [Theory] + [MemberData(nameof(ConvertFrom_CantConvert_TestData))] + public void ConvertFrom_CantConvert_ThrowsNotSupportedException(object value) + { + var converter = new SizeConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + private class CustomTypeDescriptorContext : ITypeDescriptorContext + { + public IContainer Container => throw new NotImplementedException(); + + public object Instance => throw new NotImplementedException(); + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/SizeTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/SizeTests.cs new file mode 100644 index 00000000000..5ae7ca1cf1a --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/SizeTests.cs @@ -0,0 +1,576 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Globalization; +using System.Windows.Converters; +using System.Windows.Markup; + +namespace System.Windows.Tests; + +public class SizeTests +{ + [Fact] + public void Ctor_Default() + { + var size = new Size(); + Assert.Equal(0, size.Width); + Assert.Equal(0, size.Height); + Assert.False(size.IsEmpty); + } + + [Theory] + [InlineData(double.NegativeZero, double.NegativeZero)] + [InlineData(0, 0)] + [InlineData(1, 0)] + [InlineData(0, 2)] + [InlineData(0.1, 0.2)] + [InlineData(1, 2)] + [InlineData(double.MaxValue, double.MaxValue)] + [InlineData(double.PositiveInfinity, double.PositiveInfinity)] + [InlineData(double.NaN, double.NaN)] + [InlineData(double.NaN, 1)] + [InlineData(1, double.NaN)] + public void Ctor_Double_Double(double width, double height) + { + var size = new Size(width, height); + Assert.Equal(width, size.Width); + Assert.Equal(height, size.Height); + Assert.False(size.IsEmpty); + } + + [Theory] + [InlineData(double.NegativeInfinity)] + [InlineData(double.MinValue)] + [InlineData(-1)] + public void Ctor_NegativeWidth_ThrowsArgumentException(double width) + { + // TODO: should have paramName + Assert.Throws(() => new Size(width, 0)); + } + + [Theory] + [InlineData(double.NegativeInfinity)] + [InlineData(double.MinValue)] + [InlineData(-1)] + public void Ctor_NegativeHeight_ThrowsArgumentException(double height) + { + // TODO: should have paramName + Assert.Throws(() => new Size(0, height)); + } + + [Fact] + public void Empty_Get_ReturnsExpected() + { + Size size = Size.Empty; + Assert.Equal(double.NegativeInfinity, size.Width); + Assert.Equal(double.NegativeInfinity, size.Height); + Assert.True(size.IsEmpty); + } + + [Theory] + [InlineData(double.NegativeZero)] + [InlineData(0)] + [InlineData(0.2)] + [InlineData(1)] + [InlineData(double.MaxValue)] + [InlineData(double.PositiveInfinity)] + [InlineData(double.NaN)] + public void Width_Set_GetReturnsExpected(double value) + { + var size = new Size(); + + // Set. + size.Width = value; + Assert.Equal(value, size.Width); + + // Set same. + size.Width = value; + Assert.Equal(value, size.Width); + } + + [Theory] + [InlineData(double.NegativeInfinity)] + [InlineData(double.MinValue)] + [InlineData(-1)] + public void Width_SetNegative_ThrowsArgumentException(double value) + { + var size = new Size(); + // TODO: should have paramName + Assert.Throws(() => size.Width = value); + } + + [Theory] + [InlineData(-1)] + [InlineData(1)] + public void Width_SetEmpty_ThrowsInvalidOperationException(double value) + { + var size = Size.Empty; + Assert.Throws(() => size.Width = value); + } + + [Theory] + [InlineData(double.NegativeZero)] + [InlineData(0)] + [InlineData(0.2)] + [InlineData(1)] + [InlineData(double.MaxValue)] + [InlineData(double.PositiveInfinity)] + [InlineData(double.NaN)] + public void Height_Set_GetReturnsExpected(double value) + { + var size = new Size(); + + // Set. + size.Height = value; + Assert.Equal(value, size.Height); + + // Set same. + size.Height = value; + Assert.Equal(value, size.Height); + } + + [Theory] + [InlineData(double.NegativeInfinity)] + [InlineData(double.MinValue)] + [InlineData(-1)] + public void Height_SetNegative_ThrowsArgumentException(double value) + { + var size = new Size(); + // TODO: should have paramName + Assert.Throws(() => size.Height = value); + } + + [Theory] + [InlineData(-1)] + [InlineData(1)] + public void Height_SetEmpty_ThrowsInvalidOperationException(double value) + { + var size = Size.Empty; + Assert.Throws(() => size.Height = value); + } + + public static IEnumerable Equals_TestData() + { + // Normal size. + yield return new object?[] { new Size(1, 2), new Size(1, 2), true, true }; + yield return new object?[] { new Size(1, 2), new Size(2, 2), false, false }; + yield return new object?[] { new Size(1, 2), new Size(1, 3), false, false }; + yield return new object?[] { new Size(1, 2), new Size(double.NaN, 2), false, false }; + yield return new object?[] { new Size(1, 2), new Size(1, double.NaN), false, false }; + yield return new object?[] { new Size(1, 2), new Size(double.NaN, double.NaN), false, false }; + yield return new object?[] { new Size(1, 2), new Size(0, 0), false, false }; + yield return new object?[] { new Size(1, 2), new Size(), false, false }; + yield return new object?[] { new Size(1, 2), Size.Empty, false, false }; + + // NaN width. + yield return new object[] { new Size(double.NaN, 2), new Size(double.NaN, 2), true, true }; + yield return new object[] { new Size(double.NaN, 2), new Size(2, 2), false, false }; + yield return new object[] { new Size(double.NaN, 2), new Size(double.NaN, double.NaN), false, false }; + + // NaN height. + yield return new object[] { new Size(1, double.NaN), new Size(1, double.NaN), true, true }; + yield return new object[] { new Size(1, double.NaN), new Size(1, 2), false, false }; + yield return new object[] { new Size(1, double.NaN), new Size(double.NaN, double.NaN), false, false }; + + // NaN width & height. + yield return new object[] { new Size(double.NaN, double.NaN), new Size(double.NaN, double.NaN), true, true }; + yield return new object[] { new Size(double.NaN, double.NaN), new Size(1, 2), false, false }; + yield return new object[] { new Size(double.NaN, double.NaN), new Size(double.NaN, 2), false, false }; + yield return new object[] { new Size(double.NaN, double.NaN), new Size(1, double.NaN), false, false }; + + // Zero. + yield return new object?[] { new Size(0, 0), new Size(), true, true }; + yield return new object?[] { new Size(0, 0), new Size(0, 0), true, true }; + yield return new object?[] { new Size(0, 0), new Size(1, 0), false, false }; + yield return new object?[] { new Size(0, 0), new Size(0, 1), false, false }; + yield return new object?[] { new Size(0, 0), Size.Empty, false, true }; + + // Default. + yield return new object?[] { new Size(), new Size(), true, true }; + yield return new object?[] { new Size(), new Size(0, 0), true, true }; + yield return new object?[] { new Size(), new Size(1, 0), false, false }; + yield return new object?[] { new Size(), new Size(0, 1), false, false }; + yield return new object?[] { new Size(), Size.Empty, false, true }; + + // Empty. + yield return new object?[] { Size.Empty, Size.Empty, true, true }; + yield return new object?[] { Size.Empty, new Size(1, 2), false, false }; + yield return new object?[] { Size.Empty, new Size(), false, true }; + yield return new object?[] { Size.Empty, new Size(0, 0), false, true }; + + // Other. + yield return new object?[] { Size.Empty, new object(), false, false }; + yield return new object?[] { Size.Empty, null, false, false }; + yield return new object?[] { new Size(), new object(), false, false }; + yield return new object?[] { new Size(), null, false, false }; + yield return new object?[] { new Size(0, 0), new object(), false, false }; + yield return new object?[] { new Size(0, 0), null, false, false }; + yield return new object?[] { new Size(1, 2), new object(), false, false }; + yield return new object?[] { new Size(1, 2), null, false, false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void Equals_Object_ReturnsExpected(Size size, object o, bool expected, bool expectedHashCode) + { + Assert.Equal(expected, size.Equals(o)); + if (o is Size value) + { + Assert.Equal(expected, size.Equals(value)); + Assert.Equal(expected, value.Equals(size)); + Assert.Equal(expected, Size.Equals(size, value)); + Assert.Equal(expected, Size.Equals(value, size)); + Assert.Equal(expectedHashCode, size.GetHashCode().Equals(value.GetHashCode())); + } + } + + public static IEnumerable EqualityOperator_TestData() + { + // Normal size. + yield return new object[] { new Size(1, 2), new Size(1, 2), true }; + yield return new object[] { new Size(1, 2), new Size(2, 2), false }; + yield return new object[] { new Size(1, 2), new Size(1, 3), false }; + yield return new object[] { new Size(1, 2), new Size(double.NaN, 2), false }; + yield return new object[] { new Size(1, 2), new Size(1, double.NaN), false }; + yield return new object[] { new Size(1, 2), new Size(double.NaN, double.NaN), false }; + yield return new object[] { new Size(1, 2), new Size(0, 0), false }; + yield return new object[] { new Size(1, 2), new Size(), false }; + yield return new object[] { new Size(1, 2), Size.Empty, false }; + + // NaN width. + yield return new object[] { new Size(double.NaN, 2), new Size(double.NaN, 2), false }; + yield return new object[] { new Size(double.NaN, 2), new Size(2, 2), false }; + yield return new object[] { new Size(double.NaN, 2), new Size(double.NaN, double.NaN), false }; + + // NaN height. + yield return new object[] { new Size(1, double.NaN), new Size(1, double.NaN), false }; + yield return new object[] { new Size(1, double.NaN), new Size(1, 2), false }; + yield return new object[] { new Size(1, double.NaN), new Size(double.NaN, double.NaN), false }; + + // NaN width & height. + yield return new object[] { new Size(double.NaN, double.NaN), new Size(double.NaN, double.NaN), false }; + yield return new object[] { new Size(double.NaN, double.NaN), new Size(1, 2), false }; + yield return new object[] { new Size(double.NaN, double.NaN), new Size(double.NaN, 2), false }; + yield return new object[] { new Size(double.NaN, double.NaN), new Size(1, double.NaN), false }; + + // Zero. + yield return new object[] { new Size(0, 0), new Size(), true }; + yield return new object[] { new Size(0, 0), new Size(0, 0), true }; + yield return new object[] { new Size(0, 0), new Size(1, 0), false }; + yield return new object[] { new Size(0, 0), new Size(0, 1), false }; + yield return new object[] { new Size(0, 0), Size.Empty, false }; + + // Default. + yield return new object[] { new Size(), new Size(), true }; + yield return new object[] { new Size(), new Size(0, 0), true }; + yield return new object[] { new Size(), new Size(1, 0), false }; + yield return new object[] { new Size(), new Size(0, 1), false }; + yield return new object[] { new Size(), Size.Empty, false }; + + // Empty. + yield return new object[] { Size.Empty, Size.Empty, true }; + yield return new object[] { Size.Empty, new Size(1, 2), false }; + yield return new object[] { Size.Empty, new Size(), false }; + yield return new object[] { Size.Empty, new Size(0, 0), false }; + } + + [Theory] + [MemberData(nameof(EqualityOperator_TestData))] + public void EqualityOperator_Invoke_ReturnsExpected(Size size, Size value, bool expected) + { + Assert.Equal(expected, size == value); + Assert.Equal(expected, value == size); + Assert.Equal(!expected, size != value); + Assert.Equal(!expected, value != size); + } + + [Fact] + public void GetHashCode_InvokeDefault_ReturnsEqual() + { + var size = new Size(); + Assert.Equal(0, size.GetHashCode()); + Assert.Equal(size.GetHashCode(), size.GetHashCode()); + } + + [Fact] + public void GetHashCode_InvokeEmpty_ReturnsEqual() + { + Size size = Size.Empty; + Assert.Equal(0, size.GetHashCode()); + Assert.Equal(size.GetHashCode(), size.GetHashCode()); + } + + [Fact] + public void GetHashCode_InvokeNormal_ReturnsEqual() + { + var size = new Size(1, 2); + Assert.NotEqual(0, size.GetHashCode()); + Assert.Equal(size.GetHashCode(), size.GetHashCode()); + } + + public static IEnumerable Parse_TestData() + { + yield return new object[] { "Empty", Size.Empty }; + yield return new object[] { " Empty ", Size.Empty }; + yield return new object[] { "0,0", new Size(0, 0) }; + yield return new object[] { "1,2", new Size(1, 2) }; + yield return new object[] { "1.1,2.2", new Size(1.1, 2.2) }; + yield return new object[] { " 1 , 2 ", new Size(1, 2) }; + } + + [Theory] + [MemberData(nameof(Parse_TestData))] + public void Parse_Invoke_Success(string source, Size expected) + { + Size result = Size.Parse(source); + Assert.Equal(expected.Width, result.Width, precision: 5); + Assert.Equal(expected.Height, result.Height, precision: 5); + } + + [Fact] + public void Parse_NullSource_ThrowsInvalidOperationException() + { + Assert.Throws(() => Size.Parse(null)); + } + + public static IEnumerable Parse_InvalidSource_TestData() + { + yield return new object[] { "" }; + yield return new object[] { " " }; + yield return new object[] { "," }; + yield return new object[] { "1" }; + yield return new object[] { "1," }; + yield return new object[] { "1,2," }; + yield return new object[] { "1,2,3" }; + yield return new object[] { "1,2,test" }; + yield return new object[] { "Empty," }; + yield return new object[] { "Identity," }; + } + + [Theory] + [MemberData(nameof(Parse_InvalidSource_TestData))] + public void Parse_InvalidSource_ThrowsInvalidOperationException(string source) + { + Assert.Throws(() => Size.Parse(source)); + } + + public static IEnumerable Parse_NotDouble_TestData() + { + yield return new object[] { "Identity" }; + yield return new object[] { " Identity " }; + yield return new object[] { "Identity,2" }; + yield return new object[] { "test" }; + yield return new object[] { "test,2" }; + yield return new object[] { "1,test" }; + yield return new object[] { "1,test,3" }; + yield return new object[] { "1;2" }; + yield return new object[] { "1.2.3" }; + yield return new object[] { """1"",""2""" }; + } + + [Theory] + [MemberData(nameof(Parse_NotDouble_TestData))] + public void Parse_NotDouble_ThrowsFormatException(string source) + { + Assert.Throws(() => Size.Parse(source)); + } + + public static IEnumerable Parse_Negative_TestData() + { + yield return new object[] { "-1,2" }; + yield return new object[] { "1,-2" }; + } + + [Theory] + [MemberData(nameof(Parse_Negative_TestData))] + public void Parse_Negative_ThrowsArgumentException(string source) + { + Assert.Throws(() => Size.Parse(source)); + } + + public static IEnumerable ToString_TestData() + { + yield return new object[] { Size.Empty, "Empty" }; + yield return new object[] { new Size(), "0,0" }; + yield return new object[] { new Size(0, 0), "0,0" }; + yield return new object[] { new Size(1.1, 2.2), "1.1,2.2" }; + yield return new object[] { new Size(1, 2), "1,2" }; + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_Invoke_ReturnsExpected(Size size, string expected) + { + Assert.Equal(expected, size.ToString()); + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_InvokeIFormatProviderInvariantCulture_ReturnsExpected(Size size, string expected) + { + Assert.Equal(expected, size.ToString(CultureInfo.InvariantCulture)); + } + + public static IEnumerable ToString_IFormatProviderCustom_TestData() + { + yield return new object[] { Size.Empty, "|", "Empty" }; + yield return new object[] { Size.Empty, "|_", "Empty" }; + yield return new object[] { Size.Empty, ",_", "Empty" }; + yield return new object[] { Size.Empty, ",", "Empty" }; + yield return new object[] { Size.Empty, ";", "Empty" }; + yield return new object[] { Size.Empty, " ", "Empty" }; + + yield return new object[] { new Size(), "|", "0,0" }; + yield return new object[] { new Size(), "|_", "0,0" }; + yield return new object[] { new Size(), ",_", "0;0" }; + yield return new object[] { new Size(), ",", "0;0" }; + yield return new object[] { new Size(), ";", "0,0" }; + yield return new object[] { new Size(), " ", "0,0" }; + + yield return new object[] { new Size(0, 0), "|", "0,0" }; + yield return new object[] { new Size(0, 0), "|_", "0,0" }; + yield return new object[] { new Size(0, 0), ",_", "0;0" }; + yield return new object[] { new Size(0, 0), ",", "0;0" }; + yield return new object[] { new Size(0, 0), ";", "0,0" }; + yield return new object[] { new Size(0, 0), " ", "0,0" }; + + yield return new object[] { new Size(1, 2), "|", "1,2" }; + yield return new object[] { new Size(1, 2), "|_", "1,2" }; + yield return new object[] { new Size(1, 2), ",_", "1;2" }; + yield return new object[] { new Size(1, 2), ",", "1;2" }; + yield return new object[] { new Size(1, 2), ";", "1,2" }; + yield return new object[] { new Size(1, 2), " ", "1,2" }; + + yield return new object[] { new Size(1.1, 2.2), "|", "1|1,2|2" }; + yield return new object[] { new Size(1.1, 2.2), "|_", "1|_1,2|_2" }; + yield return new object[] { new Size(1.1, 2.2), ",_", "1,_1;2,_2" }; + yield return new object[] { new Size(1.1, 2.2), ",", "1,1;2,2" }; + yield return new object[] { new Size(1.1, 2.2), ";", "1;1,2;2" }; + yield return new object[] { new Size(1.1, 2.2), " ", "1 1,2 2" }; + } + + [Theory] + [MemberData(nameof(ToString_IFormatProviderCustom_TestData))] + public void ToString_InvokeIFormatProviderCustom_ReturnsExpected(Size size, string numberDecimalSeparator, string expected) + { + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + Assert.Equal(expected, size.ToString(formatInfo)); + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_InvokeIFormattableInvariantCulture_ReturnsExpected(Size size, string expected) + { + IFormattable formattable = size; + + Assert.Equal(expected, formattable.ToString(null, null)); + Assert.Equal(expected, formattable.ToString(null, CultureInfo.InvariantCulture)); + } + + public static IEnumerable ToString_IFormattableCustomFormat_TestData() + { + yield return new object[] { "|", "1|23,2|35" }; + yield return new object[] { "|_", "1|_23,2|_35" }; + yield return new object[] { ",_", "1,_23;2,_35" }; + yield return new object[] { ",", "1,23;2,35" }; + yield return new object[] { ";", "1;23,2;35" }; + yield return new object[] { " ", "1 23,2 35" }; + } + + [Theory] + [MemberData(nameof(ToString_IFormattableCustomFormat_TestData))] + public void ToString_InvokeIFormattableCustomFormat_ReturnsExpected(string numberDecimalSeparator, string expected) + { + var size = new Size(1.23456, 2.34567); + IFormattable formattable = size; + + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + Assert.Equal(expected, formattable.ToString("F2", formatInfo)); + } + + [Fact] + public void OperatorConvertPoint_InvokeDefault_ReturnsExpected() + { + Point result = (Point)new Size(); + Assert.Equal(0, result.X); + Assert.Equal(0, result.Y); + } + + [Fact] + public void OperatorConvertPoint_InvokeEmpty_ReturnsExpected() + { + Point result = (Point)Size.Empty; + Assert.Equal(double.NegativeInfinity, result.X); + Assert.Equal(double.NegativeInfinity, result.Y); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(1, 0)] + [InlineData(0, 1)] + [InlineData(1, 1)] + [InlineData(1, 2)] + [InlineData(1.1, 2.2)] + [InlineData(double.NaN, double.NaN)] + [InlineData(double.NaN, 0)] + [InlineData(0, double.NaN)] + public void OperatorConvertPoint_InvokeNormal_ReturnsExpected(double x, double y) + { + Point result = (Point)new Size(x, y); + Assert.Equal(x, result.X); + Assert.Equal(y, result.Y); + } + + [Fact] + public void OperatorConvertVector_InvokeDefault_ReturnsExpected() + { + Vector result = (Vector)new Size(); + Assert.Equal(0, result.X); + Assert.Equal(0, result.Y); + } + + [Fact] + public void OperatorConvertVector_InvokeEmpty_ReturnsExpected() + { + Vector result = (Vector)Size.Empty; + Assert.Equal(double.NegativeInfinity, result.X); + Assert.Equal(double.NegativeInfinity, result.Y); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(1, 0)] + [InlineData(0, 1)] + [InlineData(1, 1)] + [InlineData(1, 2)] + [InlineData(1.1, 2.2)] + [InlineData(double.NaN, double.NaN)] + [InlineData(double.NaN, 0)] + [InlineData(0, double.NaN)] + public void OperatorConvertVector_InvokeNormal_ReturnsExpected(double x, double y) + { + Vector result = (Vector)new Size(x, y); + Assert.Equal(x, result.X); + Assert.Equal(y, result.Y); + } + + [Fact] + public void TypeConverter_Get_ReturnsExpected() + { + Assert.IsType(TypeDescriptor.GetConverter(typeof(Size))); + } + + [Fact] + public void ValueSerializer_Get_ReturnsExpected() + { + Assert.IsType(ValueSerializer.GetSerializerFor(typeof(Size))); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/SplashScreenTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/SplashScreenTests.cs new file mode 100644 index 00000000000..b5b16c6d116 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/SplashScreenTests.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using System.Resources; + +namespace System.Windows.Tests; + +public class SplashScreenTests +{ + [Theory] + [InlineData(" ")] + [InlineData("resourceName")] + public void Ctor_String(string resourceName) + { + new SplashScreen(resourceName); + } + + [Theory] + [InlineData(" ")] + [InlineData("resourceName")] + public void Ctor_Assembly_String(string resourceName) + { + new SplashScreen(Assembly.GetEntryAssembly(), resourceName); + } + + [Fact] + public void Ctor_NullResourceName_ThrowsArgumentNullException() + { + Assert.Throws("resourceName", () => new SplashScreen(null)); + Assert.Throws("resourceName", () => new SplashScreen(Assembly.GetEntryAssembly(), null)); + } + + [Fact] + public void Ctor_EmptyResourceName_ThrowsArgumentNullException() + { + Assert.Throws("resourceName", () => new SplashScreen(string.Empty)); + Assert.Throws("resourceName", () => new SplashScreen(Assembly.GetEntryAssembly(), string.Empty)); + } + + [Fact] + public void Ctor_NullResourceAssembly_ThrowsArgumentNullException() + { + Assert.Throws("resourceAssembly", () => new SplashScreen(null!, "resourceName")); + } + + [Theory] + [InlineData(" ")] + [InlineData("resourceName")] + public void Close_NotShown_Nop(string resourceName) + { + var splashScreen = new SplashScreen(resourceName); + splashScreen.Close(TimeSpan.Zero); + } + + [Theory] + [InlineData(" ")] + [InlineData("resourceName")] + public void Show_NoSuchResource_ThrowsMissingManifestResourceException(string resourceName) + { + var splashScreen = new SplashScreen(resourceName); + Assert.Throws(() => splashScreen.Show(false)); + Assert.Throws(() => splashScreen.Show(true)); + Assert.Throws(() => splashScreen.Show(false, false)); + Assert.Throws(() => splashScreen.Show(false, true)); + Assert.Throws(() => splashScreen.Show(true, false)); + Assert.Throws(() => splashScreen.Show(true, true)); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherFrameTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherFrameTests.cs new file mode 100644 index 00000000000..2345c87f14c --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherFrameTests.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Threading.Tests; + +public class DispatcherFrameTests +{ + [Fact] + public void Ctor_Default() + { + var frame = new DispatcherFrame(); + Assert.True(frame.Continue); + Assert.NotNull(frame.Dispatcher); + Assert.Same(frame.Dispatcher, frame.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, frame.Dispatcher); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Ctor_Bool(bool exitWhenRequested) + { + var frame = new DispatcherFrame(exitWhenRequested); + Assert.True(frame.Continue); + Assert.NotNull(frame.Dispatcher); + Assert.Same(frame.Dispatcher, frame.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, frame.Dispatcher); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Continue_Set_GetReturnsExpected(bool value) + { + var frame = new DispatcherFrame(); + + // Set same. + frame.Continue = value; + Assert.Equal(value, frame.Continue); + + // Set different. + frame.Continue = !value; + Assert.Equal(!value, frame.Continue); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherHookEventArgsTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherHookEventArgsTests.cs new file mode 100644 index 00000000000..c2dd90de9a8 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherHookEventArgsTests.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Threading.Tests; + +public class DispatcherHookEventArgsTests +{ + [Fact] + public void Ctor_DispatcherOperation() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + DispatcherOperation operation = dispatcher.BeginInvoke(() => {}); + + var args = new DispatcherHookEventArgs(operation); + Assert.Same(dispatcher, args.Dispatcher); + Assert.Same(operation, args.Operation); + } + + [Fact] + public void Ctor_NullOperation() + { + var args = new DispatcherHookEventArgs(null); + Assert.Null(args.Dispatcher); + Assert.Null(args.Operation); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherHooksTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherHooksTests.cs new file mode 100644 index 00000000000..74e1789aaca --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherHooksTests.cs @@ -0,0 +1,167 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Threading.Tests; + +public class DispatcherHooksTests +{ + // TODO + // - actually raising events + + [Fact] + public void DispatcherInactive_AddRemove_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + DispatcherHooks hooks = dispatcher.Hooks; + int callCount = 0; + EventHandler handler = (s, e) => callCount++; + hooks.DispatcherInactive += handler; + Assert.Equal(0, callCount); + + hooks.DispatcherInactive -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + hooks.DispatcherInactive -= handler; + Assert.Equal(0, callCount); + + // Add null. + hooks.DispatcherInactive += null; + Assert.Equal(0, callCount); + + // Remove null. + hooks.DispatcherInactive -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void OperationPosted_AddRemove_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + DispatcherHooks hooks = dispatcher.Hooks; + int callCount = 0; + DispatcherHookEventHandler handler = (s, e) => callCount++; + hooks.OperationPosted += handler; + Assert.Equal(0, callCount); + + hooks.OperationPosted -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + hooks.OperationPosted -= handler; + Assert.Equal(0, callCount); + + // Add null. + hooks.OperationPosted += null; + Assert.Equal(0, callCount); + + // Remove null. + hooks.OperationPosted -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void OperationAborted_AddRemove_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + DispatcherHooks hooks = dispatcher.Hooks; + int callCount = 0; + DispatcherHookEventHandler handler = (s, e) => callCount++; + hooks.OperationAborted += handler; + Assert.Equal(0, callCount); + + hooks.OperationAborted -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + hooks.OperationAborted -= handler; + Assert.Equal(0, callCount); + + // Add null. + hooks.OperationAborted += null; + Assert.Equal(0, callCount); + + // Remove null. + hooks.OperationAborted -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void OperationCompleted_AddRemove_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + DispatcherHooks hooks = dispatcher.Hooks; + int callCount = 0; + DispatcherHookEventHandler handler = (s, e) => callCount++; + hooks.OperationCompleted += handler; + Assert.Equal(0, callCount); + + hooks.OperationCompleted -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + hooks.OperationCompleted -= handler; + Assert.Equal(0, callCount); + + // Add null. + hooks.OperationCompleted += null; + Assert.Equal(0, callCount); + + // Remove null. + hooks.OperationCompleted -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void OperationPriorityChanged_AddRemove_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + DispatcherHooks hooks = dispatcher.Hooks; + int callCount = 0; + DispatcherHookEventHandler handler = (s, e) => callCount++; + hooks.OperationPriorityChanged += handler; + Assert.Equal(0, callCount); + + hooks.OperationPriorityChanged -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + hooks.OperationPriorityChanged -= handler; + Assert.Equal(0, callCount); + + // Add null. + hooks.OperationPriorityChanged += null; + Assert.Equal(0, callCount); + + // Remove null. + hooks.OperationPriorityChanged -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void OperationStarted_AddRemove_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + DispatcherHooks hooks = dispatcher.Hooks; + int callCount = 0; + DispatcherHookEventHandler handler = (s, e) => callCount++; + hooks.OperationStarted += handler; + Assert.Equal(0, callCount); + + hooks.OperationStarted -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + hooks.OperationStarted -= handler; + Assert.Equal(0, callCount); + + // Add null. + hooks.OperationStarted += null; + Assert.Equal(0, callCount); + + // Remove null. + hooks.OperationStarted -= null; + Assert.Equal(0, callCount); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherObjectTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherObjectTests.cs new file mode 100644 index 00000000000..e34186e4d05 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherObjectTests.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; + +namespace System.Windows.Threading.Tests; + +public class DispatcherObjectTests +{ + [Fact] + public void Ctor_Default() + { + var obj = new SubDispatcherObject(); + Assert.NotNull(obj.Dispatcher); + Assert.Same(obj.Dispatcher, obj.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, obj.Dispatcher); + } + + [Fact] + public void CheckAccess_InvokeOnCurrentThread_ReturnsTrue() + { + var obj = new SubDispatcherObject(); + Assert.True(obj.CheckAccess()); + } + + [Fact] + public void CheckAccess_InvokeOnDifferentThread_ReturnsFalse() + { + var obj = new SubDispatcherObject(); + bool? access = null; + var thread = new Thread(() => + { + access = obj.CheckAccess(); + }); + thread.Start(); + thread.Join(); + Assert.False(access); + } + + [Fact] + public void CheckAccess_InvokeDetachedDispatcher_ReturnsTrue() + { + var obj = new SubFreezable(); + obj.Freeze(); + Assert.Null(obj.Dispatcher); + + Assert.True(obj.CheckAccess()); + } + + [Fact] + public void VerifyAccess_InvokeOnCurrentThread_Success() + { + var obj = new SubDispatcherObject(); + obj.VerifyAccess(); + } + + [Fact] + public void VerifyAccess_InvokeDetachedDispatcher_Success() + { + var obj = new SubFreezable(); + obj.Freeze(); + Assert.Null(obj.Dispatcher); + + obj.VerifyAccess(); + } + + [Fact] + public void VerifyAccess_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + var obj = new SubDispatcherObject(); + bool? threwInvalidOperationException = null; + var thread = new Thread(() => + { + try + { + obj.VerifyAccess(); + threwInvalidOperationException = false; + } + catch (InvalidOperationException) + { + threwInvalidOperationException = true; + } + catch + { + threwInvalidOperationException = false; + } + }); + thread.Start(); + thread.Join(); + Assert.True(threwInvalidOperationException); + } + + private class SubFreezable : Freezable + { + protected override Freezable CreateInstanceCore() => throw new NotImplementedException(); + } + + public class SubDispatcherObject : DispatcherObject + { + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherOperationTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherOperationTests.cs new file mode 100644 index 00000000000..0e291cebec9 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherOperationTests.cs @@ -0,0 +1,346 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; + +namespace System.Windows.Threading.Tests; + +public class DispatcherOperationTests +{ + [Fact] + public void Aborted_AddRemove_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + DispatcherOperation operation = dispatcher.BeginInvoke(() => {}); + + int callCount = 0; + EventHandler handler = (s, e) => callCount++; + operation.Aborted += handler; + Assert.Equal(0, callCount); + + operation.Aborted -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + operation.Aborted -= handler; + Assert.Equal(0, callCount); + + // Add null. + operation.Aborted += null; + Assert.Equal(0, callCount); + + // Remove null. + operation.Aborted -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void Completed_AddRemove_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + DispatcherOperation operation = dispatcher.BeginInvoke(() => {}); + + int callCount = 0; + EventHandler handler = (s, e) => callCount++; + operation.Completed += handler; + Assert.Equal(0, callCount); + + operation.Completed -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + operation.Completed -= handler; + Assert.Equal(0, callCount); + + // Add null. + operation.Completed += null; + Assert.Equal(0, callCount); + + // Remove null. + operation.Completed -= null; + Assert.Equal(0, callCount); + } + + [Theory] + [InlineData(DispatcherPriority.ApplicationIdle)] + [InlineData(DispatcherPriority.Background)] + [InlineData(DispatcherPriority.ContextIdle)] + [InlineData(DispatcherPriority.DataBind)] + [InlineData(DispatcherPriority.Inactive)] + [InlineData(DispatcherPriority.Input)] + [InlineData(DispatcherPriority.Loaded)] + [InlineData(DispatcherPriority.Normal)] + [InlineData(DispatcherPriority.Render)] + [InlineData(DispatcherPriority.Send)] + [InlineData(DispatcherPriority.SystemIdle)] + public void Priority_Set_GetReturnsExpected(DispatcherPriority value) + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + DispatcherOperation operation = dispatcher.BeginInvoke(() => { }); + + // Set. + operation.Priority = value; + Assert.Equal(value, operation.Priority); + + // Set same. + operation.Priority = value; + Assert.Equal(value, operation.Priority); + } + + [Theory] + [InlineData(DispatcherPriority.Invalid)] + [InlineData(DispatcherPriority.Invalid - 1)] + [InlineData(DispatcherPriority.Send + 1)] + public void Priority_SetInvalid_ThrowsInvalidEnumArgumentException(DispatcherPriority value) + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + DispatcherOperation operation = dispatcher.BeginInvoke(() => { }); + + Assert.Throws("value", () => operation.Priority = value); + } + + [Fact] + public void Abort_Invoke_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + int callCount = 0; + DispatcherOperation operation = null!; + Delegate method = new Action(() => callCount++); + operation = dispatcher.BeginInvoke(method); + + // Abort. + Assert.True(operation.Abort()); + Assert.Equal(0, callCount); + Assert.Equal(DispatcherOperationStatus.Aborted, operation.Status); + Assert.Null(operation.Result); + + // Abort again. + Assert.False(operation.Abort()); + Assert.Equal(0, callCount); + Assert.Equal(DispatcherOperationStatus.Aborted, operation.Status); + Assert.Null(operation.Result); + } + + [Fact] + public void Abort_InvokeCompleted_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + int callCount = 0; + DispatcherOperation operation = null!; + Delegate method = new Action(() => callCount++); + operation = dispatcher.BeginInvoke(method); + + // Wait. + Assert.Equal(DispatcherOperationStatus.Completed, operation.Wait()); + Assert.Equal(1, callCount); + Assert.Equal(DispatcherOperationStatus.Completed, operation.Status); + Assert.Null(operation.Result); + + // Abort. + Assert.False(operation.Abort()); + Assert.Equal(1, callCount); + Assert.Equal(DispatcherOperationStatus.Completed, operation.Status); + Assert.Null(operation.Result); + + // Abort again. + Assert.False(operation.Abort()); + Assert.Equal(1, callCount); + Assert.Equal(DispatcherOperationStatus.Completed, operation.Status); + Assert.Null(operation.Result); + } + + [Fact] + public void Abort_InvokeWithHandler_CallsAborted() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + int callCount = 0; + DispatcherOperation operation = null!; + Delegate method = new Action(() => callCount++); + operation = dispatcher.BeginInvoke(method); + + int abortedCallCount = 0; + EventHandler aborted = (sender, e) => + { + Assert.Same(operation, sender); + Assert.Equal(EventArgs.Empty, e); + abortedCallCount++; + }; + operation.Aborted += aborted; + + // Abort. + Assert.True(operation.Abort()); + Assert.Equal(0, callCount); + Assert.Equal(1, abortedCallCount); + Assert.Equal(DispatcherOperationStatus.Aborted, operation.Status); + Assert.Null(operation.Result); + + // Abort again. + Assert.False(operation.Abort()); + Assert.Equal(0, callCount); + Assert.Equal(1, abortedCallCount); + Assert.Equal(DispatcherOperationStatus.Aborted, operation.Status); + Assert.Null(operation.Result); + } + + [Fact] + public void Abort_InvokeWithRemovedHandler_DoesNotCallAborted() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + int callCount = 0; + DispatcherOperation operation = null!; + Delegate method = new Action(() => callCount++); + operation = dispatcher.BeginInvoke(method); + + int abortedCallCount = 0; + EventHandler aborted = (sender, e) => abortedCallCount++; + operation.Aborted += aborted; + operation.Aborted -= aborted; + + // Abort. + Assert.True(operation.Abort()); + Assert.Equal(0, callCount); + Assert.Equal(0, abortedCallCount); + Assert.Equal(DispatcherOperationStatus.Aborted, operation.Status); + Assert.Null(operation.Result); + + // Abort again. + Assert.False(operation.Abort()); + Assert.Equal(0, callCount); + Assert.Equal(0, abortedCallCount); + Assert.Equal(DispatcherOperationStatus.Aborted, operation.Status); + Assert.Null(operation.Result); + } + + [Fact] + public void Wait_InvokeNoResult_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + int callCount = 0; + DispatcherOperation operation = null!; + Delegate method = new Action(() => + { + Assert.Equal(DispatcherOperationStatus.Executing, operation.Status); + callCount++; + }); + operation = dispatcher.BeginInvoke(method); + + // Wait. + Assert.Equal(DispatcherOperationStatus.Completed, operation.Wait()); + Assert.Equal(1, callCount); + Assert.Equal(DispatcherOperationStatus.Completed, operation.Status); + Assert.Null(operation.Result); + + // Wait again. + Assert.Equal(DispatcherOperationStatus.Completed, operation.Wait()); + Assert.Equal(1, callCount); + Assert.Equal(DispatcherOperationStatus.Completed, operation.Status); + Assert.Null(operation.Result); + } + + [Fact] + public void Wait_InvokeResult_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + int callCount = 0; + DispatcherOperation operation = null!; + var result = new object(); + Delegate method = new Func(() => + { + Assert.Equal(DispatcherOperationStatus.Executing, operation.Status); + callCount++; + return result; + }); + operation = dispatcher.BeginInvoke(method); + + // Wait. + Assert.Equal(DispatcherOperationStatus.Completed, operation.Wait()); + Assert.Equal(1, callCount); + Assert.Equal(DispatcherOperationStatus.Completed, operation.Status); + Assert.Same(result, operation.Result); + + // Wait again. + Assert.Equal(DispatcherOperationStatus.Completed, operation.Wait()); + Assert.Equal(1, callCount); + Assert.Equal(DispatcherOperationStatus.Completed, operation.Status); + Assert.Same(result, operation.Result); + } + + [Fact] + public void Wait_InvokeWithHandler_CallsCompleted() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + int callCount = 0; + DispatcherOperation operation = null!; + Delegate method = new Action(() => + { + Assert.Equal(DispatcherOperationStatus.Executing, operation.Status); + callCount++; + }); + operation = dispatcher.BeginInvoke(method); + + int completedCallCount = 0; + EventHandler handler = (sender, e) => + { + Assert.Same(operation, sender); + Assert.Equal(EventArgs.Empty, e); + completedCallCount++; + }; + operation.Completed += handler; + + // Wait. + Assert.Equal(DispatcherOperationStatus.Completed, operation.Wait()); + Assert.Equal(1, callCount); + Assert.Equal(1, completedCallCount); + Assert.Equal(DispatcherOperationStatus.Completed, operation.Status); + Assert.Null(operation.Result); + + // Wait again. + Assert.Equal(DispatcherOperationStatus.Completed, operation.Wait()); + Assert.Equal(1, callCount); + Assert.Equal(1, completedCallCount); + Assert.Equal(DispatcherOperationStatus.Completed, operation.Status); + Assert.Null(operation.Result); + } + + [Fact] + public void Wait_InvokeWithRemovedHandler_DoesNotCallCompleted() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + int callCount = 0; + DispatcherOperation operation = null!; + Delegate method = new Action(() => + { + Assert.Equal(DispatcherOperationStatus.Executing, operation.Status); + callCount++; + }); + operation = dispatcher.BeginInvoke(method); + + int completedCallCount = 0; + EventHandler handler = (sender, e) => completedCallCount++; + operation.Completed += handler; + operation.Completed -= handler; + + // Wait. + Assert.Equal(DispatcherOperationStatus.Completed, operation.Wait()); + Assert.Equal(1, callCount); + Assert.Equal(0, completedCallCount); + Assert.Equal(DispatcherOperationStatus.Completed, operation.Status); + Assert.Null(operation.Result); + + // Wait again. + Assert.Equal(DispatcherOperationStatus.Completed, operation.Wait()); + Assert.Equal(1, callCount); + Assert.Equal(0, completedCallCount); + Assert.Equal(DispatcherOperationStatus.Completed, operation.Status); + Assert.Null(operation.Result); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherPriorityAwaitableTests..cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherPriorityAwaitableTests..cs new file mode 100644 index 00000000000..2adce5e9514 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherPriorityAwaitableTests..cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Threading.Tests; + +public class DispatcherPriorityAwaitableTests +{ + [Theory] + [InlineData(DispatcherPriority.ApplicationIdle)] + [InlineData(DispatcherPriority.Background)] + [InlineData(DispatcherPriority.ContextIdle)] + [InlineData(DispatcherPriority.DataBind)] + [InlineData(DispatcherPriority.Inactive)] + [InlineData(DispatcherPriority.Input)] + [InlineData(DispatcherPriority.Loaded)] + [InlineData(DispatcherPriority.Normal)] + [InlineData(DispatcherPriority.Render)] + [InlineData(DispatcherPriority.Send)] + [InlineData(DispatcherPriority.SystemIdle)] + public void GetAwaiter_InvokeWithDispatcher_ReturnsExpected(DispatcherPriority priority) + { + DispatcherPriorityAwaitable awaitable; + try + { + awaitable = Dispatcher.Yield(priority); + } + catch (InvalidOperationException) + { + // Yield throws if there is no dispatcher. + return; + } + + DispatcherPriorityAwaiter awaiter = awaitable.GetAwaiter(); + Assert.False(awaiter.IsCompleted); + Assert.Equal(awaiter, awaitable.GetAwaiter()); + } + + [Fact] + public void GetAwaiter_InvokeDefault_ReturnsExpected() + { + var awaitable = new DispatcherPriorityAwaitable(); + DispatcherPriorityAwaiter awaiter = awaitable.GetAwaiter(); + Assert.False(awaiter.IsCompleted); + Assert.Equal(awaiter, awaitable.GetAwaiter()); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherPriorityAwaiterTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherPriorityAwaiterTests.cs new file mode 100644 index 00000000000..2f219f32fb0 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherPriorityAwaiterTests.cs @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Threading.Tests; + +public class DispatcherPriorityAwaiterTests +{ + [Fact] + public void Ctor_Default() + { + var awaiter = new DispatcherPriorityAwaiter(); + Assert.False(awaiter.IsCompleted); + } + + [Theory] + [InlineData(DispatcherPriority.ApplicationIdle)] + [InlineData(DispatcherPriority.Background)] + [InlineData(DispatcherPriority.ContextIdle)] + [InlineData(DispatcherPriority.DataBind)] + [InlineData(DispatcherPriority.Inactive)] + [InlineData(DispatcherPriority.Input)] + [InlineData(DispatcherPriority.Loaded)] + [InlineData(DispatcherPriority.Normal)] + [InlineData(DispatcherPriority.Render)] + [InlineData(DispatcherPriority.Send)] + [InlineData(DispatcherPriority.SystemIdle)] + public void GetResult_InvokeWithDispatcher_Nop(DispatcherPriority priority) + { + DispatcherPriorityAwaitable awaitable; + try + { + awaitable = Dispatcher.Yield(priority); + } + catch (InvalidOperationException) + { + // Yield throws if there is no dispatcher. + return; + } + + DispatcherPriorityAwaiter awaiter = awaitable.GetAwaiter(); + + // Invoke. + awaiter.GetResult(); + + // Invoke again. + awaiter.GetResult(); + } + + [Fact] + public void GetResult_InvokeDefaultAwaitable_Nop() + { + var awaitable = new DispatcherPriorityAwaitable(); + DispatcherPriorityAwaiter awaiter = awaitable.GetAwaiter(); + + // Invoke. + awaiter.GetResult(); + + // Invoke again. + awaiter.GetResult(); + } + + [Fact] + public void GetResult_InvokeDefault_Nop() + { + var awaiter = new DispatcherPriorityAwaiter(); + + // Invoke. + awaiter.GetResult(); + + // Invoke again. + awaiter.GetResult(); + } + + [Theory] + [InlineData(DispatcherPriority.ApplicationIdle)] + [InlineData(DispatcherPriority.Background)] + [InlineData(DispatcherPriority.ContextIdle)] + [InlineData(DispatcherPriority.DataBind)] + [InlineData(DispatcherPriority.Inactive)] + [InlineData(DispatcherPriority.Input)] + [InlineData(DispatcherPriority.Loaded)] + [InlineData(DispatcherPriority.Normal)] + [InlineData(DispatcherPriority.Render)] + [InlineData(DispatcherPriority.Send)] + [InlineData(DispatcherPriority.SystemIdle)] + public void OnCompleted_InvokeWithDispatcher_Success(DispatcherPriority priority) + { + DispatcherPriorityAwaitable awaitable; + try + { + awaitable = Dispatcher.Yield(priority); + } + catch (InvalidOperationException) + { + // Yield throws if there is no dispatcher. + return; + } + + DispatcherPriorityAwaiter awaiter = awaitable.GetAwaiter(); + + int callCount = 0; + Action action = () => + { + callCount++; + }; + + // Invoke. + awaiter.OnCompleted(action); + Assert.Equal(0, callCount); + + // Invoke again. + awaiter.OnCompleted(action); + Assert.Equal(0, callCount); + } + + [Fact] + public void OnCompleted_NullAction_ThrowsArgumentNullException() + { + DispatcherPriorityAwaitable awaitable; + try + { + awaitable = Dispatcher.Yield(DispatcherPriority.Normal); + } + catch (InvalidOperationException) + { + // Yield throws if there is no dispatcher. + return; + } + + DispatcherPriorityAwaiter awaiter = awaitable.GetAwaiter(); + Assert.Throws(() => awaiter.OnCompleted(null)); + } + + [Fact] + public void OnCompleted_InvokeDefaultAwaitable_ThrowsInvalidOperationException() + { + var awaitable = new DispatcherPriorityAwaitable(); + DispatcherPriorityAwaiter awaiter = awaitable.GetAwaiter(); + Assert.Throws(() => awaiter.OnCompleted(null)); + Assert.Throws(() => awaiter.OnCompleted(() => { })); + } + + [Fact] + public void OnCompleted_InvokeDefault_ThrowsInvalidOperationException() + { + var awaiter = new DispatcherPriorityAwaiter(); + Assert.Throws(() => awaiter.OnCompleted(null)); + Assert.Throws(() => awaiter.OnCompleted(() => { })); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherProcessingDisabledTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherProcessingDisabledTests.cs new file mode 100644 index 00000000000..146f2a3ff7c --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherProcessingDisabledTests.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Windows.Threading.Tests; + +public class DispatcherProcessingDisabledTests +{ + [Fact] + public void Dispose_Default_Nop() + { + var disabled = new DispatcherProcessingDisabled(); + disabled.Dispose(); + + // Call again. + disabled.Dispose(); + } + + public static IEnumerable Equals_TestData() + { + yield return new object?[] { new DispatcherProcessingDisabled(), new DispatcherProcessingDisabled(), true }; + yield return new object?[] { new DispatcherProcessingDisabled(), new object(), false }; + yield return new object?[] { new DispatcherProcessingDisabled(), null, false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void Equals_Invoke_Success(DispatcherProcessingDisabled disabled, object? obj, bool expected) + { + Assert.Equal(expected, disabled.Equals(obj)); + if (obj is DispatcherProcessingDisabled other) + { + Assert.Equal(expected, disabled.GetHashCode().Equals(other.GetHashCode())); + Assert.Equal(expected, disabled == other); + Assert.Equal(!expected, disabled != other); + } + } + + [Fact] + public void GetHashCode_Invoke_ReturnsExpected() + { + var disabled = new DispatcherProcessingDisabled(); + Assert.Equal(disabled.GetHashCode(), disabled.GetHashCode()); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherSynchronizationContextTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherSynchronizationContextTests.cs new file mode 100644 index 00000000000..29f18e834d5 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherSynchronizationContextTests.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; + +namespace System.Windows.Threading.Tests; + +public class DispatcherSynchronizationContextTests +{ + [Fact] + public void Ctor_Default() + { + new DispatcherSynchronizationContext(); + } + + [Fact] + public void Ctor_Dispatcher() + { + new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher); + } + + [Theory] + [InlineData(DispatcherPriority.ApplicationIdle)] + [InlineData(DispatcherPriority.Background)] + [InlineData(DispatcherPriority.ContextIdle)] + [InlineData(DispatcherPriority.DataBind)] + [InlineData(DispatcherPriority.Inactive)] + [InlineData(DispatcherPriority.Input)] + [InlineData(DispatcherPriority.Loaded)] + [InlineData(DispatcherPriority.Normal)] + [InlineData(DispatcherPriority.Render)] + [InlineData(DispatcherPriority.Send)] + [InlineData(DispatcherPriority.SystemIdle)] + public void Ctor_Dispatcher_DispatcherPriority(DispatcherPriority priority) + { + new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher, priority); + } + + [Fact] + public void Ctor_NullDispatcher_ThrowsArgumentNullException() + { + Assert.Throws("dispatcher", () => new DispatcherSynchronizationContext(null)); + Assert.Throws("dispatcher", () => new DispatcherSynchronizationContext(null, DispatcherPriority.Normal)); + } + + [Theory] + [InlineData(DispatcherPriority.Invalid)] + [InlineData(DispatcherPriority.Invalid - 1)] + [InlineData(DispatcherPriority.Send + 1)] + public void Ctor_InvalidPriority_ThrowsInvalidEnumArgumentException(DispatcherPriority priority) + { + Assert.Throws("priority", () => new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher, priority)); + } + + [Fact] + public void CreateCopy_Default_ReturnsSelf() + { + var context = new DispatcherSynchronizationContext(); + DispatcherSynchronizationContext copy = Assert.IsType(context.CreateCopy()); + Assert.NotNull(copy); + Assert.NotSame(context, copy); + } + + [Theory] + [InlineData(DispatcherPriority.ApplicationIdle)] + public void CreateCopy_Custom_ReturnsSelf(DispatcherPriority priority) + { + var context = new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher, priority); + DispatcherSynchronizationContext copy = Assert.IsType(context.CreateCopy()); + Assert.NotNull(copy); + Assert.NotSame(context, copy); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherTests.cs new file mode 100644 index 00000000000..b45867daffd --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherTests.cs @@ -0,0 +1,204 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Threading; + +namespace System.Windows.Threading.Tests; + +public class DispatcherTests +{ + [Fact] + public void CurrentDispatcher_Get_ReturnsExpected() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + Assert.NotNull(dispatcher); + Assert.Same(dispatcher, Dispatcher.CurrentDispatcher); + Assert.False(dispatcher.HasShutdownFinished); + Assert.False(dispatcher.HasShutdownStarted); + Assert.NotNull(dispatcher.Hooks); + Assert.Same(dispatcher.Hooks, dispatcher.Hooks); + Assert.Same(Thread.CurrentThread, dispatcher.Thread); + } + + [Fact] + public void PushFrame_NullFrame_ThrowsArgumentNullException() + { + Assert.Throws("frame", () => Dispatcher.PushFrame(null!)); + } + + [Fact] + public void FromThread_CurrentThread_ReturnsExpected() + { + Dispatcher dispatcher = Dispatcher.FromThread(Thread.CurrentThread); + Assert.NotNull(dispatcher); + Assert.Same(dispatcher, Dispatcher.CurrentDispatcher); + Assert.False(dispatcher.HasShutdownFinished); + Assert.False(dispatcher.HasShutdownStarted); + Assert.NotNull(dispatcher.Hooks); + Assert.Same(dispatcher.Hooks, dispatcher.Hooks); + Assert.Same(Thread.CurrentThread, dispatcher.Thread); + } + + [Fact] + public void FromThread_NoSuchThread_ReturnsNull() + { + var thread = new Thread(() => { }); + Assert.Null(Dispatcher.FromThread(thread)); + } + + [Fact] + public void FromThread_NullThread_ReturnsNull() + { + Assert.Null(Dispatcher.FromThread(null)); + } + + [Theory] + [InlineData(DispatcherPriority.ApplicationIdle)] + [InlineData(DispatcherPriority.Background)] + [InlineData(DispatcherPriority.ContextIdle)] + [InlineData(DispatcherPriority.DataBind)] + [InlineData(DispatcherPriority.Inactive)] + [InlineData(DispatcherPriority.Input)] + [InlineData(DispatcherPriority.Loaded)] + [InlineData(DispatcherPriority.Normal)] + [InlineData(DispatcherPriority.Render)] + [InlineData(DispatcherPriority.Send)] + [InlineData(DispatcherPriority.SystemIdle)] + public void ValidatePriority_InvokeValidPriority_Nop(DispatcherPriority priority) + { + Dispatcher.ValidatePriority(priority, "priority"); + Dispatcher.ValidatePriority(priority, string.Empty); + Dispatcher.ValidatePriority(priority, null); + } + + [Theory] + [InlineData(DispatcherPriority.Invalid)] + [InlineData(DispatcherPriority.Invalid - 1)] + [InlineData(DispatcherPriority.Send + 1)] + public void ValidatePriority_InvokeInvalidPriority_ThrowsInvalidEnumArgumentException(DispatcherPriority priority) + { + Assert.Throws("priority", () => Dispatcher.ValidatePriority(priority, "priority")); + Assert.Throws("", () => Dispatcher.ValidatePriority(priority, "")); + Assert.Throws(() => Dispatcher.ValidatePriority(priority, null)); + } + + [Theory] + [InlineData(DispatcherPriority.ApplicationIdle)] + [InlineData(DispatcherPriority.Background)] + [InlineData(DispatcherPriority.ContextIdle)] + [InlineData(DispatcherPriority.DataBind)] + [InlineData(DispatcherPriority.Inactive)] + [InlineData(DispatcherPriority.Input)] + [InlineData(DispatcherPriority.Loaded)] + [InlineData(DispatcherPriority.Normal)] + [InlineData(DispatcherPriority.Render)] + [InlineData(DispatcherPriority.Send)] + [InlineData(DispatcherPriority.SystemIdle)] + public void Yield_Invoke_ReturnsExpected(DispatcherPriority priority) + { + DispatcherPriorityAwaitable awaitable; + try + { + awaitable = Dispatcher.Yield(priority); + } + catch (InvalidOperationException) + { + // Yield throws if there is no dispatcher. + return; + } + + Assert.Equal(awaitable, Dispatcher.Yield(priority)); + } + + [Theory] + [InlineData(DispatcherPriority.Invalid)] + [InlineData(DispatcherPriority.Invalid - 1)] + [InlineData(DispatcherPriority.Send + 1)] + public void Yield_InvokeInvalidPriority_ThrowsInvalidEnumArgumentException(DispatcherPriority priority) + { + Assert.Throws("priority", () => Dispatcher.Yield(priority)); + } + + [Fact] + public void BeginInvoke_InvokeDelegateObjectArray_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + Delegate action = new Action(() => { }); + DispatcherOperation operation = dispatcher.BeginInvoke(action, Array.Empty()); + Assert.NotNull(operation); + Assert.Same(dispatcher, operation.Dispatcher); + Assert.Equal(DispatcherPriority.Normal, operation.Priority); + Assert.Equal(DispatcherOperationStatus.Pending, operation.Status); + Assert.NotNull(operation.Task); + } + + [Theory] + [InlineData(DispatcherPriority.Invalid)] + [InlineData(DispatcherPriority.Invalid - 1)] + [InlineData(DispatcherPriority.Send + 1)] + public void BeginInvoke_InvokeInvalidPriority_ThrowsInvalidEnumArgumentException(DispatcherPriority priority) + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + Assert.Throws("priority", () => dispatcher.BeginInvoke(priority, (Action)(() => { }))); + Assert.Throws("priority", () => dispatcher.BeginInvoke(priority, (Action)((arg) => { }), new object())); + Assert.Throws("priority", () => dispatcher.BeginInvoke(priority, (Action)((arg1, arg2) => { }), new object(), new object[] { new object() })); + Assert.Throws("priority", () => dispatcher.BeginInvoke(priority, (Action)((arg1, arg2) => { }), new object[] { new object(), new object() })); + } + + [Fact] + public void CheckAccess_InvokeOnCurrentThread_ReturnsTrue() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + Assert.True(dispatcher.CheckAccess()); + } + + [Fact] + public void CheckAccess_InvokeOnDifferentThread_ReturnsFalse() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + bool? access = null; + var thread = new Thread(() => + { + access = dispatcher.CheckAccess(); + }); + thread.Start(); + thread.Join(); + Assert.False(access); + } + + [Fact] + public void VerifyAccess_InvokeOnCurrentThread_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + dispatcher.VerifyAccess(); + } + + [Fact] + public void VerifyAccess_InvokeOnDifferentThread_ThrowsInvalidOperationException() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + bool? threwInvalidOperationException = null; + var thread = new Thread(() => + { + try + { + dispatcher.VerifyAccess(); + threwInvalidOperationException = false; + } + catch (InvalidOperationException) + { + threwInvalidOperationException = true; + } + catch + { + threwInvalidOperationException = false; + } + }); + thread.Start(); + thread.Join(); + Assert.True(threwInvalidOperationException); + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherTimerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherTimerTests.cs new file mode 100644 index 00000000000..5701ea9ecd1 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherTimerTests.cs @@ -0,0 +1,305 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; + +namespace System.Windows.Threading.Tests; + +public class DispatcherTimerTests +{ + [Fact] + public void Ctor_Default() + { + var timer = new DispatcherTimer(); + Assert.Same(Dispatcher.CurrentDispatcher, timer.Dispatcher); + Assert.Equal(TimeSpan.Zero, timer.Interval); + Assert.False(timer.IsEnabled); + Assert.Null(timer.Tag); + } + + [Theory] + [InlineData(DispatcherPriority.ApplicationIdle)] + [InlineData(DispatcherPriority.Background)] + [InlineData(DispatcherPriority.ContextIdle)] + [InlineData(DispatcherPriority.DataBind)] + [InlineData(DispatcherPriority.Input)] + [InlineData(DispatcherPriority.Loaded)] + [InlineData(DispatcherPriority.Normal)] + [InlineData(DispatcherPriority.Render)] + [InlineData(DispatcherPriority.Send)] + [InlineData(DispatcherPriority.SystemIdle)] + public void Ctor_DispatcherPriority(DispatcherPriority priority) + { + var timer = new DispatcherTimer(priority); + Assert.Same(Dispatcher.CurrentDispatcher, timer.Dispatcher); + Assert.Equal(TimeSpan.Zero, timer.Interval); + Assert.False(timer.IsEnabled); + Assert.Null(timer.Tag); + } + + [Theory] + [InlineData(DispatcherPriority.ApplicationIdle)] + [InlineData(DispatcherPriority.Background)] + [InlineData(DispatcherPriority.ContextIdle)] + [InlineData(DispatcherPriority.DataBind)] + [InlineData(DispatcherPriority.Input)] + [InlineData(DispatcherPriority.Loaded)] + [InlineData(DispatcherPriority.Normal)] + [InlineData(DispatcherPriority.Render)] + [InlineData(DispatcherPriority.Send)] + [InlineData(DispatcherPriority.SystemIdle)] + public void Ctor_DispatcherPriority_Dispatcher(DispatcherPriority priority) + { + var timer = new DispatcherTimer(priority, Dispatcher.CurrentDispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, timer.Dispatcher); + Assert.Equal(TimeSpan.Zero, timer.Interval); + Assert.False(timer.IsEnabled); + Assert.Null(timer.Tag); + } + + public static IEnumerable Ctor_TimeSpan_DispatcherPriority_EventHandler_Dispatcher_TestData() + { + yield return new object?[] { TimeSpan.FromMilliseconds(0), DispatcherPriority.ApplicationIdle }; + yield return new object?[] { TimeSpan.FromMilliseconds(0), DispatcherPriority.Background }; + yield return new object?[] { TimeSpan.FromMilliseconds(int.MaxValue), DispatcherPriority.ContextIdle }; + yield return new object?[] { TimeSpan.FromMilliseconds(0), DispatcherPriority.DataBind }; + yield return new object?[] { TimeSpan.FromMilliseconds(1), DispatcherPriority.Input }; + yield return new object?[] { TimeSpan.FromMilliseconds(2), DispatcherPriority.Loaded }; + yield return new object?[] { TimeSpan.FromMilliseconds(3), DispatcherPriority.Normal }; + yield return new object?[] { TimeSpan.FromMilliseconds(4), DispatcherPriority.Render }; + yield return new object?[] { TimeSpan.FromMilliseconds(5), DispatcherPriority.Send }; + yield return new object?[] { TimeSpan.FromMilliseconds(6), DispatcherPriority.SystemIdle }; + } + + [Theory] + [MemberData(nameof(Ctor_TimeSpan_DispatcherPriority_EventHandler_Dispatcher_TestData))] + public void Ctor_TimeSpan_DispatcherPriority_EventHandler_Dispatcher(TimeSpan interval, DispatcherPriority priority) + { + EventHandler callback = (s, e) => {}; + var timer = new DispatcherTimer(interval, priority, callback, Dispatcher.CurrentDispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, timer.Dispatcher); + Assert.Equal(interval, timer.Interval); + Assert.True(timer.IsEnabled); + Assert.Null(timer.Tag); + } + + [Theory] + [InlineData(DispatcherPriority.Invalid)] + [InlineData(DispatcherPriority.Invalid - 1)] + [InlineData(DispatcherPriority.Send + 1)] + public void Ctor_InvalidPriority_ThrowsInvalidEnumArgumentException(DispatcherPriority priority) + { + Assert.Throws("priority", () => new DispatcherTimer(priority)); + Assert.Throws("priority", () => new DispatcherTimer(priority, Dispatcher.CurrentDispatcher)); + Assert.Throws("priority", () => new DispatcherTimer(TimeSpan.Zero, priority, (s, e) => {}, Dispatcher.CurrentDispatcher)); + } + + [Fact] + public void Ctor_InactivePriority_ThrowsArgumentException() + { + Assert.Throws("priority", () => new DispatcherTimer(DispatcherPriority.Inactive)); + Assert.Throws("priority", () => new DispatcherTimer(DispatcherPriority.Inactive, Dispatcher.CurrentDispatcher)); + Assert.Throws("priority", () => new DispatcherTimer(TimeSpan.Zero, DispatcherPriority.Inactive, (s, e) => {}, Dispatcher.CurrentDispatcher)); + } + + [Fact] + public void Ctor_NullDispatcher_ThrowsArgumentNullException() + { + Assert.Throws("dispatcher", () => new DispatcherTimer(DispatcherPriority.Normal, null)); + Assert.Throws("dispatcher", () => new DispatcherTimer(TimeSpan.Zero, DispatcherPriority.Normal, (s, e) => {}, null)); + } + + public static IEnumerable InvalidInterval_TestData() + { + yield return new object[] { TimeSpan.FromTicks(-1) }; + yield return new object[] { TimeSpan.FromMilliseconds((double)int.MaxValue + 1) }; + yield return new object[] { TimeSpan.MinValue }; + yield return new object[] { TimeSpan.MaxValue }; + } + + [Theory] + [MemberData(nameof(InvalidInterval_TestData))] + public void Ctor_InvalidInterval_ThrowsArgumentOutOfRangeException(TimeSpan interval) + { + Assert.Throws("interval", () => new DispatcherTimer(interval, DispatcherPriority.Normal, (s, e) => {}, Dispatcher.CurrentDispatcher)); + } + + [Fact] + public void Ctor_NullCallback_ThrowsArgumentNullException() + { + Assert.Throws("callback", () => new DispatcherTimer(TimeSpan.Zero, DispatcherPriority.Normal, null, Dispatcher.CurrentDispatcher)); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void IsEnabled_Set_GetReturnsExpected(bool value) + { + var timer = new DispatcherTimer + { + IsEnabled = value + }; + Assert.Equal(value, timer.IsEnabled); + + // Set same. + timer.IsEnabled = value; + Assert.Equal(value, timer.IsEnabled); + + // Set different. + timer.IsEnabled = !value; + Assert.Equal(!value, timer.IsEnabled); + } + + public static IEnumerable Interval_Set_TestData() + { + yield return new object[] { TimeSpan.Zero }; + yield return new object[] { TimeSpan.FromMilliseconds(1) }; + yield return new object[] { TimeSpan.FromMilliseconds(int.MaxValue) }; + } + + [Theory] + [MemberData(nameof(Interval_Set_TestData))] + public void Interval_Set_GetReturnsExpected(TimeSpan value) + { + var timer = new DispatcherTimer + { + Interval = value + }; + Assert.Equal(value, timer.Interval); + + // Set same. + timer.Interval = value; + Assert.Equal(value, timer.Interval); + } + + [Theory] + [MemberData(nameof(Interval_Set_TestData))] + public void Interval_SetEnabled_GetReturnsExpected(TimeSpan value) + { + var timer = new DispatcherTimer(); + timer.IsEnabled = true; + + // Set. + timer.Interval = value; + Assert.Equal(value, timer.Interval); + + // Set same. + timer.Interval = value; + Assert.Equal(value, timer.Interval); + } + + [Theory] + [MemberData(nameof(InvalidInterval_TestData))] + public void Interval_SetInvalid_ThrowsArgumentOutOfRangeException(TimeSpan value) + { + var timer = new DispatcherTimer(); + Assert.Throws("value", () => timer.Interval = value); + } + + public static IEnumerable Tag_Set_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new object() }; + } + + [Theory] + [MemberData(nameof(Tag_Set_TestData))] + public void Tag_Set_GetReturnsExpected(object? value) + { + var timer = new DispatcherTimer(); + + // Set. + timer.Tag = value; + Assert.Same(value, timer.Tag); + + // Set same. + timer.Tag = value; + Assert.Same(value, timer.Tag); + } + + [Fact] + public void Tick_AddRemove_Success() + { + var timer = new DispatcherTimer(); + + int callCount = 0; + EventHandler handler = (s, e) => callCount++; + timer.Tick += handler; + Assert.Equal(0, callCount); + + timer.Tick -= handler; + Assert.Equal(0, callCount); + + // Remove non existent. + timer.Tick -= handler; + Assert.Equal(0, callCount); + + // Add null. + timer.Tick += null; + Assert.Equal(0, callCount); + + // Remove null. + timer.Tick -= null; + Assert.Equal(0, callCount); + } + + [Fact] + public void Start_Invoke_Success() + { + var timer = new DispatcherTimer(); + int callCount = 0; + timer.Tick += (s, e) => callCount++; + + // Start. + timer.Start(); + Assert.True(timer.IsEnabled); + Assert.Equal(0, callCount); + + // Start again. + timer.Start(); + Assert.True(timer.IsEnabled); + Assert.Equal(0, callCount); + } + + [Fact] + public void Stop_InvokeStarted_Success() + { + var timer = new DispatcherTimer(); + int callCount = 0; + timer.Tick += (s, e) => callCount++; + + // Start. + timer.Start(); + Assert.True(timer.IsEnabled); + Assert.Equal(0, callCount); + + // Stop. + timer.Stop(); + Assert.False(timer.IsEnabled); + Assert.Equal(0, callCount); + + // Stop again. + timer.Stop(); + Assert.False(timer.IsEnabled); + Assert.Equal(0, callCount); + } + + [Fact] + public void Stop_InvokeNotStarted_Success() + { + var timer = new DispatcherTimer(); + int callCount = 0; + timer.Tick += (s, e) => callCount++; + + // Stop. + timer.Stop(); + Assert.False(timer.IsEnabled); + Assert.Equal(0, callCount); + + // Stop again. + timer.Stop(); + Assert.False(timer.IsEnabled); + Assert.Equal(0, callCount); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/VectorConverterTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/VectorConverterTests.cs new file mode 100644 index 00000000000..0fc0307cec4 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/VectorConverterTests.cs @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.ComponentModel.Design.Serialization; +using System.Globalization; + +namespace System.Windows.Tests; + +public class VectorConverterTests +{ + [Theory] + [InlineData(null, false)] + [InlineData(typeof(object), false)] + [InlineData(typeof(string), true)] + [InlineData(typeof(InstanceDescriptor), false)] + [InlineData(typeof(Vector), false)] + public void CanConvertTo_Invoke_ReturnsExpected(Type? destinationType, bool expected) + { + var converter = new VectorConverter(); + Assert.Equal(expected, converter.CanConvertTo(null, destinationType)); + Assert.Equal(expected, converter.CanConvertTo(new CustomTypeDescriptorContext(), destinationType)); + } + + [Theory] + [MemberData(nameof(VectorTests.ToString_TestData), MemberType = typeof(VectorTests))] + public void ConvertTo_InvokeVectorToString_ReturnsExpected(Vector matrix, string expected) + { + var converter = new VectorConverter(); + Assert.Equal(expected, converter.ConvertTo(matrix, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), null, matrix, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, matrix, typeof(string))); + } + + [Theory] + [MemberData(nameof(VectorTests.ToString_IFormatProviderCustom_TestData), MemberType = typeof(VectorTests))] + public void ConvertTo_InvokeVectorToStringCustomCulture_ReturnsExpected(Vector matrix, string numberDecimalSeparator, string expected) + { + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + var converter = new VectorConverter(); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), culture, matrix, typeof(string))); + } + + [Theory] + [InlineData(null, "")] + [InlineData("", "")] + [InlineData("value", "value")] + [InlineData(1, "1")] + public void ConvertTo_InvokeNotVectorToString_ReturnsExpected(object? value, string expected) + { + var converter = new VectorConverter(); + Assert.Equal(expected, converter.ConvertTo(value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), null, value, typeof(string))); + Assert.Equal(expected, converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, typeof(string))); + } + + public static IEnumerable ConvertTo_CantConvert_TestData() + { + yield return new object?[] { null, typeof(object) }; + yield return new object?[] { string.Empty, typeof(object) }; + yield return new object?[] { "value", typeof(object) }; + yield return new object?[] { new object(), typeof(object) }; + yield return new object?[] { new Vector(), typeof(object) }; + + yield return new object?[] { null, typeof(Vector) }; + yield return new object?[] { string.Empty, typeof(Vector) }; + yield return new object?[] { "value", typeof(Vector) }; + yield return new object?[] { new object(), typeof(Vector) }; + yield return new object?[] { new Vector(), typeof(Vector) }; + } + + [Theory] + [MemberData(nameof(ConvertTo_CantConvert_TestData))] + public void ConvertTo_CantConvert_ThrowsNotSupportedException(object value, Type destinationType) + { + var converter = new VectorConverter(); + Assert.Throws(() => converter.ConvertTo(value, destinationType)); + Assert.Throws(() => converter.ConvertTo(null, null, value, destinationType)); + Assert.Throws(() => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value, destinationType)); + } + + public static IEnumerable ConvertTo_NullDestinationType_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { string.Empty }; + yield return new object?[] { "value" }; + yield return new object?[] { new object() }; + yield return new object?[] { new Vector() }; + } + + [Theory] + [MemberData(nameof(ConvertTo_NullDestinationType_TestData))] + public void ConvertTo_NullDestinationType_ThrowsArgumentNullException(object value) + { + var converter = new VectorConverter(); + Assert.Throws("destinationType", () => converter.ConvertTo(value, null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(null, null, new Vector(), null!)); + Assert.Throws("destinationType", () => converter.ConvertTo(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, new Vector(), null!)); + } + + [Theory] + [InlineData(null, false)] + [InlineData(typeof(object), false)] + [InlineData(typeof(string), true)] + [InlineData(typeof(InstanceDescriptor), true)] + [InlineData(typeof(Vector), false)] + public void CanConvertFrom_Invoke_ReturnsExpected(Type? sourceType, bool expected) + { + var converter = new VectorConverter(); + Assert.Equal(expected, converter.CanConvertFrom(sourceType!)); + Assert.Equal(expected, converter.CanConvertFrom(null, sourceType)); + Assert.Equal(expected, converter.CanConvertFrom(new CustomTypeDescriptorContext(), sourceType)); + } + + [Theory] + [MemberData(nameof(VectorTests.Parse_TestData), MemberType = typeof(VectorTests))] + public void ConvertFrom_InvokeStringValue_ReturnsExpected(object value, Vector expected) + { + var converter = new VectorConverter(); + Assert.Equal(expected, converter.ConvertFrom(value)); + Assert.Equal(expected, converter.ConvertFrom(null, null, value)); + Assert.Equal(expected, converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Fact] + public void ConvertFrom_NullValue_ThrowsNotSupportedException() + { + var converter = new VectorConverter(); + Assert.Throws(() => converter.ConvertFrom(null!)); + Assert.Throws(() => converter.ConvertFrom(null, null, null)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, null)); + } + + [Theory] + [MemberData(nameof(VectorTests.Parse_InvalidSource_TestData), MemberType = typeof(VectorTests))] + public void ConvertFrom_InvalidStringValue_ThrowsInvalidOperationException(object value) + { + var converter = new VectorConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + [Theory] + [MemberData(nameof(VectorTests.Parse_NotDouble_TestData), MemberType = typeof(VectorTests))] + public void ConvertFrom_NotDoubleStringValue_ThrowsFormatException(object value) + { + var converter = new VectorConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + public static IEnumerable ConvertFrom_CantConvert_TestData() + { + yield return new object[] { new object() }; + yield return new object[] { new Vector() }; + } + + [Theory] + [MemberData(nameof(ConvertFrom_CantConvert_TestData))] + public void ConvertFrom_CantConvert_ThrowsNotSupportedException(object value) + { + var converter = new VectorConverter(); + Assert.Throws(() => converter.ConvertFrom(value)); + Assert.Throws(() => converter.ConvertFrom(null, null, value)); + Assert.Throws(() => converter.ConvertFrom(new CustomTypeDescriptorContext(), CultureInfo.InvariantCulture, value)); + } + + private class CustomTypeDescriptorContext : ITypeDescriptorContext + { + public IContainer Container => throw new NotImplementedException(); + + public object Instance => throw new NotImplementedException(); + + public PropertyDescriptor PropertyDescriptor => throw new NotImplementedException(); + + public object? GetService(Type serviceType) => throw new NotImplementedException(); + + public void OnComponentChanged() => throw new NotImplementedException(); + + public bool OnComponentChanging() => throw new NotImplementedException(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/VectorTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/VectorTests.cs new file mode 100644 index 00000000000..42f945d64fe --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/VectorTests.cs @@ -0,0 +1,861 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; +using System.Globalization; +using System.Windows.Converters; +using System.Windows.Markup; +using System.Windows.Media; +using System.Windows.Media.Tests; + +namespace System.Windows.Tests; + +public class VectorTests +{ + [Fact] + public void Ctor_Default() + { + var vector = new Vector(); + Assert.Equal(0, vector.X); + Assert.Equal(0, vector.Y); + } + + [Theory] + [InlineData(double.NegativeInfinity, double.NegativeInfinity)] + [InlineData(double.MinValue, double.MinValue)] + [InlineData(-1, -2)] + [InlineData(-1, 2)] + [InlineData(1, -2)] + [InlineData(double.NegativeZero, double.NegativeZero)] + [InlineData(0, 0)] + [InlineData(1, 0)] + [InlineData(0, 2)] + [InlineData(0.1, 0.2)] + [InlineData(1, 2)] + [InlineData(double.MaxValue, double.MaxValue)] + [InlineData(double.PositiveInfinity, double.PositiveInfinity)] + [InlineData(double.NaN, double.NaN)] + [InlineData(1, double.NaN)] + [InlineData(double.NaN, 2)] + public void Ctor_Double_Double(double x, double y) + { + var vector = new Vector(x, y); + Assert.Equal(x, vector.X); + Assert.Equal(y, vector.Y); + } + + public static IEnumerable Length_TestData() + { + yield return new object[] { 0, 0, 0 }; + yield return new object[] { 1, 2, Math.Sqrt(5) }; + yield return new object[] { 1.1, 2.2, Math.Sqrt(6.05) }; + yield return new object[] { -1.1, -2.2, Math.Sqrt(6.05) }; + yield return new object[] { double.MaxValue, double.MaxValue, Math.Sqrt(double.PositiveInfinity) }; + } + + [Theory] + [MemberData(nameof(Length_TestData))] + public void Length_Get_ReturnsExpected(double x, double y, double expected) + { + var vector = new Vector(x, y); + Assert.Equal(expected, vector.Length, precision: 5); + Assert.Equal(Math.Sqrt(vector.LengthSquared), vector.Length, precision: 5); + } + + [Theory] + [MemberData(nameof(Length_TestData))] + public void LengthSquared_Get_ReturnsExpected(double x, double y, double expected) + { + var vector = new Vector(x, y); + Assert.Equal(expected * expected, vector.LengthSquared, precision: 5); + Assert.Equal(vector.Length * vector.Length, vector.LengthSquared, precision: 5); + } + + [Theory] + [InlineData(double.NegativeInfinity)] + [InlineData(double.MinValue)] + [InlineData(-1)] + [InlineData(double.NegativeZero)] + [InlineData(0)] + [InlineData(0.2)] + [InlineData(1)] + [InlineData(double.MaxValue)] + [InlineData(double.PositiveInfinity)] + [InlineData(double.NaN)] + public void X_Set_GetReturnsExpected(double value) + { + var vector = new Vector(); + + // Set. + vector.X = value; + Assert.Equal(value, vector.X); + + // Set same. + vector.X = value; + Assert.Equal(value, vector.X); + } + + [Theory] + [InlineData(double.NegativeInfinity)] + [InlineData(double.MinValue)] + [InlineData(-1)] + [InlineData(double.NegativeZero)] + [InlineData(0)] + [InlineData(0.2)] + [InlineData(1)] + [InlineData(double.MaxValue)] + [InlineData(double.PositiveInfinity)] + [InlineData(double.NaN)] + public void Y_Set_GetReturnsExpected(double value) + { + var vector = new Vector(); + + // Set. + vector.Y = value; + Assert.Equal(value, vector.Y); + + // Set same. + vector.Y = value; + Assert.Equal(value, vector.Y); + } + + public static IEnumerable AngleBetween_TestData() + { + yield return new object[] { new Vector(1, 2), new Vector(1, 2), 0 }; + yield return new object[] { new Vector(1, 2), new Vector(3, 4), -10.30485 }; + yield return new object[] { new Vector(1, 2), new Vector(-3, -4), 169.69515 }; + yield return new object[] { new Vector(1, 2), new Vector(1, 0), -63.43495 }; + yield return new object[] { new Vector(1, 2), new Vector(0, 1), 26.56505 }; + yield return new object[] { new Vector(1, 2), new Vector(0, -1), -153.43495 }; + yield return new object[] { new Vector(1, 2), new Vector(-1, 0), 116.56505 }; + yield return new object[] { new Vector(1, 2), new Vector(-1, -2), 180 }; + yield return new object[] { new Vector(1, 2), new Vector(1, -2), -126.8699 }; + yield return new object[] { new Vector(1, 2), new Vector(), 0 }; + yield return new object[] { new Vector(-1, -2), new Vector(-3, -4), -10.30485 }; + + yield return new object[] { new Vector(), new Vector(), 0 }; + yield return new object[] { new Vector(), new Vector(1, 2), 0 }; + } + + [Theory] + [MemberData(nameof(AngleBetween_TestData))] + public void AngleBetween_Invoke_ReturnsExpected(Vector vector1, Vector vector2, double expected) + { + Assert.Equal(expected, Vector.AngleBetween(vector1, vector2), precision: 5); + } + + public static IEnumerable CrossProduct_TestData() + { + yield return new object[] { new Vector(1, 2), new Vector(3, 4), -2 }; + yield return new object[] { new Vector(1.1, 2.2), new Vector(3.3, 4.4), -2.42 }; + yield return new object[] { new Vector(1, 2), new Vector(-3, -4), 2 }; + yield return new object[] { new Vector(1, 2), new Vector(1, 2), 0 }; + yield return new object[] { new Vector(1, 2), new Vector(0, 0), 0 }; + yield return new object[] { new Vector(1, 2), new Vector(double.MaxValue, double.MaxValue), double.NegativeInfinity }; + yield return new object[] { new Vector(1, 2), new Vector(double.PositiveInfinity, double.PositiveInfinity), double.NaN }; + yield return new object[] { new Vector(1, 2), new Vector(double.NaN, double.NaN), double.NaN }; + yield return new object[] { new Vector(-1, -2), new Vector(-3, -4), -2 }; + + yield return new object[] { new Vector(), new Vector(), 0 }; + yield return new object[] { new Vector(), new Vector(1, 2), 0 }; + } + + [Theory] + [MemberData(nameof(CrossProduct_TestData))] + public void CrossProduct_Invoke_ReturnsExpected(Vector vector1, Vector vector2, double expected) + { + Assert.Equal(expected, Vector.CrossProduct(vector1, vector2), precision: 5); + Assert.Equal(-expected, Vector.CrossProduct(vector2, vector1), precision: 5); + } + + public static IEnumerable Equals_TestData() + { + // Normal size. + yield return new object?[] { new Vector(1, 2), new Vector(1, 2), true }; + yield return new object?[] { new Vector(1, 2), new Vector(2, 2), false }; + yield return new object?[] { new Vector(1, 2), new Vector(1, 3), false }; + yield return new object?[] { new Vector(1, 2), new Vector(double.NaN, 2), false }; + yield return new object?[] { new Vector(1, 2), new Vector(1, double.NaN), false }; + yield return new object?[] { new Vector(1, 2), new Vector(double.NaN, double.NaN), false }; + yield return new object?[] { new Vector(1, 2), new Vector(0, 0), false }; + yield return new object?[] { new Vector(1, 2), new Vector(), false }; + + // NaN x. + yield return new object[] { new Vector(double.NaN, 2), new Vector(double.NaN, 2), true }; + yield return new object[] { new Vector(double.NaN, 2), new Vector(1, 2), false }; + yield return new object[] { new Vector(double.NaN, 2), new Vector(double.NaN, double.NaN), false }; + + // NaN y. + yield return new object[] { new Vector(1, double.NaN), new Vector(1, double.NaN), true }; + yield return new object[] { new Vector(1, double.NaN), new Vector(1, 2), false }; + yield return new object[] { new Vector(1, double.NaN), new Vector(double.NaN, double.NaN), false }; + + // NaN x & y. + yield return new object[] { new Vector(double.NaN, double.NaN), new Vector(double.NaN, double.NaN), true }; + yield return new object[] { new Vector(double.NaN, double.NaN), new Vector(1, 2), false }; + yield return new object[] { new Vector(double.NaN, double.NaN), new Vector(double.NaN, 2), false }; + yield return new object[] { new Vector(double.NaN, double.NaN), new Vector(1, double.NaN), false }; + + // Zero. + yield return new object?[] { new Vector(0, 0), new Vector(), true }; + yield return new object?[] { new Vector(0, 0), new Vector(0, 0), true }; + yield return new object?[] { new Vector(0, 0), new Vector(1, 0), false }; + yield return new object?[] { new Vector(0, 0), new Vector(0, 1), false }; + + // Default. + yield return new object?[] { new Vector(), new Vector(), true }; + yield return new object?[] { new Vector(), new Vector(0, 0), true }; + yield return new object?[] { new Vector(), new Vector(1, 0), false }; + yield return new object?[] { new Vector(), new Vector(0, 1), false }; + } + + [Theory] + [MemberData(nameof(Equals_TestData))] + public void Equals_Object_ReturnsExpected(Vector vector, object o, bool expected) + { + Assert.Equal(expected, vector.Equals(o)); + if (o is Vector other) + { + Assert.Equal(expected, vector.Equals(other)); + Assert.Equal(expected, other.Equals(vector)); + Assert.Equal(expected, Vector.Equals(vector, other)); + Assert.Equal(expected, Vector.Equals(other, vector)); + Assert.Equal(expected, vector.GetHashCode().Equals(other.GetHashCode())); + } + } + + public static IEnumerable EqualityOperator_TestData() + { + // Normal size. + yield return new object[] { new Vector(1, 2), new Vector(1, 2), true }; + yield return new object[] { new Vector(1, 2), new Vector(2, 2), false }; + yield return new object[] { new Vector(1, 2), new Vector(1, 3), false }; + yield return new object[] { new Vector(1, 2), new Vector(double.NaN, 2), false }; + yield return new object[] { new Vector(1, 2), new Vector(1, double.NaN), false }; + yield return new object[] { new Vector(1, 2), new Vector(double.NaN, double.NaN), false }; + yield return new object[] { new Vector(1, 2), new Vector(0, 0), false }; + yield return new object[] { new Vector(1, 2), new Vector(), false }; + + // NaN x. + yield return new object[] { new Vector(double.NaN, 2), new Vector(double.NaN, 2), false }; + yield return new object[] { new Vector(double.NaN, 2), new Vector(1, 2), false }; + yield return new object[] { new Vector(double.NaN, 2), new Vector(double.NaN, double.NaN), false }; + + // NaN y. + yield return new object[] { new Vector(1, double.NaN), new Vector(1, double.NaN), false }; + yield return new object[] { new Vector(1, double.NaN), new Vector(1, 2), false }; + yield return new object[] { new Vector(1, double.NaN), new Vector(double.NaN, double.NaN), false }; + + // NaN x & y. + yield return new object[] { new Vector(double.NaN, double.NaN), new Vector(double.NaN, double.NaN), false }; + yield return new object[] { new Vector(double.NaN, double.NaN), new Vector(1, 2), false }; + yield return new object[] { new Vector(double.NaN, double.NaN), new Vector(double.NaN, 2), false }; + yield return new object[] { new Vector(double.NaN, double.NaN), new Vector(1, double.NaN), false }; + + // Zero. + yield return new object[] { new Vector(0, 0), new Vector(), true }; + yield return new object[] { new Vector(0, 0), new Vector(0, 0), true }; + yield return new object[] { new Vector(0, 0), new Vector(1, 0), false }; + yield return new object[] { new Vector(0, 0), new Vector(0, 1), false }; + + // Default. + yield return new object[] { new Vector(), new Vector(), true }; + yield return new object[] { new Vector(), new Vector(0, 0), true }; + yield return new object[] { new Vector(), new Vector(1, 0), false }; + yield return new object[] { new Vector(), new Vector(0, 1), false }; + } + + [Theory] + [MemberData(nameof(EqualityOperator_TestData))] + public void EqualityOperator_Invoke_ReturnsExpected(Vector vector1, Vector vector2, bool expected) + { + Assert.Equal(expected, vector1 == vector2); + Assert.Equal(expected, vector2 == vector1); + Assert.Equal(!expected, vector1 != vector2); + Assert.Equal(!expected, vector2 != vector1); + } + + [Fact] + public void GetHashCode_InvokeDefault_ReturnsEqual() + { + var vector = new Vector(); + Assert.Equal(0, vector.GetHashCode()); + Assert.Equal(vector.GetHashCode(), vector.GetHashCode()); + } + + [Fact] + public void GetHashCode_InvokeNormal_ReturnsEqual() + { + var vector = new Vector(1, 2); + Assert.NotEqual(0, vector.GetHashCode()); + Assert.Equal(vector.GetHashCode(), vector.GetHashCode()); + } + + [Fact] + public void Normalize_InvokeDefault_ReturnsExpected() + { + var vector = new Vector(); + vector.Normalize(); + Assert.Equal(double.NaN, vector.X); + Assert.Equal(double.NaN, vector.Y); + Assert.Equal(double.NaN, vector.Length); + Assert.Equal(double.NaN, vector.LengthSquared); + } + + [Fact] + public void Normalize_InvokeZero_ReturnsExpected() + { + var vector = new Vector(0, 0); + vector.Normalize(); + Assert.Equal(double.NaN, vector.X); + Assert.Equal(double.NaN, vector.Y); + Assert.Equal(double.NaN, vector.Length); + Assert.Equal(double.NaN, vector.LengthSquared); + } + + [Fact] + public void Normalize_InvokeNormal_ReturnsExpected() + { + var vector = new Vector(1, 2); + vector.Normalize(); + Assert.Equal(1 / Math.Sqrt(5), vector.X, precision: 5); + Assert.Equal(2 / Math.Sqrt(5), vector.Y, precision: 5); + Assert.Equal(1, vector.Length, precision: 5); + Assert.Equal(1, vector.LengthSquared, precision: 5); + } + + [Fact] + public void Normalize_InvokeNegative_ReturnsExpected() + { + var vector = new Vector(-1, -2); + vector.Normalize(); + Assert.Equal(-1 / Math.Sqrt(5), vector.X, precision: 5); + Assert.Equal(-2 / Math.Sqrt(5), vector.Y, precision: 5); + Assert.Equal(1, vector.Length, precision: 5); + Assert.Equal(1, vector.LengthSquared, precision: 5); + } + + [Fact] + public void Normalize_InvokeLarge_ReturnsExpected() + { + var vector = new Vector(double.MaxValue, double.MaxValue); + vector.Normalize(); + Assert.Equal(0.70711, vector.X, precision: 5); + Assert.Equal(0.70711, vector.Y, precision: 5); + Assert.Equal(1, vector.Length, precision: 5); + Assert.Equal(1, vector.LengthSquared, precision: 5); + } + + public static IEnumerable Add_Vector_TestData() + { + yield return new object[] { new Vector(1, 2), new Vector(3, 4), new Vector(4, 6) }; + yield return new object[] { new Vector(1, 2), new Vector(-3, -4), new Vector(-2, -2) }; + yield return new object[] { new Vector(1.1, 2.2), new Vector(3.3, 4.4), new Vector(4.4, 6.6) }; + yield return new object[] { new Vector(1, 2), new Vector(), new Vector(1, 2) }; + + yield return new object[] { new Vector(), new Vector(), new Vector() }; + yield return new object[] { new Vector(), new Vector(1, 2), new Vector(1, 2) }; + } + + [Theory] + [MemberData(nameof(Add_Vector_TestData))] + public void Add_InvokeVector_ReturnsExpected(Vector vector1, Vector vector2, Vector expected) + { + Vector result = Vector.Add(vector1, vector2); + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + Assert.Equal(result, Vector.Add(vector2, vector1)); + } + + [Theory] + [MemberData(nameof(Add_Vector_TestData))] + public void OperatorAdd_InvokeVector_ReturnsExpected(Vector vector1, Vector vector2, Vector expected) + { + Vector result = vector1 + vector2; + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + Assert.Equal(result, vector2 + vector1); + } + + public static IEnumerable Add_Point_TestData() + { + yield return new object[] { new Vector(1, 2), new Point(3, 4), new Point(4, 6) }; + yield return new object[] { new Vector(1, 2), new Point(-3, -4), new Point(-2, -2) }; + yield return new object[] { new Vector(1.1, 2.2), new Point(3.3, 4.4), new Point(4.4, 6.6) }; + yield return new object[] { new Vector(1, 2), new Point(), new Point(1, 2) }; + + yield return new object[] { new Vector(), new Point(), new Point() }; + yield return new object[] { new Vector(), new Point(1, 2), new Point(1, 2) }; + } + + [Theory] + [MemberData(nameof(Add_Point_TestData))] + public void Add_InvokePoint_ReturnsExpected(Vector vector, Point point, Point expected) + { + Point result = Vector.Add(vector, point); + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + } + + [Theory] + [MemberData(nameof(Add_Point_TestData))] + public void OperatorAdd_InvokePoint_ReturnsExpected(Vector vector, Point point, Point expected) + { + Point result = vector + point; + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + } + + public static IEnumerable Determinant_TestData() + { + yield return new object[] { new Vector(1, 2), new Vector(3, 4), -2 }; + yield return new object[] { new Vector(1, 2), new Vector(-3, -4), 2 }; + yield return new object[] { new Vector(1.1, 2.2), new Vector(3.3, 4.4), -2.42 }; + yield return new object[] { new Vector(1, 2), new Vector(), 0 }; + + yield return new object[] { new Vector(-1, -2), new Vector(3, 4), 2 }; + yield return new object[] { new Vector(-1, -2), new Vector(-3, -4), -2 }; + yield return new object[] { new Vector(-1, -2), new Vector(), 0 }; + + yield return new object[] { new Vector(), new Vector(), 0 }; + yield return new object[] { new Vector(), new Vector(1, 2), 0 }; + yield return new object[] { new Vector(), new Vector(-1, -2), 0 }; + } + + [Theory] + [MemberData(nameof(Determinant_TestData))] + public void Determinant_Invoke_ReturnsExpected(Vector vector1, Vector vector2, double expected) + { + Assert.Equal(expected, Vector.Determinant(vector1, vector2), precision: 5); + } + + public static IEnumerable Divide_TestData() + { + yield return new object[] { new Vector(1, 2), 2, new Vector(0.5, 1) }; + yield return new object[] { new Vector(1, 2), 1, new Vector(1, 2) }; + yield return new object[] { new Vector(1, 2), -2, new Vector(-0.5, -1) }; + yield return new object[] { new Vector(1.1, 2.2), 2, new Vector(0.55, 1.1) }; + yield return new object[] { new Vector(1, 2), 0, new Vector(double.PositiveInfinity, double.PositiveInfinity) }; + + yield return new object[] { new Vector(-1, -2), 2, new Vector(-0.5, -1) }; + yield return new object[] { new Vector(-1, -2), 1, new Vector(-1, -2) }; + yield return new object[] { new Vector(-1, -2), -2, new Vector(0.5, 1) }; + + yield return new object[] { new Vector(), 2, new Vector() }; + yield return new object[] { new Vector(), 1, new Vector() }; + yield return new object[] { new Vector(), -2, new Vector() }; + yield return new object[] { new Vector(), 0, new Vector(double.NaN, double.NaN) }; + } + + [Theory] + [MemberData(nameof(Divide_TestData))] + public void Divide_Invoke_ReturnsExpected(Vector vector, double scalar, Vector expected) + { + Vector result = Vector.Divide(vector, scalar); + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + } + + [Theory] + [MemberData(nameof(Divide_TestData))] + public void OperatorDivide_Invoke_ReturnsExpected(Vector vector, double scalar, Vector expected) + { + Vector result = vector / scalar; + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + } + + public static IEnumerable Subtract_Vector_TestData() + { + yield return new object[] { new Vector(1, 2), new Vector(3, 4), new Vector(-2, -2) }; + yield return new object[] { new Vector(1, 2), new Vector(-3, -4), new Vector(4, 6) }; + yield return new object[] { new Vector(1.1, 2.2), new Vector(3.3, 4.4), new Vector(-2.2, -2.2) }; + yield return new object[] { new Vector(1, 2), new Vector(), new Vector(1, 2) }; + + yield return new object[] { new Vector(), new Vector(1, 2), new Vector(-1, -2) }; + yield return new object[] { new Vector(), new Vector(), new Vector() }; + } + + [Theory] + [MemberData(nameof(Subtract_Vector_TestData))] + public void Subtract_InvokeVector_ReturnsExpected(Vector vector1, Vector vector2, Vector expected) + { + Vector result = Vector.Subtract(vector1, vector2); + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + Assert.Equal(new Vector(-result.X, -result.Y), Vector.Subtract(vector2, vector1)); + } + + [Theory] + [MemberData(nameof(Subtract_Vector_TestData))] + public void OperatorSubtract_InvokeVector_ReturnsExpected(Vector vector1, Vector vector2, Vector expected) + { + Vector result = vector1 - vector2; + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + Assert.Equal(new Vector(-result.X, -result.Y), vector2 - vector1); + } + + public static IEnumerable Multiply_Double_TestData() + { + yield return new object[] { new Vector(1, 2), 2, new Vector(2, 4) }; + yield return new object[] { new Vector(1, 2), -2, new Vector(-2, -4) }; + yield return new object[] { new Vector(1.1, 2.2), 2, new Vector(2.2, 4.4) }; + yield return new object[] { new Vector(1, 2), 0, new Vector(0, 0) }; + yield return new object[] { new Vector(1, 2), 1, new Vector(1, 2) }; + yield return new object[] { new Vector(1, 2), double.PositiveInfinity, new Vector(double.PositiveInfinity, double.PositiveInfinity) }; + + yield return new object[] { new Vector(-1, -2), 2, new Vector(-2, -4) }; + yield return new object[] { new Vector(-1, -2), -2, new Vector(2, 4) }; + yield return new object[] { new Vector(-1, -2), 0, new Vector(0, 0) }; + yield return new object[] { new Vector(-1, -2), 1, new Vector(-1, -2) }; + + yield return new object[] { new Vector(), 2, new Vector() }; + yield return new object[] { new Vector(), -2, new Vector() }; + yield return new object[] { new Vector(), 0, new Vector() }; + yield return new object[] { new Vector(), 1, new Vector() }; + } + + [Theory] + [MemberData(nameof(Multiply_Double_TestData))] + public void Multiply_InvokeDouble_ReturnsExpected(Vector vector, double scalar, Vector expected) + { + Vector result = Vector.Multiply(vector, scalar); + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + Assert.Equal(result, Vector.Multiply(scalar, vector)); + } + + [Theory] + [MemberData(nameof(Multiply_Double_TestData))] + public void OperatorMultiply_InvokeDouble_ReturnsExpected(Vector vector, double scalar, Vector expected) + { + Vector result = vector * scalar; + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + Assert.Equal(result, scalar * vector); + } + + [Theory] + [MemberData(nameof(MatrixTests.Transform_Vector_TestData), MemberType = typeof(MatrixTests))] + public void Multiply_InvokeMatrix_ReturnsExpected(Matrix matrix, Vector vector, Vector expected) + { + Vector result = Vector.Multiply(vector, matrix); + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + } + + [Theory] + [MemberData(nameof(MatrixTests.Transform_Vector_TestData), MemberType = typeof(MatrixTests))] + public void OperatorMultiply_InvokeMatrix_ReturnsExpected(Matrix matrix, Vector vector, Vector expected) + { + Vector result = vector * matrix; + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + } + + public static IEnumerable Multiply_Vector_TestData() + { + yield return new object[] { new Vector(1, 2), new Vector(3, 4), 11 }; + yield return new object[] { new Vector(1, 2), new Vector(1, 2), 5 }; + yield return new object[] { new Vector(1, 2), new Vector(0, 0), 0 }; + yield return new object[] { new Vector(1, 2), new Vector(-1, -2), -5 }; + + yield return new object[] { new Vector(-1, -2), new Vector(3, 4), -11 }; + yield return new object[] { new Vector(-1, -2), new Vector(-1, -2), 5 }; + yield return new object[] { new Vector(-1, -2), new Vector(0, 0), 0 }; + + yield return new object[] { new Vector(), new Vector(3, 4), 0 }; + yield return new object[] { new Vector(), new Vector(), 0 }; + yield return new object[] { new Vector(), new Vector(-1, -2), 0 }; + } + + [Theory] + [MemberData(nameof(Multiply_Vector_TestData))] + public void Multiply_InvokeVector_ReturnsExpected(Vector vector1, Vector vector2, double expected) + { + double result = Vector.Multiply(vector1, vector2); + Assert.Equal(expected, result, precision: 5); + Assert.Equal(result, Vector.Multiply(vector1, vector2)); + } + + [Theory] + [MemberData(nameof(Multiply_Vector_TestData))] + public void OperatorMultiply_InvokeVector_ReturnsExpected(Vector vector1, Vector vector2, double expected) + { + double result = vector1 * vector2; + Assert.Equal(expected, result, precision: 5); + Assert.Equal(result, vector2 * vector1); + } + + [Fact] + public void Negate_InvokeEmpty_Success() + { + var vector = new Vector(); + vector.Negate(); + Assert.Equal(0, vector.X); + Assert.Equal(0, vector.Y); + } + + [Fact] + public void Negate_InvokeNormal_Success() + { + var vector = new Vector(1, 2); + vector.Negate(); + Assert.Equal(-1, vector.X); + Assert.Equal(-2, vector.Y); + } + + [Fact] + public void OperatorNegate_InvokeEmpty_Success() + { + var vector = new Vector(); + Vector result = -vector; + Assert.Equal(0, result.X); + Assert.Equal(0, result.Y); + } + + [Fact] + public void OperatorNegate_InvokeNormal_Success() + { + var vector = new Vector(1, 2); + Vector result = -vector; + Assert.Equal(-1, result.X); + Assert.Equal(-2, result.Y); + } + + public static IEnumerable Parse_TestData() + { + yield return new object[] { "0,0", new Vector(0, 0) }; + yield return new object[] { "1,2", new Vector(1, 2) }; + yield return new object[] { "1.1,2.2", new Vector(1.1, 2.2) }; + yield return new object[] { " 1 , 2 ", new Vector(1, 2) }; + yield return new object[] { "-1,-2", new Vector(-1, -2) }; + } + + [Theory] + [MemberData(nameof(Parse_TestData))] + public void Parse_Invoke_Success(string source, Vector expected) + { + Vector result = Vector.Parse(source); + Assert.Equal(expected.X, result.X, precision: 5); + Assert.Equal(expected.Y, result.Y, precision: 5); + } + + [Fact] + public void Parse_NullSource_ThrowsInvalidOperationException() + { + Assert.Throws(() => Vector.Parse(null)); + } + + public static IEnumerable Parse_InvalidSource_TestData() + { + yield return new object[] { "" }; + yield return new object[] { " " }; + yield return new object[] { "," }; + yield return new object[] { "1" }; + yield return new object[] { "1," }; + yield return new object[] { "1,2," }; + yield return new object[] { "1,2,3" }; + yield return new object[] { "1,2,test" }; + yield return new object[] { "Empty," }; + yield return new object[] { "Identity," }; + } + + [Theory] + [MemberData(nameof(Parse_InvalidSource_TestData))] + public void Parse_InvalidSource_ThrowsInvalidOperationException(string source) + { + Assert.Throws(() => Vector.Parse(source)); + } + + public static IEnumerable Parse_NotDouble_TestData() + { + yield return new object[] { "Empty" }; + yield return new object[] { " Empty " }; + yield return new object[] { "Empty,2" }; + yield return new object[] { "Identity" }; + yield return new object[] { " Identity " }; + yield return new object[] { "Identity,2" }; + yield return new object[] { "test" }; + yield return new object[] { "test,2" }; + yield return new object[] { "1,test" }; + yield return new object[] { "1,test,3" }; + yield return new object[] { "1;2" }; + yield return new object[] { "1.2.3" }; + yield return new object[] { """1"",""2""" }; + } + + [Theory] + [MemberData(nameof(Parse_NotDouble_TestData))] + public void Parse_NotDouble_ThrowsFormatException(string source) + { + Assert.Throws(() => Vector.Parse(source)); + } + + public static IEnumerable ToString_TestData() + { + yield return new object[] { new Vector(), "0,0" }; + yield return new object[] { new Vector(0, 0), "0,0" }; + yield return new object[] { new Vector(1, 2), "1,2" }; + yield return new object[] { new Vector(1.1, 2.2), "1.1,2.2" }; + yield return new object[] { new Vector(-1, -2), "-1,-2" }; + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_Invoke_ReturnsExpected(Vector vector, string expected) + { + Assert.Equal(expected, vector.ToString()); + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_InvokeInvariantCulture_ReturnsExpected(Vector vector, string expected) + { + Assert.Equal(expected, vector.ToString(CultureInfo.InvariantCulture)); + } + + public static IEnumerable ToString_IFormatProviderCustom_TestData() + { + yield return new object[] { new Vector(), "|", "0,0" }; + yield return new object[] { new Vector(), "|_", "0,0" }; + yield return new object[] { new Vector(), ",_", "0;0" }; + yield return new object[] { new Vector(), ",", "0;0" }; + yield return new object[] { new Vector(), ";", "0,0" }; + yield return new object[] { new Vector(), " ", "0,0" }; + + yield return new object[] { new Vector(0, 0), "|", "0,0" }; + yield return new object[] { new Vector(0, 0), "|_", "0,0" }; + yield return new object[] { new Vector(0, 0), ",_", "0;0" }; + yield return new object[] { new Vector(0, 0), ",", "0;0" }; + yield return new object[] { new Vector(0, 0), ";", "0,0" }; + yield return new object[] { new Vector(0, 0), " ", "0,0" }; + + yield return new object[] { new Vector(1, 2), "|", "1,2" }; + yield return new object[] { new Vector(1, 2), "|_", "1,2" }; + yield return new object[] { new Vector(1, 2), ",_", "1;2" }; + yield return new object[] { new Vector(1, 2), ",", "1;2" }; + yield return new object[] { new Vector(1, 2), ";", "1,2" }; + yield return new object[] { new Vector(1, 2), " ", "1,2" }; + + yield return new object[] { new Vector(1.1, 2.2), "|", "1|1,2|2" }; + yield return new object[] { new Vector(1.1, 2.2), "|_", "1|_1,2|_2" }; + yield return new object[] { new Vector(1.1, 2.2), ",_", "1,_1;2,_2" }; + yield return new object[] { new Vector(1.1, 2.2), ",", "1,1;2,2" }; + yield return new object[] { new Vector(1.1, 2.2), ";", "1;1,2;2" }; + yield return new object[] { new Vector(1.1, 2.2), " ", "1 1,2 2" }; + + yield return new object[] { new Vector(-1, -2), "|", "-1,-2" }; + yield return new object[] { new Vector(-1, -2), "|_", "-1,-2" }; + yield return new object[] { new Vector(-1, -2), ",_", "-1;-2" }; + yield return new object[] { new Vector(-1, -2), ",", "-1;-2" }; + yield return new object[] { new Vector(-1, -2), ";", "-1,-2" }; + yield return new object[] { new Vector(-1, -2), " ", "-1,-2" }; + } + + [Theory] + [MemberData(nameof(ToString_IFormatProviderCustom_TestData))] + public void ToString_InvokeIFormatProviderCustom_ReturnsExpected(Vector vector, string numberDecimalSeparator, string expected) + { + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + Assert.Equal(expected, vector.ToString(formatInfo)); + } + + [Theory] + [MemberData(nameof(ToString_TestData))] + public void ToString_InvokeIFormattableInvariantCulture_ReturnsExpected(Vector vector, string expected) + { + IFormattable formattable = vector; + + Assert.Equal(expected, formattable.ToString(null, null)); + Assert.Equal(expected, formattable.ToString(null, CultureInfo.InvariantCulture)); + } + + [Theory] + [InlineData("|", "1|23,2|35")] + [InlineData("|_", "1|_23,2|_35")] + [InlineData(",_", "1,_23;2,_35")] + [InlineData(",", "1,23;2,35")] + [InlineData(";", "1;23,2;35")] + [InlineData(" ", "1 23,2 35")] + public void ToString_InvokeIFormattableCustomFormat_ReturnsExpected(string numberDecimalSeparator, string expected) + { + var vector = new Vector(1.23456, 2.34567); + IFormattable formattable = vector; + + var culture = new CultureInfo("en-US"); + NumberFormatInfo formatInfo = culture.NumberFormat; + formatInfo.NumberDecimalSeparator = numberDecimalSeparator; + + Assert.Equal(expected, formattable.ToString("F2", formatInfo)); + } + + [Fact] + public void OperatorConvertPoint_InvokeEmpty_ReturnsExpected() + { + Point result = (Point)new Vector(); + Assert.Equal(0, result.X); + Assert.Equal(0, result.Y); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(1, 0)] + [InlineData(0, 1)] + [InlineData(1, 1)] + [InlineData(1, 2)] + [InlineData(1.1, 2.2)] + [InlineData(double.NaN, double.NaN)] + [InlineData(double.NaN, 0)] + [InlineData(0, double.NaN)] + [InlineData(-1, -2)] + public void OperatorConvertPoint_InvokeNormal_ReturnsExpected(double x, double y) + { + Point result = (Point)new Vector(x, y); + Assert.Equal(x, result.X); + Assert.Equal(y, result.Y); + } + + [Fact] + public void OperatorConvertSize_InvokeEmpty_ReturnsExpected() + { + Size result = (Size)new Vector(); + Assert.Equal(0, result.Width); + Assert.Equal(0, result.Height); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(1, 0, 1, 0)] + [InlineData(0, 1, 0, 1)] + [InlineData(1, 1, 1, 1)] + [InlineData(1, 2, 1, 2)] + [InlineData(1.1, 2.2, 1.1, 2.2)] + [InlineData(double.NaN, double.NaN, double.NaN, double.NaN)] + [InlineData(double.NaN, 0, double.NaN, 0)] + [InlineData(0, double.NaN, 0, double.NaN)] + [InlineData(-1, -2, 1, 2)] + public void OperatorConvertSize_InvokeNormal_ReturnsExpected(double x, double y, double expectedWidth, double expectedHeight) + { + Size result = (Size)new Vector(x, y); + Assert.Equal(expectedWidth, result.Width); + Assert.Equal(expectedHeight, result.Height); + } + + [Fact] + public void TypeConverter_Get_ReturnsExpected() + { + Assert.IsType(TypeDescriptor.GetConverter(typeof(Vector))); + } + + [Fact] + public void ValueSerializer_Get_ReturnsExpected() + { + Assert.IsType(ValueSerializer.GetSerializerFor(typeof(Vector))); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/WeakEventManagerTTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/WeakEventManagerTTests.cs new file mode 100644 index 00000000000..f25b355fcf3 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/WeakEventManagerTTests.cs @@ -0,0 +1,412 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; + +namespace System.Windows.Tests; + +public class WeakEventManagerTTests +{ + public static IEnumerable AddHandler_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new EventArgs() }; + yield return new object?[] { EventArgs.Empty }; + } + + [Theory] + [MemberData(nameof(AddHandler_TestData))] + public void AddHandler_Invoke_Success(EventArgs e) + { + var source1 = new ClassWithEvents(); + var source2 = new ClassWithEvents(); + int callCount1 = 0; + int callCount2 = 0; + EventHandler handler1 = (sender, actualE) => + { + Assert.Same(source1, sender); + Assert.Same(e, actualE); + callCount1++; + }; + EventHandler handler2 = (sender, actualE) => + { + Assert.Same(source1, sender); + Assert.Same(e, actualE); + callCount2++; + }; + + // Add. + WeakEventManager.AddHandler(source1, nameof(ClassWithEvents.CustomEvent), handler1); + + // Call. + source1.OnCustomEvent(source1, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Call invalid source1. + source1.OnCustomEvent(source2, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source1.OnCustomEvent(new object(), e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + source1.OnCustomEvent(null!, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Add again. + WeakEventManager.AddHandler(source1, nameof(ClassWithEvents.CustomEvent), handler1); + source1.OnCustomEvent(source1, e); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + // Add another. + WeakEventManager.AddHandler(source1, nameof(ClassWithEvents.CustomEvent), handler2); + source1.OnCustomEvent(source1, e); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + + // Remove second. + WeakEventManager.RemoveHandler(source1, nameof(ClassWithEvents.CustomEvent), handler2); + source1.OnCustomEvent(source1, e); + Assert.Equal(7, callCount1); + Assert.Equal(1, callCount2); + + // Remove first. + WeakEventManager.RemoveHandler(source1, nameof(ClassWithEvents.CustomEvent), handler1); + source1.OnCustomEvent(source1, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + + // Remove again. + WeakEventManager.RemoveHandler(source1, nameof(ClassWithEvents.CustomEvent), handler1); + source1.OnCustomEvent(source1, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + } + + [Theory] + [MemberData(nameof(AddHandler_TestData))] + public void AddHandler_InvokeNoSource_Success(EventArgs e) + { + var source = new ClassWithEvents(); + int callCount1 = 0; + int callCount2 = 0; + EventHandler handler1 = (sender, actualE) => + { + Assert.Null(sender); + //Assert.Same(e, actualE); + callCount1++; + }; + EventHandler handler2 = (sender, actualE) => + { + Assert.Null(sender); + //Assert.Same(e, actualE); + callCount2++; + }; + + // Add. + WeakEventManager.AddHandler(null!, nameof(ClassWithEvents.StaticEvent1), handler1); + + // Call. + ClassWithEvents.OnStaticEvent1(null, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Call invalid source. + ClassWithEvents.OnStaticEvent1(source, e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + ClassWithEvents.OnStaticEvent1(new object(), e); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Add again. + WeakEventManager.AddHandler(null!, nameof(ClassWithEvents.StaticEvent1), handler1); + ClassWithEvents.OnStaticEvent1(null, e); + Assert.Equal(3, callCount1); + Assert.Equal(0, callCount2); + + // Add another. + WeakEventManager.AddHandler(null!, nameof(ClassWithEvents.StaticEvent1), handler2); + ClassWithEvents.OnStaticEvent1(null, e); + Assert.Equal(5, callCount1); + Assert.Equal(1, callCount2); + + // Remove second. + WeakEventManager.RemoveHandler(null!, nameof(ClassWithEvents.StaticEvent1), handler2); + ClassWithEvents.OnStaticEvent1(null, e); + Assert.Equal(7, callCount1); + Assert.Equal(1, callCount2); + + // Remove first. + WeakEventManager.RemoveHandler(null!, nameof(ClassWithEvents.StaticEvent1), handler1); + ClassWithEvents.OnStaticEvent1(null, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + + // Remove again. + WeakEventManager.RemoveHandler(null!, nameof(ClassWithEvents.StaticEvent1), handler1); + ClassWithEvents.OnStaticEvent1(null, e); + Assert.Equal(8, callCount1); + Assert.Equal(1, callCount2); + } + + [Fact] + public void AddHandler_InvokeMultipleTimes_Success() + { + var source = new ClassWithEvents(); + EventHandler handler1 = (sender, e) => { }; + EventHandler handler2 = (sender, actualE) => { }; + + // Add. + WeakEventManager.AddHandler(source, nameof(ClassWithEvents.CustomEvent), handler1); + + // Add again. + WeakEventManager.AddHandler(source, nameof(ClassWithEvents.CustomEvent), handler1); + + // Add another. + WeakEventManager.AddHandler(source, nameof(ClassWithEvents.CustomEvent), handler2); + } + + [Fact] + public void AddHandler_NullSourceNonStaticEvent_ThrowsTargetException() + { + // Add. + Assert.Throws(() => WeakEventManager.AddHandler(null!, nameof(ClassWithEvents.CustomEvent), (sender, e) => { })); + + // Add again. + WeakEventManager.AddHandler(null!, nameof(ClassWithEvents.CustomEvent), (sender, e) => { }); + } + + [Fact] + public void AddHandler_NullEventName_ThrowsArgumentNullException() + { + // TODO: should throw ArgumentNullException + //Assert.Throws("eventName", () => WeakEventManager.AddHandler(new object(), null, (sender, e) => { })); + Assert.Throws(() => WeakEventManager.AddHandler(new object(), null, (sender, e) => { })); + + // Call again to test caching. + Assert.Throws(() => WeakEventManager.AddHandler(new object(), null, (sender, e) => { })); + } + + public static IEnumerable AddHandler_NoSuchEvent_TestData() + { + yield return new object?[] { null, "" }; + yield return new object?[] { null, " " }; + yield return new object?[] { null, "eventName" }; + yield return new object?[] { new object(), "" }; + yield return new object?[] { new object(), " " }; + yield return new object?[] { new object(), "eventName" }; + yield return new object?[] { new ClassWithEvents(), nameof(ClassWithEvents.CustomEvent) }; + yield return new object?[] { new ClassWithEvents(), nameof(ClassWithEvents.StaticEvent1) }; + } + + [Theory] + [MemberData(nameof(AddHandler_NoSuchEvent_TestData))] + public void AddHandler_NoSuchEvent_ThrowsArgumentException(object source, string eventName) + { + var handler = new EventHandler((sender, e) => { }); + // TODO: this should have a paramName. + Assert.Throws(() => WeakEventManager.AddHandler(source, eventName, handler)); + + // Call again to test caching. + Assert.Throws(() => WeakEventManager.AddHandler(source, eventName, handler)); + } + + [Fact] + public void AddHandler_NullHandler_ThrowsArgumentNullException() + { + Assert.Throws("handler", () => WeakEventManager.AddHandler(new object(), "EventName", null)); + } + + [Fact] + public void RemoveHandler_Invoke_Success() + { + var source = new ClassWithEvents(); + int callCount = 0; + EventHandler handler = (sender, e) => callCount++; + + WeakEventManager.AddHandler(source, nameof(ClassWithEvents.CustomEvent), handler); + + // Remove. + WeakEventManager.RemoveHandler(source, nameof(ClassWithEvents.CustomEvent), handler); + Assert.Equal(0, callCount); + + // Call event. + source.OnCustomEvent(source, EventArgs.Empty); + Assert.Equal(0, callCount); + + // Remove again. + WeakEventManager.RemoveHandler(source, nameof(ClassWithEvents.CustomEvent), handler); + Assert.Equal(0, callCount); + + // Call event. + source.OnCustomEvent(source, EventArgs.Empty); + Assert.Equal(0, callCount); + } + + [Fact] + public void RemoveHandler_InvokeNoSource_Success() + { + int callCount = 0; + EventHandler handler = (sender, e) => callCount++; + + WeakEventManager.AddHandler(null!, nameof(ClassWithEvents.StaticEvent2), handler); + + // Remove. + WeakEventManager.RemoveHandler(null!, nameof(ClassWithEvents.StaticEvent2), handler); + Assert.Equal(0, callCount); + + // Call event. + ClassWithEvents.OnStaticEvent2(null!, EventArgs.Empty); + Assert.Equal(0, callCount); + + // Remove again. + WeakEventManager.RemoveHandler(null!, nameof(ClassWithEvents.StaticEvent2), handler); + Assert.Equal(0, callCount); + + // Call event. + ClassWithEvents.OnStaticEvent2(null!, EventArgs.Empty); + Assert.Equal(0, callCount); + } + + [Fact] + public void RemoveHandler_NoSuchSource_Success() + { + var source1 = new ClassWithEvents(); + var source2 = new ClassWithEvents(); + int callCount = 0; + EventHandler handler = (sender, e) => callCount++; + + WeakEventManager.AddHandler(source1, nameof(ClassWithEvents.CustomEvent), handler); + + // Remove. + WeakEventManager.RemoveHandler(source2, nameof(ClassWithEvents.CustomEvent), handler); + Assert.Equal(0, callCount); + + // Call event. + source1.OnCustomEvent(source1, EventArgs.Empty); + Assert.Equal(1, callCount); + + // Remove again. + WeakEventManager.RemoveHandler(source2, nameof(ClassWithEvents.CustomEvent), handler); + Assert.Equal(1, callCount); + + // Call event. + source1.OnCustomEvent(source1, EventArgs.Empty); + Assert.Equal(2, callCount); + } + + [Fact] + public void RemoveHandler_NoSuchHandler_Success() + { + var source = new ClassWithEvents(); + int callCount1 = 0; + EventHandler handler1 = (sender, e) => callCount1++; + int callCount2 = 0; + EventHandler handler2 = (sender, e) => callCount2++; + + WeakEventManager.AddHandler(source, nameof(ClassWithEvents.CustomEvent), handler1); + + // Remove. + WeakEventManager.RemoveHandler(source, nameof(ClassWithEvents.CustomEvent), handler2); + Assert.Equal(0, callCount1); + Assert.Equal(0, callCount2); + + // Call event. + source.OnCustomEvent(source, EventArgs.Empty); + Assert.Equal(1, callCount1); + Assert.Equal(0, callCount2); + + // Remove again. + WeakEventManager.RemoveHandler(source, nameof(ClassWithEvents.CustomEvent), handler2); + + // Call event. + source.OnCustomEvent(source, EventArgs.Empty); + Assert.Equal(2, callCount1); + Assert.Equal(0, callCount2); + } + + [Fact] + public void RemoveHandler_NullSourceNonStaticEvent_() + { + EventHandler handler = (sender, e) => { }; + WeakEventManager.RemoveHandler(null!, nameof(ClassWithEvents.CustomEvent), handler); + } + + [Fact] + public void RemoveHandler_NullHandler_ThrowsArgumentNullException() + { + Assert.Throws("handler", () => WeakEventManager.RemoveHandler(new object(), "EventName", null)); + } + + [Fact] + public void RemoveHandler_NullEventName_ThrowsArgumentNullException() + { + // TODO: this should throw ArgumentNullException + //Assert.Throws("eventName", () => WeakEventManager.RemoveHandler(new object(), null, (sender, e) => { })); + Assert.Throws(() => WeakEventManager.RemoveHandler(new object(), null, (sender, e) => { })); + } + + public static IEnumerable RemoveHandler_NoSuchEvent_TestData() + { + yield return new object?[] { null, "" }; + yield return new object?[] { null, " " }; + yield return new object?[] { null, "eventName" }; + yield return new object?[] { new object(), "" }; + yield return new object?[] { new object(), " " }; + yield return new object?[] { new object(), "eventName" }; + yield return new object?[] { new ClassWithEvents(), nameof(ClassWithEvents.CustomEvent) }; + yield return new object?[] { new ClassWithEvents(), nameof(ClassWithEvents.StaticEvent1) }; + } + + [Theory] + [MemberData(nameof(RemoveHandler_NoSuchEvent_TestData))] + public void RemoveHandler_NoSuchEvent_ThrowsArgumentException(object source, string eventName) + { + var handler = new EventHandler((sender, e) => { }); + // TODO: this should have a paramName. + Assert.Throws(() => WeakEventManager.RemoveHandler(source, eventName, handler)); + + // Call again to test caching. + Assert.Throws(() => WeakEventManager.RemoveHandler(source, eventName, handler)); + } + + private class ClassWithEvents + { + private EventHandler? _event; + + public event EventHandler CustomEvent + { + add => _event += value; + remove => _event -= value; + } + + public void OnCustomEvent(object sender, EventArgs e) => _event?.Invoke(sender, e); + + private static EventHandler? _staticEvent1; + + public static event EventHandler StaticEvent1 + { + add => _staticEvent1 += value; + remove => _staticEvent1 -= value; + } + + public static void OnStaticEvent1(object? sender, EventArgs e) => _staticEvent1?.Invoke(sender, e); + + private static EventHandler? _staticEvent2; + + public static event EventHandler StaticEvent2 + { + add => _staticEvent2 += value; + remove => _staticEvent2 -= value; + } + + public static void OnStaticEvent2(object? sender, EventArgs e) => _staticEvent2?.Invoke(sender, e); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/WeakEventManagerTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/WeakEventManagerTests.cs new file mode 100644 index 00000000000..fab6d3be383 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/WeakEventManagerTests.cs @@ -0,0 +1,6063 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using System.Windows.Threading; + +namespace System.Windows.Tests; + +public class WeakEventManagerTests : WeakEventManager +{ + // TODO: + // Read/Write lock using RemoteExecutor + + [Fact] + public void Ctor_Default() + { + var manager = new SubWeakEventManager(); + Assert.NotNull(manager.Dispatcher); + Assert.Same(manager.Dispatcher, manager.Dispatcher); + Assert.Same(Dispatcher.CurrentDispatcher, manager.Dispatcher); + IDisposable readLock = manager.ReadLock; + try + { + Assert.NotNull(readLock); + } + finally + { + readLock.Dispose(); + } + + IDisposable writeLock = manager.WriteLock; + try + { + Assert.NotNull(writeLock); + } + finally + { + writeLock.Dispose(); + } + } + + [Fact] + public void Item_Get_ReturnsExpected() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction = (source) => {}; + + var source = new object(); + var listener = new CustomWeakEventListener(); + manager.ProtectedAddListener(source, listener); + + // Get. + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + } + + public static IEnumerable Item_GetNoSuchSource_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new object() }; + yield return new object?[] { 1 }; + } + + [Theory] + [MemberData(nameof(Item_GetNoSuchSource_TestData))] + public void Item_GetNoSuchSourceNotEmpty_ReturnsExpected(object source) + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction = (source) => {}; + + manager.ProtectedAddListener(new object(), new CustomWeakEventListener()); + Assert.Null(manager[source]); + } + + [Theory] + [MemberData(nameof(Item_GetNoSuchSource_TestData))] + public void Item_GetNoSuchSourceEmpty_ReturnsExpected(object source) + { + var manager = new SubWeakEventManager(); + Assert.Null(manager[source]); + } + + public static IEnumerable Item_Set_TestData() + { + yield return new object?[] { new object(), null }; + yield return new object?[] { new object(), new object() }; + yield return new object?[] { new object(), 1 }; + yield return new object?[] { 1, null }; + yield return new object?[] { 1, new object() }; + yield return new object?[] { 1, 1 }; + } + + [Theory] + [MemberData(nameof(Item_Set_TestData))] + public void Item_Set_GetReturnsExpected(object source, object value) + { + var manager = new SubWeakEventManager(); + + // Set. + manager[source] = value; + Assert.Equal(value, manager[source]); + + // Set same. + manager[source] = value; + Assert.Equal(value, manager[source]); + } + + public static IEnumerable Item_SetNullSource_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new object() }; + yield return new object?[] { 1 }; + } + + [Theory] + [MemberData(nameof(Item_SetNullSource_TestData))] + public void Item_SetNullSource_GetReturnsNull(object value) + { + var manager = new SubWeakEventManager(); + object source = null!; + + // Set. + manager[source] = value; + Assert.Null(manager[source]); + + // Set same. + manager[source] = value; + Assert.Null(manager[source]); + } + + public static IEnumerable DeliverEvent_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new EventArgs() }; + yield return new object?[] { EventArgs.Empty }; + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithListeners_Success(EventArgs args) + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction = (source) => {}; + var source = new object(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(typeof(SubWeakEventManager), t); + Assert.Same(source, s); + Assert.Same(args, e); + events.Add("listener1"); + return true; + }; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(typeof(SubWeakEventManager), t); + Assert.Same(source, s); + Assert.Same(args, e); + events.Add("listener2"); + return true; + }; + manager.ProtectedAddListener(source, listener1); + manager.ProtectedAddListener(source, listener2); + + manager.DeliverEvent(source, args); + Assert.Equal(new string[] { "listener1", "listener2" }, events); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithHandlers_Success(EventArgs args) + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction = (source) => {}; + var source = new object(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.HandlerAction = (s, e) => + { + Assert.Same(source, s); + Assert.Same(args, e); + events.Add("handler1"); + }; + var listener2 = new CustomWeakEventListener(); + listener2.HandlerAction = (s, e) => + { + Assert.Same(source, s); + Assert.Same(args, e); + events.Add("handler2"); + }; + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), listener1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), listener2, nameof(CustomWeakEventListener.Handler)); + + manager.ProtectedAddHandler(source, handler1); + manager.ProtectedAddHandler(source, handler2); + + manager.DeliverEvent(source, args); + Assert.Equal(new string[] { "handler1", "handler2" }, events); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithListenersAndHandlers_Success(EventArgs args) + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction = (source) => {}; + var source = new object(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.HandlerAction = (s, e) => + { + Assert.Same(source, s); + Assert.Same(args, e); + events.Add("handler1"); + }; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(typeof(SubWeakEventManager), t); + Assert.Same(source, s); + Assert.Same(args, e); + events.Add("listener1"); + return true; + }; + var listener3 = new CustomWeakEventListener(); + listener3.HandlerAction = (s, e) => + { + Assert.Same(source, s); + Assert.Same(args, e); + events.Add("handler2"); + }; + var listener4 = new CustomWeakEventListener(); + listener4.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(typeof(SubWeakEventManager), t); + Assert.Same(source, s); + Assert.Same(args, e); + events.Add("listener2"); + return true; + }; + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), listener1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), listener3, nameof(CustomWeakEventListener.Handler)); + manager.ProtectedAddHandler(source, handler1); + manager.ProtectedAddListener(source, listener2); + manager.ProtectedAddHandler(source, handler2); + manager.ProtectedAddListener(source, listener4); + + manager.DeliverEvent(source, args); + Assert.Equal(new string[] { "handler1", "listener1", "handler2", "listener2" }, events); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeMultipleSources_Success(EventArgs args) + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction = (source) => {}; + var source1 = new object(); + var source2 = new object(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(typeof(SubWeakEventManager), t); + Assert.Same(source1, s); + Assert.Same(args, e); + events.Add("listener1"); + return true; + }; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(typeof(SubWeakEventManager), t); + Assert.Same(source2, s); + Assert.Same(args, e); + events.Add("listener2"); + return true; + }; + manager.ProtectedAddListener(source1, listener1); + manager.ProtectedAddListener(source2, listener2); + + manager.DeliverEvent(source2, args); + Assert.Equal(new string[] { "listener2" }, events); + + manager.DeliverEvent(source1, args); + Assert.Equal(new string[] { "listener2", "listener1" }, events); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithInvalidHandlerAction_ThrowsInvalidCastException(EventArgs args) + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction = (source) => {}; + var source = new object(); + Delegate handler = () => {}; + manager.ProtectedAddHandler(source, handler); + + Assert.Throws(() => manager.DeliverEvent(source, args)); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithInvalidHandlerFunc_ThrowsInvalidCastException(EventArgs args) + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction = (source) => {}; + var source = new object(); + bool EventHandler(object sender, EventArgs args) => true; + manager.ProtectedAddHandler(source, EventHandler); + + Assert.Throws(() => manager.DeliverEvent(source, args)); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithInvalidHandlerGenericEventArgs_ThrowsInvalidCastException(EventArgs args) + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction = (source) => {}; + var source = new object(); + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + manager.ProtectedAddHandler(source, handler); + + Assert.Throws(() => manager.DeliverEvent(source, args)); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeNoSuchSource_Success(EventArgs args) + { + var manager = new SubWeakEventManager(); + manager.DeliverEvent(new object(), args); + manager.DeliverEvent(null!, args); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeNotEmptyNoSuchSource_Success(EventArgs args) + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction = (source) => {}; + var source1 = new object(); + var events = new List(); + var listener = new CustomWeakEventListener(); + listener.ReceiveWeakEventAction = (t, s, e) => + { + events.Add("listener1"); + return true; + }; + manager.ProtectedAddListener(source1, listener); + + var source2 = new object(); + manager.DeliverEvent(source2, args); + Assert.Empty(events); + + manager.DeliverEvent(null!, args); + Assert.Empty(events); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeEmpty_Success(EventArgs args) + { + var manager = new SubWeakEventManager(); + manager.DeliverEvent(new object(), args); + manager.DeliverEvent(null!, args); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_SourceNotListenerList_ThrowsInvalidCastException(EventArgs args) + { + var manager = new SubWeakEventManager(); + var source = new object(); + manager[source] = new object(); + + Assert.Throws(() => manager.DeliverEvent(source, args)); + } + + public static IEnumerable DeliverEventToList_TestData() + { + yield return new object?[] { null, null }; + yield return new object?[] { new object(), new EventArgs() }; + yield return new object?[] { new object(), EventArgs.Empty }; + } + + [Theory] + [MemberData(nameof(DeliverEventToList_TestData))] + public void DeliverEventToList_InvokeWithListeners_Success(object sender, EventArgs args) + { + var manager = new SubWeakEventManager(); + + var list = new ListenerList(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Equal(typeof(SubWeakEventManager), t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener1"); + return true; + }; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Equal(typeof(SubWeakEventManager), t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener2"); + return true; + }; + list.Add(listener1); + list.Add(listener2); + + manager.DeliverEventToList(sender, args, list); + Assert.Equal(new string[] { "listener1", "listener2" }, events); + } + + [Theory] + [MemberData(nameof(DeliverEventToList_TestData))] + public void DeliverEventToList_InvokeWithHandlers_Success(object sender, EventArgs args) + { + var manager = new SubWeakEventManager(); + + var list = new ListenerList(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.HandlerAction = (s, e) => + { + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("handler1"); + }; + var listener2 = new CustomWeakEventListener(); + listener2.HandlerAction = (s, e) => + { + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("handler2"); + }; + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), listener1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), listener2, nameof(CustomWeakEventListener.Handler)); + list.AddHandler(handler1); + list.AddHandler(handler2); + + manager.DeliverEventToList(sender, args, list); + Assert.Equal(new string[] { "handler1", "handler2" }, events); + } + + [Theory] + [MemberData(nameof(DeliverEventToList_TestData))] + public void DeliverEventToList_InvokeWithListenersAndHandlers_Success(object sender, EventArgs args) + { + var manager = new SubWeakEventManager(); + + var list = new ListenerList(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.HandlerAction = (s, e) => + { + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("handler1"); + }; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Equal(typeof(SubWeakEventManager), t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener1"); + return true; + }; + var listener3 = new CustomWeakEventListener(); + listener3.HandlerAction = (s, e) => + { + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("handler2"); + }; + var listener4 = new CustomWeakEventListener(); + listener4.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Equal(typeof(SubWeakEventManager), t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener2"); + return true; + }; + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), listener1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), listener3, nameof(CustomWeakEventListener.Handler)); + list.AddHandler(handler1); + list.Add(listener2); + list.AddHandler(handler2); + list.Add(listener4); + + manager.DeliverEventToList(sender, args, list); + Assert.Equal(new string[] { "handler1", "listener1", "handler2", "listener2" }, events); + } + + // TODO: this causes a crash. +#if false + [Theory] + [MemberData(nameof(DeliverEventToList_TestData))] + public void DeliverEventToList_InvokeWithListenersNotHandled_Success(object sender, EventArgs args) + { + var manager = new SubWeakEventManager(); + + var list = new ListenerList(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Equal(typeof(SubWeakEventManager), t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener1"); + return false; + }; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Equal(typeof(SubWeakEventManager), t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener2"); + return true; + }; + list.Add(listener1); + list.Add(listener2); + + manager.DeliverEventToList(sender, args, list); + Assert.Equal(new string[] { "listener1", "listener2" }, events); + } +#endif + + [Theory] + [MemberData(nameof(DeliverEventToList_TestData))] + public void DeliverEventToList_InvokeWithInvalidHandlerAction_ThrowsInvalidCastException(object sender, EventArgs args) + { + var manager = new SubWeakEventManager(); + + var list = new ListenerList(); + Delegate handler = () => {}; + list.AddHandler(handler); + + Assert.Throws(() => manager.DeliverEventToList(sender, args, list)); + } + + [Theory] + [MemberData(nameof(DeliverEventToList_TestData))] + public void DeliverEventToList_InvokeWithInvalidHandlerFunc_ThrowsInvalidCastException(object sender, EventArgs args) + { + var manager = new SubWeakEventManager(); + + var list = new ListenerList(); + bool EventHandler(object sender, EventArgs args) => true; + list.AddHandler(EventHandler); + + Assert.Throws(() => manager.DeliverEventToList(sender, args, list)); + } + + [Theory] + [MemberData(nameof(DeliverEventToList_TestData))] + public void DeliverEventToList_InvokeWithInvalidHandlerGenericEventArgs_ThrowsInvalidCastException(object sender, EventArgs args) + { + var manager = new SubWeakEventManager(); + + var list = new ListenerList(); + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + list.AddHandler(handler); + + Assert.Throws(() => manager.DeliverEventToList(sender, args, list)); + } + + [Theory] + [MemberData(nameof(DeliverEventToList_TestData))] + public void DeliverEventToList_InvokeEmpty_Success(object sender, EventArgs args) + { + var manager = new SubWeakEventManager(); + + var list = new ListenerList(); + manager.DeliverEventToList(sender, args, list); + } + + public static IEnumerable DeliverEventToList_CustomDeliverEvent_TestData() + { + yield return new object?[] { null, null, true }; + yield return new object?[] { null, null, false }; + yield return new object?[] { new object(), new EventArgs(), true }; + yield return new object?[] { new object(), new EventArgs(), false }; + yield return new object?[] { new object(), EventArgs.Empty, true }; + yield return new object?[] { new object(), EventArgs.Empty, false }; + } + + [Theory] + [MemberData(nameof(DeliverEventToList_CustomDeliverEvent_TestData))] + public void DeliverEventToList_InvokeCustomDeliverEvent_CallsDeliverEvent(object sender, EventArgs args, bool result) + { + var manager = new SubWeakEventManager(); + + var list = new CustomListenerList(); + var events = new List(); + var listener = new CustomWeakEventListener(); + listener.ReceiveWeakEventAction = (t, s, e) => + { + events.Add("listener1"); + return true; + }; + list.Add(listener); + int deliverEventCallCount = 0; + list.DeliverEventAction = (s, a, t) => + { + Assert.Same(sender, s); + Assert.Same(args, a); + Assert.Equal(typeof(SubWeakEventManager), t); + deliverEventCallCount++; + return result; + }; + + // Call. + manager.DeliverEventToList(sender, args, list); + Assert.Equal(1, deliverEventCallCount); + Assert.Empty(events); + + // Call again. + manager.DeliverEventToList(sender, args, list); + Assert.Equal(2, deliverEventCallCount); + Assert.Empty(events); + } + + [Theory] + [MemberData(nameof(DeliverEventToList_CustomDeliverEvent_TestData))] + public void DeliverEventToList_SourceNotListenerList_Success(object sender, EventArgs args, bool result) + { + var manager = new SubWeakEventManager(); + manager[sender] = new object(); + + var list = new CustomListenerList(); + var events = new List(); + var listener = new CustomWeakEventListener(); + listener.ReceiveWeakEventAction = (t, s, e) => + { + events.Add("listener1"); + return true; + }; + list.Add(listener); + int deliverEventCallCount = 0; + list.DeliverEventAction = (s, a, t) => + { + Assert.Same(sender, s); + Assert.Same(args, a); + Assert.Equal(typeof(SubWeakEventManager), t); + deliverEventCallCount++; + return result; + }; + + // Call. + manager.DeliverEventToList(sender, args, list); + Assert.Equal(1, deliverEventCallCount); + Assert.Empty(events); + + // Call again. + manager.DeliverEventToList(sender, args, list); + Assert.Equal(2, deliverEventCallCount); + Assert.Empty(events); + } + + [Fact] + public void GetCurrentManager_InvokeNoSuchManagerType_ReturnsNull() + { + Assert.Null(SubWeakEventManager.GetCurrentManager(typeof(SentinelType1))); + } + + private class SentinelType1 { } + + [Fact] + public void GetCurrentManager_NullSource_ThrowsArgumentNullException() + { + Assert.Throws("key", () => SubWeakEventManager.GetCurrentManager(null!)); + } + + [Fact] + public void NewListenerList_Invoke_ReturnsExpected() + { + var manager = new SubWeakEventManager(); + SubWeakEventManager.ListenerList list = manager.NewListenerList(); + Assert.NotNull(list); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + + // Invoke again. + Assert.NotSame(list, manager.NewListenerList()); + } + + [Fact] + public void ProtectedAddHandler_Invoke_Success() + { + var manager = new SubWeakEventManager(); + object? expectedSource = null; + int startListeningCallCount = 0; + manager.StartListeningAction += (actualSource) => + { + Assert.Same(expectedSource, actualSource); + startListeningCallCount++; + }; + + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = (EventHandler)delegate { }; + Delegate handler3 = StaticEventHandler; + Delegate handler4 = (EventHandler)delegate { }; + + // Add new source. + var source1 = new object(); + expectedSource = source1; + manager.ProtectedAddHandler(source1, handler1); + Assert.Equal(1, startListeningCallCount); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source1]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + + // Add another listener to first source. + manager.ProtectedAddHandler(source1, handler2); + Assert.Equal(1, startListeningCallCount); + list = Assert.IsType(manager[source1]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Throws(() => list[1]); + + // Add another source. + var source2 = new object(); + expectedSource = source2; + manager.ProtectedAddHandler(source2, handler3); + Assert.Equal(2, startListeningCallCount); + list = Assert.IsType(manager[source1]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Throws(() => list[1]); + list = Assert.IsType(manager[source2]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + + // Add static source. + expectedSource = null; + manager.ProtectedAddHandler(null!, handler4); + Assert.Equal(3, startListeningCallCount); + list = Assert.IsType(manager[source1]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Throws(() => list[1]); + list = Assert.IsType(manager[source2]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Null(manager[null!]); + } + + [Fact] + public void ProtectedAddHandler_InvokeSameTargetMultipleTimes_Success() + { + var manager = new SubWeakEventManager(); + + object? expectedSource = null; + int startListeningCallCount = 0; + manager.StartListeningAction += (actualSource) => + { + Assert.Same(expectedSource, actualSource); + startListeningCallCount++; + }; + + var source = new object(); + var target1 = new CustomWeakEventListener(); + var target2 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + Delegate handler3 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.SecondHandler)); + Delegate handler4 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + Delegate handler5 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.SecondHandler)); + Delegate handler6 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.SecondHandler)); + Delegate handler7 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + + // Add first. + expectedSource = source; + manager.ProtectedAddHandler(source, handler1); + Assert.Equal(1, startListeningCallCount); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + + // Add second. + manager.ProtectedAddHandler(source, handler2); + Assert.Equal(1, startListeningCallCount); + list = Assert.IsType(manager[source]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target2, list[1]); + + // Add third. + manager.ProtectedAddHandler(source, handler3); + Assert.Equal(1, startListeningCallCount); + list = Assert.IsType(manager[source]); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target2, list[1]); + Assert.Same(target1, list[2]); + + // Add fourth. + manager.ProtectedAddHandler(source, handler4); + Assert.Equal(1, startListeningCallCount); + list = Assert.IsType(manager[source]); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target2, list[1]); + Assert.Same(target1, list[2]); + Assert.Same(target1, list[3]); + + // Add fifth. + manager.ProtectedAddHandler(source, handler5); + Assert.Equal(1, startListeningCallCount); + list = Assert.IsType(manager[source]); + Assert.Equal(5, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target2, list[1]); + Assert.Same(target1, list[2]); + Assert.Same(target1, list[3]); + Assert.Same(target1, list[4]); + + // Add sixth. + manager.ProtectedAddHandler(source, handler6); + Assert.Equal(1, startListeningCallCount); + list = Assert.IsType(manager[source]); + Assert.Equal(6, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target2, list[1]); + Assert.Same(target1, list[2]); + Assert.Same(target1, list[3]); + Assert.Same(target1, list[4]); + Assert.Same(target2, list[5]); + + // Add seventh. + manager.ProtectedAddHandler(source, handler6); + Assert.Equal(1, startListeningCallCount); + list = Assert.IsType(manager[source]); + Assert.Equal(7, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target2, list[1]); + Assert.Same(target1, list[2]); + Assert.Same(target1, list[3]); + Assert.Same(target1, list[4]); + Assert.Same(target2, list[5]); + Assert.Same(target2, list[6]); + } + + [Fact] + public void ProtectedAddHandler_InvokeStaticSource_Success() + { + var manager = new SubWeakEventManager(); + + FieldInfo actualSourceField = typeof(WeakEventManager).GetField("StaticSource", BindingFlags.NonPublic | BindingFlags.Static)!; + Assert.NotNull(actualSourceField); + object actualSource = actualSourceField.GetValue(null)!; + Assert.Equal("{StaticSource}", actualSource.ToString()); + + int startListeningCallCount = 0; + manager.StartListeningAction += (source) => + { + Assert.Null(source); + startListeningCallCount++; + }; + + Delegate handler1 = (EventHandler)delegate { }; + Delegate handler2 = (EventHandler)delegate { }; + + // Add first listener. + manager.ProtectedAddHandler(null!, handler1); + Assert.Equal(1, startListeningCallCount); + Assert.Null(manager[null!]); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[actualSource]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + + // Add second listener. + manager.ProtectedAddHandler(null!, handler2); + Assert.Equal(1, startListeningCallCount); + Assert.Null(manager[null!]); + list = Assert.IsType(manager[actualSource]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Throws(() => list[1]); + } + + [Fact] + public void ProtectedAddHandler_InvokeWithListeners_Success() + { + var manager = new SubWeakEventManager(); + object? expectedSource = null; + int startListeningCallCount = 0; + manager.StartListeningAction += (actualSource) => + { + Assert.Same(expectedSource, actualSource); + startListeningCallCount++; + }; + + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + var listener = new CustomWeakEventListener(); + + // Add listener. + var source = new object(); + expectedSource = source; + manager.ProtectedAddListener(source, listener); + Assert.Equal(1, startListeningCallCount); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + + // Add handler. + manager.ProtectedAddHandler(source, handler); + Assert.Equal(1, startListeningCallCount); + list = Assert.IsType(manager[source]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + Assert.Same(target, list[1]); + } + + [Fact] + public void ProtectedAddHandler_InvokeMultipleTimesSameSource_Success() + { + var manager = new SubWeakEventManager(); + object? expectedSource = null; + int startListeningCallCount = 0; + manager.StartListeningAction += (actualSource) => + { + Assert.Same(expectedSource, actualSource); + startListeningCallCount++; + }; + + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + + // Add first. + var source = new object(); + expectedSource = source; + manager.ProtectedAddHandler(source, handler); + Assert.Equal(1, startListeningCallCount); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target, list[0]); + + // Add second. + manager.ProtectedAddHandler(source, handler); + Assert.Equal(1, startListeningCallCount); + list = Assert.IsType(manager[source]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target, list[0]); + Assert.Same(target, list[1]); + } + + [Fact] + public void ProtectedAddHandler_InvokeMultipleTimesDifferentSource_Success() + { + var manager = new SubWeakEventManager(); + object? expectedSource = null; + int startListeningCallCount = 0; + manager.StartListeningAction += (actualSource) => + { + Assert.Same(expectedSource, actualSource); + startListeningCallCount++; + }; + + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + + // Add first source. + var source1 = new object(); + expectedSource = source1; + manager.ProtectedAddHandler(source1, handler); + Assert.Equal(1, startListeningCallCount); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source1]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target, list[0]); + + // Add second source. + var source2 = new object(); + expectedSource = source2; + manager.ProtectedAddHandler(source2, handler); + Assert.Equal(2, startListeningCallCount); + list = Assert.IsType(manager[source1]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target, list[0]); + list = Assert.IsType(manager[source2]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target, list[0]); + } + + [Fact] + public void ProtectedAddHandler_InvokeMultipleSameSourceNotInUse_DoesNotCloneListenerList() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + + var source = new object(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + + // Add first listener. + manager.ProtectedAddHandler(source, handler1); + SubWeakEventManager.ListenerList list1 = Assert.IsType(manager[source]); + Assert.Equal(1, list1.Count); + Assert.False(list1.IsEmpty); + Assert.Same(target1, list1[0]); + + // Add second listener. + manager.ProtectedAddHandler(source, handler2); + SubWeakEventManager.ListenerList list2 = Assert.IsType(manager[source]); + Assert.Same(list2, list1); + Assert.Equal(2, list2.Count); + Assert.False(list2.IsEmpty); + Assert.Same(target1, list2[0]); + Assert.Same(target2, list2[1]); + } + + [Fact] + public void ProtectedAddHandler_InvokeMultipleSameSourceInUse_ClonesListenerList() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + + var source = new object(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + + // Add first listener. + manager.ProtectedAddHandler(source, handler1); + SubWeakEventManager.ListenerList list1 = Assert.IsType(manager[source]); + Assert.Equal(1, list1.Count); + Assert.False(list1.IsEmpty); + Assert.Same(target1, list1[0]); + + // Begin use. + list1.BeginUse(); + + // Add second listener. + manager.ProtectedAddHandler(source, handler2); + SubWeakEventManager.ListenerList list2 = Assert.IsType(manager[source]); + Assert.NotSame(list2, list1); + Assert.Equal(1, list1.Count); + Assert.False(list1.IsEmpty); + Assert.Same(target1, list1[0]); + Assert.Equal(2, list2.Count); + Assert.False(list2.IsEmpty); + Assert.Same(target1, list2[0]); + Assert.Same(target2, list2[1]); + } + + [Fact] + public void ProtectedAddHandler_InvokeMultipleSameSourceNoLongerInUse_DoesNotCloneListenerList() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + + var source = new object(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + + // Add first listener. + manager.ProtectedAddHandler(source, handler1); + SubWeakEventManager.ListenerList list1 = Assert.IsType(manager[source]); + Assert.Equal(1, list1.Count); + Assert.False(list1.IsEmpty); + Assert.Same(target1, list1[0]); + + // Begin use. + list1.BeginUse(); + + // End use. + list1.EndUse(); + + // Add second listener. + manager.ProtectedAddHandler(source, handler2); + SubWeakEventManager.ListenerList list2 = Assert.IsType(manager[source]); + Assert.Same(list2, list1); + Assert.Equal(2, list2.Count); + Assert.False(list2.IsEmpty); + Assert.Same(target1, list2[0]); + Assert.Same(target2, list2[1]); + } + + [Theory] + [InlineData(1)] + [InlineData(3)] + [InlineData(4)] + [InlineData(6)] + [InlineData(7)] + public void ProtectedAddHandler_InvokeDifferentSizes_Success(int count) + { + // Test internal FrugalList implementation. + var handlers = new List(); + for (int i = 0; i < count; i++) + { + handlers.Add(Delegate.CreateDelegate(typeof(EventHandler), new CustomWeakEventListener(), nameof(CustomWeakEventListener.Handler))); + } + + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + + // Add. + var source = new object(); + for (int i = 0; i < count; i++) + { + manager.ProtectedAddHandler(source, handlers[i]); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(i + 1, list.Count); + Assert.False(list.IsEmpty); + for (int j = 0; j <= i; j++) + { + Assert.Same(handlers[j].Target, list[j]); + } + } + } + + [Fact] + public void ProtectedAddHandler_NullHandler_ThrowsArgumentNullException() + { + var manager = new SubWeakEventManager(); + var source = new object(); + Assert.Throws("handler", () => manager.ProtectedAddHandler(source, null!)); + } + + [Fact] + public void ProtectedAddHandler_SourceNotListenerList_ThrowsInvalidCastException() + { + var manager = new SubWeakEventManager(); + var source = new object(); + manager[source] = new object(); + + var target = new CustomWeakEventListener(); + var handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + Assert.Throws(() => manager.ProtectedAddHandler(source, handler)); + } + + [Fact] + public void ProtectedAddListener_Invoke_Success() + { + var manager = new SubWeakEventManager(); + object? expectedSource = null; + int startListeningCallCount = 0; + manager.StartListeningAction += (actualSource) => + { + Assert.Same(expectedSource, actualSource); + startListeningCallCount++; + }; + + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + var listener4 = new CustomWeakEventListener(); + + // Add new source. + var source1 = new object(); + expectedSource = source1; + manager.ProtectedAddListener(source1, listener1); + Assert.Equal(1, startListeningCallCount); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source1]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + + // Add another listener to first source. + manager.ProtectedAddListener(source1, listener2); + Assert.Equal(1, startListeningCallCount); + list = Assert.IsType(manager[source1]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + + // Add another source. + var source2 = new object(); + expectedSource = source2; + manager.ProtectedAddListener(source2, listener3); + Assert.Equal(2, startListeningCallCount); + list = Assert.IsType(manager[source1]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + list = Assert.IsType(manager[source2]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener3, list[0]); + + // Add static source. + expectedSource = null; + manager.ProtectedAddListener(null!, listener4); + Assert.Equal(3, startListeningCallCount); + list = Assert.IsType(manager[source1]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + list = Assert.IsType(manager[source2]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener3, list[0]); + Assert.Null(manager[null!]); + } + + [Fact] + public void ProtectedAddListener_InvokeStaticSource_Success() + { + var manager = new SubWeakEventManager(); + + FieldInfo actualSourceField = typeof(WeakEventManager).GetField("StaticSource", BindingFlags.NonPublic | BindingFlags.Static)!; + Assert.NotNull(actualSourceField); + object actualSource = actualSourceField.GetValue(null)!; + Assert.Equal("{StaticSource}", actualSource.ToString()); + + int startListeningCallCount = 0; + manager.StartListeningAction += (source) => + { + Assert.Null(source); + startListeningCallCount++; + }; + + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + + // Add first listener. + manager.ProtectedAddListener(null!, listener1); + Assert.Equal(1, startListeningCallCount); + Assert.Null(manager[null!]); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[actualSource]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + + // Add second listener. + manager.ProtectedAddListener(null!, listener2); + Assert.Equal(1, startListeningCallCount); + Assert.Null(manager[null!]); + list = Assert.IsType(manager[actualSource]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + } + + [Fact] + public void ProtectedAddListener_InvokeWithHandlers_Success() + { + var manager = new SubWeakEventManager(); + object? expectedSource = null; + int startListeningCallCount = 0; + manager.StartListeningAction += (actualSource) => + { + Assert.Same(expectedSource, actualSource); + startListeningCallCount++; + }; + + Delegate handler = () => { }; + var listener = new CustomWeakEventListener(); + + // Add handler. + var source = new object(); + expectedSource = source; + manager.ProtectedAddHandler(source, handler); + Assert.Equal(1, startListeningCallCount); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + + // Add listener. + manager.ProtectedAddListener(source, listener); + Assert.Equal(1, startListeningCallCount); + list = Assert.IsType(manager[source]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Same(listener, list[1]); + } + + [Fact] + public void ProtectedAddListener_InvokeMultipleTimesSameSource_Success() + { + var manager = new SubWeakEventManager(); + object? expectedSource = null; + int startListeningCallCount = 0; + manager.StartListeningAction += (actualSource) => + { + Assert.Same(expectedSource, actualSource); + startListeningCallCount++; + }; + + var listener = new CustomWeakEventListener(); + + // Add first. + var source = new object(); + expectedSource = source; + manager.ProtectedAddListener(source, listener); + Assert.Equal(1, startListeningCallCount); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + + // Add second. + manager.ProtectedAddListener(source, listener); + Assert.Equal(1, startListeningCallCount); + list = Assert.IsType(manager[source]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + Assert.Same(listener, list[1]); + } + + [Fact] + public void ProtectedAddListener_InvokeMultipleTimesDifferentSource_Success() + { + var manager = new SubWeakEventManager(); + object? expectedSource = null; + int startListeningCallCount = 0; + manager.StartListeningAction += (actualSource) => + { + Assert.Same(expectedSource, actualSource); + startListeningCallCount++; + }; + + var listener = new CustomWeakEventListener(); + + // Add first source. + var source1 = new object(); + expectedSource = source1; + manager.ProtectedAddListener(source1, listener); + Assert.Equal(1, startListeningCallCount); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source1]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + + // Add second source. + var source2 = new object(); + expectedSource = source2; + manager.ProtectedAddListener(source2, listener); + Assert.Equal(2, startListeningCallCount); + list = Assert.IsType(manager[source1]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + list = Assert.IsType(manager[source2]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + } + + [Fact] + public void ProtectedAddListener_InvokeMultipleSameSourceNotInUse_DoesNotCloneListenerList() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + + var source = new object(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + + // Add first listener. + manager.ProtectedAddListener(source, listener1); + SubWeakEventManager.ListenerList list1 = Assert.IsType(manager[source]); + Assert.Equal(1, list1.Count); + Assert.False(list1.IsEmpty); + Assert.Same(listener1, list1[0]); + + // Add second listener. + manager.ProtectedAddListener(source, listener2); + SubWeakEventManager.ListenerList list2 = Assert.IsType(manager[source]); + Assert.Same(list2, list1); + Assert.Equal(2, list2.Count); + Assert.False(list2.IsEmpty); + Assert.Same(listener1, list2[0]); + Assert.Same(listener2, list2[1]); + } + + [Fact] + public void ProtectedAddListener_InvokeMultipleSameSourceInUse_ClonesListenerList() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + + var source = new object(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + + // Add first listener. + manager.ProtectedAddListener(source, listener1); + SubWeakEventManager.ListenerList list1 = Assert.IsType(manager[source]); + Assert.Equal(1, list1.Count); + Assert.False(list1.IsEmpty); + Assert.Same(listener1, list1[0]); + + // Begin use. + list1.BeginUse(); + + // Add second listener. + manager.ProtectedAddListener(source, listener2); + SubWeakEventManager.ListenerList list2 = Assert.IsType(manager[source]); + Assert.NotSame(list2, list1); + Assert.Equal(1, list1.Count); + Assert.False(list1.IsEmpty); + Assert.Same(listener1, list1[0]); + Assert.Equal(2, list2.Count); + Assert.False(list2.IsEmpty); + Assert.Same(listener1, list2[0]); + Assert.Same(listener2, list2[1]); + } + + [Fact] + public void ProtectedAddListener_InvokeMultipleSameSourceNoLongerInUse_DoesNotCloneListenerList() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + + var source = new object(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + + // Add first listener. + manager.ProtectedAddListener(source, listener1); + SubWeakEventManager.ListenerList list1 = Assert.IsType(manager[source]); + Assert.Equal(1, list1.Count); + Assert.False(list1.IsEmpty); + Assert.Same(listener1, list1[0]); + + // Begin use. + list1.BeginUse(); + + // End use. + list1.EndUse(); + + // Add second listener. + manager.ProtectedAddListener(source, listener2); + SubWeakEventManager.ListenerList list2 = Assert.IsType(manager[source]); + Assert.Same(list2, list1); + Assert.Equal(2, list2.Count); + Assert.False(list2.IsEmpty); + Assert.Same(listener1, list2[0]); + Assert.Same(listener2, list2[1]); + } + + [Theory] + [InlineData(1)] + [InlineData(3)] + [InlineData(4)] + [InlineData(6)] + [InlineData(7)] + public void ProtectedAddListener_InvokeDifferentSizes_Success(int count) + { + // Test internal FrugalList implementation. + var listeners = new List(); + for (int i = 0; i < count; i++) + { + listeners.Add(new CustomWeakEventListener()); + } + + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + + // Add. + var source = new object(); + for (int i = 0; i < count; i++) + { + manager.ProtectedAddListener(source, listeners[i]); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(i + 1, list.Count); + Assert.False(list.IsEmpty); + for (int j = 0; j <= i; j++) + { + Assert.Same(listeners[j], list[j]); + } + } + } + + [Fact] + public void ProtectedAddListener_NullListener_ThrowsArgumentNullException() + { + var manager = new SubWeakEventManager(); + var source = new object(); + Assert.Throws("listener", () => manager.ProtectedAddListener(source, null!)); + } + + [Fact] + public void ProtectedAddListener_SourceNotListenerList_ThrowsInvalidCastException() + { + var manager = new SubWeakEventManager(); + var source = new object(); + manager[source] = new object(); + + var listener = new CustomWeakEventListener(); + Assert.Throws(() => manager.ProtectedAddListener(source, listener)); + } + + [Fact] + public void ProtectedRemoveHandler_Invoke_Success() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + object? expectedSource = null; + int stopListeningCallCount = 0; + manager.StopListeningAction += (source) => + { + Assert.Same(expectedSource, source); + stopListeningCallCount++; + }; + + var source1 = new object(); + var source2 = new object(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = (EventHandler)delegate { }; + Delegate handler3 = StaticEventHandler; + Delegate handler4 = (EventHandler)delegate { }; + manager.ProtectedAddHandler(source1, handler1); + manager.ProtectedAddHandler(source1, handler2); + manager.ProtectedAddHandler(source2, handler3); + manager.ProtectedAddHandler(source2, handler4); + + // Remove handler2. + manager.ProtectedRemoveHandler(source1, handler2); + Assert.Equal(0, stopListeningCallCount); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source1]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + list = Assert.IsType(manager[source2]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Throws(() => list[1]); + + // Remove handler2 again. + manager.ProtectedRemoveHandler(source1, handler2); + Assert.Equal(0, stopListeningCallCount); + list = Assert.IsType(manager[source1]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + list = Assert.IsType(manager[source2]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Throws(() => list[1]); + + // Remove handler1. + expectedSource = source1; + manager.ProtectedRemoveHandler(source1, handler1); + Assert.Equal(1, stopListeningCallCount); + Assert.Null(manager[source1]); + list = Assert.IsType(manager[source2]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Throws(() => list[1]); + + // Remove handler3. + expectedSource = source2; + manager.ProtectedRemoveHandler(source2, handler3); + Assert.Equal(1, stopListeningCallCount); + Assert.Null(manager[source1]); + list = Assert.IsType(manager[source2]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + + // Remove handler4. + expectedSource = source2; + manager.ProtectedRemoveHandler(source2, handler4); + Assert.Equal(2, stopListeningCallCount); + Assert.Null(manager[source1]); + Assert.Null(manager[source2]); + } + + [Fact] + public void ProtectedRemoveHandler_InvokeStaticSource_Success() + { + var manager = new SubWeakEventManager(); + + FieldInfo actualSourceField = typeof(WeakEventManager).GetField("StaticSource", BindingFlags.NonPublic | BindingFlags.Static)!; + Assert.NotNull(actualSourceField); + object actualSource = actualSourceField.GetValue(null)!; + Assert.Equal("{StaticSource}", actualSource.ToString()); + + manager.StartListeningAction += (source) => {}; + int stopListeningCallCount = 0; + manager.StopListeningAction += (source) => + { + Assert.Null(source); + stopListeningCallCount++; + }; + + Delegate handler1 = (EventHandler)delegate { }; + Delegate handler2 = (EventHandler)delegate { }; + manager.ProtectedAddHandler(null!, handler1); + manager.ProtectedAddHandler(null!, handler2); + + // Add first handler. + manager.ProtectedRemoveHandler(null!, handler1); + Assert.Equal(0, stopListeningCallCount); + Assert.Null(manager[null!]); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[actualSource]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + + // Add second handler. + manager.ProtectedRemoveHandler(null!, handler2); + Assert.Equal(1, stopListeningCallCount); + Assert.Null(manager[null!]); + Assert.Null(manager[actualSource]); + } + + [Fact] + public void ProtectedRemoveHandler_InvokeSameTargetMultipleTimesSuccess() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + object? expectedSource = null; + int stopListeningCallCount = 0; + manager.StopListeningAction += (source) => + { + Assert.Same(expectedSource, source); + stopListeningCallCount++; + }; + + var source = new object(); + var target1 = new CustomWeakEventListener(); + var target2 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + Delegate handler3 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.SecondHandler)); + Delegate handler4 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + Delegate handler5 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.SecondHandler)); + Delegate handler6 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.SecondHandler)); + Delegate handler7 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + + manager.ProtectedAddHandler(source, handler1); + manager.ProtectedAddHandler(source, handler2); + manager.ProtectedAddHandler(source, handler3); + manager.ProtectedAddHandler(source, handler4); + manager.ProtectedAddHandler(source, handler5); + manager.ProtectedAddHandler(source, handler6); + manager.ProtectedAddHandler(source, handler7); + + // Remove handler1. + manager.ProtectedRemoveHandler(source, handler1); + Assert.Equal(0, stopListeningCallCount); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(6, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target1, list[0]); + Assert.Equal(target2, list[1]); + Assert.Equal(target1, list[2]); + Assert.Equal(target1, list[3]); + Assert.Equal(target2, list[4]); + Assert.Equal(target2, list[5]); + + // Remove handler2. + manager.ProtectedRemoveHandler(source, handler2); + Assert.Equal(0, stopListeningCallCount); + list = Assert.IsType(manager[source]); + Assert.Equal(5, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target1, list[0]); + Assert.Equal(target2, list[1]); + Assert.Equal(target1, list[2]); + Assert.Equal(target1, list[3]); + Assert.Equal(target2, list[4]); + + // Remove handler3. + manager.ProtectedRemoveHandler(source, handler3); + Assert.Equal(0, stopListeningCallCount); + list = Assert.IsType(manager[source]); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target1, list[0]); + Assert.Equal(target2, list[1]); + Assert.Equal(target1, list[2]); + Assert.Equal(target2, list[3]); + + // Remove handler4. + manager.ProtectedRemoveHandler(source, handler4); + Assert.Equal(0, stopListeningCallCount); + list = Assert.IsType(manager[source]); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target2, list[0]); + Assert.Equal(target1, list[1]); + Assert.Equal(target2, list[2]); + + // Remove handler5. + manager.ProtectedRemoveHandler(source, handler5); + Assert.Equal(0, stopListeningCallCount); + list = Assert.IsType(manager[source]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target2, list[0]); + Assert.Equal(target2, list[1]); + + // Remove handler6. + manager.ProtectedRemoveHandler(source, handler6); + Assert.Equal(0, stopListeningCallCount); + list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target2, list[0]); + + // Remove handler7. + expectedSource = source; + manager.ProtectedRemoveHandler(source, handler7); + Assert.Equal(1, stopListeningCallCount); + Assert.Null(manager[source]); + } + + [Fact] + public void ProtectedRemoveHandler_InvokeWithListeners_Success() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (actualSource) => {}; + manager.StopListeningAction += (source) => {}; + + var source = new object(); + var listener = new CustomWeakEventListener(); + Delegate handler = (EventHandler)delegate { }; + manager.ProtectedAddListener(source, listener); + manager.ProtectedAddHandler(source, handler); + + // Remove handler. + manager.ProtectedRemoveHandler(source, handler); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + } + + [Fact] + public void ProtectedRemoveHandler_InvokeMultipleSameSourceNotInUse_DoesNotCloneListenerList() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + manager.StopListeningAction += (source) => {}; + + var source = new object(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + var target3 = new CustomWeakEventListener(); + Delegate handler3 = Delegate.CreateDelegate(typeof(EventHandler), target3, nameof(CustomWeakEventListener.Handler)); + manager.ProtectedAddHandler(source, handler1); + manager.ProtectedAddHandler(source, handler2); + manager.ProtectedAddHandler(source, handler3); + + // Remove first handler. + SubWeakEventManager.ListenerList list1 = Assert.IsType(manager[source]); + manager.ProtectedRemoveHandler(source, handler1); + SubWeakEventManager.ListenerList list2 = Assert.IsType(manager[source]); + Assert.Same(list2, list1); + Assert.Equal(2, list2.Count); + Assert.False(list2.IsEmpty); + Assert.Same(target2, list2[0]); + Assert.Same(target3, list2[1]); + + // Remove second handler. + manager.ProtectedRemoveHandler(source, handler2); + SubWeakEventManager.ListenerList list3 = Assert.IsType(manager[source]); + Assert.Same(list3, list1); + Assert.Equal(1, list1.Count); + Assert.False(list3.IsEmpty); + Assert.Same(target3, list3[0]); + + // Remove third handler. + manager.ProtectedRemoveHandler(source, handler3); + Assert.Null(manager[source]); + } + + [Fact] + public void ProtectedRemoveHandler_InvokeMultipleSameSourceInUse_ClonesListenerList() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + manager.StopListeningAction += (source) => {}; + + var source = new object(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + var target3 = new CustomWeakEventListener(); + Delegate handler3 = Delegate.CreateDelegate(typeof(EventHandler), target3, nameof(CustomWeakEventListener.Handler)); + manager.ProtectedAddHandler(source, handler1); + manager.ProtectedAddHandler(source, handler2); + manager.ProtectedAddHandler(source, handler3); + + // Remove first handler. + SubWeakEventManager.ListenerList list1 = Assert.IsType(manager[source]); + manager.ProtectedRemoveHandler(source, handler1); + SubWeakEventManager.ListenerList list2 = Assert.IsType(manager[source]); + Assert.Same(list2, list1); + Assert.Equal(2, list2.Count); + Assert.False(list2.IsEmpty); + Assert.Same(target2, list2[0]); + Assert.Same(target3, list2[1]); + + // Begin use. + list1.BeginUse(); + + // Remove second handler. + manager.ProtectedRemoveHandler(source, handler2); + SubWeakEventManager.ListenerList list3 = Assert.IsType(manager[source]); + Assert.NotSame(list3, list1); + Assert.Equal(2, list1.Count); + Assert.False(list1.IsEmpty); + Assert.Same(target2, list1[0]); + Assert.Same(target3, list1[1]); + Assert.Equal(1, list3.Count); + Assert.False(list3.IsEmpty); + Assert.Same(target3, list3[0]); + + // Remove third handler. + manager.ProtectedRemoveHandler(source, handler3); + Assert.Null(manager[source]); + Assert.NotSame(list3, list1); + Assert.Equal(2, list1.Count); + Assert.False(list1.IsEmpty); + Assert.Same(target2, list1[0]); + Assert.Same(target3, list1[1]); + Assert.Equal(0, list3.Count); + Assert.True(list3.IsEmpty); + } + + [Fact] + public void ProtectedRemoveHandler_InvokeMultipleSameSourceNoLongerInUse_DoesNotCloneListenerList() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + manager.StopListeningAction += (source) => {}; + + var source = new object(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + var target3 = new CustomWeakEventListener(); + Delegate handler3 = Delegate.CreateDelegate(typeof(EventHandler), target3, nameof(CustomWeakEventListener.Handler)); + manager.ProtectedAddHandler(source, handler1); + manager.ProtectedAddHandler(source, handler2); + manager.ProtectedAddHandler(source, handler3); + + // Remove first handler. + SubWeakEventManager.ListenerList list1 = Assert.IsType(manager[source]); + manager.ProtectedRemoveHandler(source, handler1); + SubWeakEventManager.ListenerList list2 = Assert.IsType(manager[source]); + Assert.Same(list2, list1); + Assert.Equal(2, list1.Count); + Assert.False(list1.IsEmpty); + Assert.Same(target2, list1[0]); + Assert.Same(target3, list1[1]); + + // Begin use. + list1.BeginUse(); + + // End use. + list1.EndUse(); + + // Remove second handler. + manager.ProtectedRemoveHandler(source, handler2); + SubWeakEventManager.ListenerList list3 = Assert.IsType(manager[source]); + Assert.Same(list3, list1); + Assert.Equal(1, list3.Count); + Assert.False(list3.IsEmpty); + Assert.Same(target3, list3[0]); + + // Remove third handler. + manager.ProtectedRemoveHandler(source, handler3); + Assert.Null(manager[source]); + Assert.Same(list3, list1); + Assert.Equal(0, list3.Count); + Assert.True(list3.IsEmpty); + } + + [Fact] + public void ProtectedRemoveHandler_InvokeNoSuchSourceEmpty_Succcess() + { + var manager = new SubWeakEventManager(); + + // Remove. + manager.ProtectedRemoveHandler(new object(), (EventHandler)delegate { }); + + // Remove again. + manager.ProtectedRemoveHandler(new object(), (EventHandler)delegate { }); + } + + [Fact] + public void ProtectedRemoveHandler_InvokeNoSuchSourceNotEmpty_Succcess() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + var source = new object(); + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + manager.ProtectedAddHandler(source, handler); + + // Remove. + manager.ProtectedRemoveHandler(new object(), (EventHandler)delegate { }); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target, list[0]); + + // Remove again. + manager.ProtectedRemoveHandler(new object(), (EventHandler)delegate { }); + list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target, list[0]); + } + + [Fact] + public void ProtectedRemoveHandler_InvokeNoSuchHandler_Success() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + var source = new object(); + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + manager.ProtectedAddHandler(source, handler); + + // Remove. + manager.ProtectedRemoveHandler(source, (EventHandler)delegate { }); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target, list[0]); + + // Remove again. + manager.ProtectedRemoveHandler(source, (EventHandler)delegate { }); + list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target, list[0]); + } + + [Theory] + [InlineData(1, 0)] + [InlineData(3, 0)] + [InlineData(3, 1)] + [InlineData(3, 2)] + [InlineData(4, 0)] + [InlineData(4, 1)] + [InlineData(4, 2)] + [InlineData(4, 3)] + [InlineData(6, 0)] + [InlineData(6, 1)] + [InlineData(6, 2)] + [InlineData(6, 3)] + [InlineData(6, 4)] + [InlineData(6, 5)] + [InlineData(7, 0)] + [InlineData(7, 1)] + [InlineData(7, 2)] + [InlineData(7, 3)] + [InlineData(7, 4)] + [InlineData(7, 5)] + public void ProtectedRemoveHandler_InvokeDifferentSizes_Success(int count, int removeIndex) + { + // Test internal FrugalList implementation. + var handlers = new List(); + for (int i = 0; i < count; i++) + { + handlers.Add(Delegate.CreateDelegate(typeof(EventHandler), new CustomWeakEventListener(), nameof(CustomWeakEventListener.Handler))); + } + + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + manager.StopListeningAction += (source) => {}; + + var source = new object(); + for (int i = 0; i < handlers.Count; i++) + { + manager.ProtectedAddHandler(source, handlers[i]); + } + + // Remove. + manager.ProtectedRemoveHandler(source, handlers[removeIndex]); + + var expectedHandlers = new List(handlers); + expectedHandlers.RemoveAt(removeIndex); + if (expectedHandlers.Count != 0) + { + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(expectedHandlers.Count, list.Count); + Assert.False(list.IsEmpty); + for (int i = 0; i < expectedHandlers.Count; i++) + { + Assert.Same(expectedHandlers[i].Target, list[i]); + } + } + else + { + Assert.Null(manager[source]); + } + + // Remove all. + for (int i = 0; i < expectedHandlers.Count; i++) + { + manager.ProtectedRemoveHandler(source, expectedHandlers[i]); + } + + Assert.Null(manager[source]); + } + + [Fact] + public void ProtectedRemoveHandler_NullHandler_ThrowsArgumentNullException() + { + var manager = new SubWeakEventManager(); + var source = new object(); + Assert.Throws("handler", () => manager.ProtectedRemoveHandler(source, null!)); + } + + [Fact] + public void ProtectedRemoveHandler_SourceNotListenerList_ThrowsInvalidCastException() + { + var manager = new SubWeakEventManager(); + var source = new object(); + manager[source] = new object(); + + var target = new CustomWeakEventListener(); + var handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + Assert.Throws(() => manager.ProtectedRemoveHandler(source, handler)); + } + + [Fact] + public void ProtectedRemoveListener_Invoke_Success() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + object? expectedSource = null; + int stopListeningCallCount = 0; + manager.StopListeningAction += (source) => + { + Assert.Same(expectedSource, source); + stopListeningCallCount++; + }; + + var source1 = new object(); + var source2 = new object(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + var listener4 = new CustomWeakEventListener(); + manager.ProtectedAddListener(source1, listener1); + manager.ProtectedAddListener(source1, listener2); + manager.ProtectedAddListener(source2, listener3); + manager.ProtectedAddListener(source2, listener4); + + // Remove listener2. + manager.ProtectedRemoveListener(source1, listener2); + Assert.Equal(0, stopListeningCallCount); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source1]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + list = Assert.IsType(manager[source2]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener3, list[0]); + Assert.Same(listener4, list[1]); + + // Remove listener2 again. + manager.ProtectedRemoveListener(source1, listener2); + Assert.Equal(0, stopListeningCallCount); + list = Assert.IsType(manager[source1]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + list = Assert.IsType(manager[source2]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener3, list[0]); + Assert.Same(listener4, list[1]); + + // Remove listener1. + expectedSource = source1; + manager.ProtectedRemoveListener(source1, listener1); + Assert.Equal(1, stopListeningCallCount); + Assert.Null(manager[source1]); + list = Assert.IsType(manager[source2]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener3, list[0]); + Assert.Same(listener4, list[1]); + + // Remove listener3. + expectedSource = source2; + manager.ProtectedRemoveListener(source2, listener3); + Assert.Equal(1, stopListeningCallCount); + Assert.Null(manager[source1]); + list = Assert.IsType(manager[source2]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener4, list[0]); + + // Remove listener4. + expectedSource = source2; + manager.ProtectedRemoveListener(source2, listener4); + Assert.Equal(2, stopListeningCallCount); + Assert.Null(manager[source1]); + Assert.Null(manager[source2]); + } + + [Fact] + public void ProtectedRemoveListener_InvokeStaticSource_Success() + { + var manager = new SubWeakEventManager(); + + FieldInfo actualSourceField = typeof(WeakEventManager).GetField("StaticSource", BindingFlags.NonPublic | BindingFlags.Static)!; + Assert.NotNull(actualSourceField); + object actualSource = actualSourceField.GetValue(null)!; + Assert.Equal("{StaticSource}", actualSource.ToString()); + + manager.StartListeningAction += (source) => {}; + int stopListeningCallCount = 0; + manager.StopListeningAction += (source) => + { + Assert.Null(source); + stopListeningCallCount++; + }; + + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + manager.ProtectedAddListener(null!, listener1); + manager.ProtectedAddListener(null!, listener2); + + // Add first listener. + manager.ProtectedRemoveListener(null!, listener1); + Assert.Equal(0, stopListeningCallCount); + Assert.Null(manager[null!]); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[actualSource]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener2, list[0]); + + // Add second listener. + manager.ProtectedRemoveListener(null!, listener2); + Assert.Equal(1, stopListeningCallCount); + Assert.Null(manager[null!]); + Assert.Null(manager[actualSource]); + } + + [Fact] + public void ProtectedRemoveListener_InvokeWithHandlers_Success() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (actualSource) => {}; + manager.StopListeningAction += (source) => {}; + + var source = new object(); + Delegate handler = () => { }; + var listener = new CustomWeakEventListener(); + manager.ProtectedAddHandler(source, handler); + manager.ProtectedAddListener(source, listener); + + // Remove listener. + manager.ProtectedRemoveListener(source, listener); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + } + + [Fact] + public void ProtectedRemoveListener_InvokeMultipleSameSourceNotInUse_DoesNotCloneListenerList() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + manager.StopListeningAction += (source) => {}; + + var source = new object(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + manager.ProtectedAddListener(source, listener1); + manager.ProtectedAddListener(source, listener2); + manager.ProtectedAddListener(source, listener3); + + // Remove first listener. + SubWeakEventManager.ListenerList list1 = Assert.IsType(manager[source]); + manager.ProtectedRemoveListener(source, listener1); + SubWeakEventManager.ListenerList list2 = Assert.IsType(manager[source]); + Assert.Same(list2, list1); + Assert.Equal(2, list2.Count); + Assert.False(list2.IsEmpty); + Assert.Same(listener2, list2[0]); + Assert.Same(listener3, list2[1]); + + // Remove second listener. + manager.ProtectedRemoveListener(source, listener2); + SubWeakEventManager.ListenerList list3 = Assert.IsType(manager[source]); + Assert.Same(list3, list1); + Assert.Equal(1, list1.Count); + Assert.False(list3.IsEmpty); + Assert.Same(listener3, list3[0]); + + // Remove third listener. + manager.ProtectedRemoveListener(source, listener3); + Assert.Null(manager[source]); + } + + [Fact] + public void ProtectedRemoveListener_InvokeMultipleSameSourceInUse_ClonesListenerList() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + manager.StopListeningAction += (source) => {}; + + var source = new object(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + manager.ProtectedAddListener(source, listener1); + manager.ProtectedAddListener(source, listener2); + manager.ProtectedAddListener(source, listener3); + + // Remove first listener. + SubWeakEventManager.ListenerList list1 = Assert.IsType(manager[source]); + manager.ProtectedRemoveListener(source, listener1); + SubWeakEventManager.ListenerList list2 = Assert.IsType(manager[source]); + Assert.Same(list2, list1); + Assert.Equal(2, list1.Count); + Assert.False(list1.IsEmpty); + Assert.Same(listener2, list1[0]); + Assert.Same(listener3, list1[1]); + + // Begin use. + list1.BeginUse(); + + // Remove second listener. + manager.ProtectedRemoveListener(source, listener2); + SubWeakEventManager.ListenerList list3 = Assert.IsType(manager[source]); + Assert.NotSame(list3, list1); + Assert.Equal(2, list1.Count); + Assert.False(list1.IsEmpty); + Assert.Same(listener2, list1[0]); + Assert.Same(listener3, list1[1]); + Assert.Equal(1, list3.Count); + Assert.False(list3.IsEmpty); + Assert.Same(listener3, list3[0]); + + // Remove third listener. + manager.ProtectedRemoveListener(source, listener3); + Assert.Null(manager[source]); + Assert.NotSame(list3, list1); + Assert.Equal(2, list1.Count); + Assert.False(list1.IsEmpty); + Assert.Same(listener2, list1[0]); + Assert.Same(listener3, list1[1]); + Assert.Equal(0, list3.Count); + Assert.True(list3.IsEmpty); + } + + [Fact] + public void ProtectedRemoveListener_InvokeMultipleSameSourceNoLongerInUse_DoesNotCloneListenerList() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + manager.StopListeningAction += (source) => {}; + + var source = new object(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + manager.ProtectedAddListener(source, listener1); + manager.ProtectedAddListener(source, listener2); + manager.ProtectedAddListener(source, listener3); + + // Remove first listener. + SubWeakEventManager.ListenerList list1 = Assert.IsType(manager[source]); + manager.ProtectedRemoveListener(source, listener1); + SubWeakEventManager.ListenerList list2 = Assert.IsType(manager[source]); + Assert.Same(list2, list1); + Assert.Equal(2, list1.Count); + Assert.False(list1.IsEmpty); + Assert.Same(listener2, list1[0]); + Assert.Same(listener3, list1[1]); + + // Begin use. + list1.BeginUse(); + + // End use. + list1.EndUse(); + + // Remove second listener. + manager.ProtectedRemoveListener(source, listener2); + SubWeakEventManager.ListenerList list3 = Assert.IsType(manager[source]); + Assert.Same(list3, list1); + Assert.Equal(1, list3.Count); + Assert.False(list3.IsEmpty); + Assert.Same(listener3, list3[0]); + + // Remove third listener. + manager.ProtectedRemoveListener(source, listener3); + Assert.Null(manager[source]); + Assert.Same(list3, list1); + Assert.Equal(0, list3.Count); + Assert.True(list3.IsEmpty); + } + + [Fact] + public void ProtectedRemoveListener_InvokeNoSuchSourceEmpty_Succcess() + { + var manager = new SubWeakEventManager(); + + // Remove. + manager.ProtectedRemoveListener(new object(), new CustomWeakEventListener()); + + // Remove again. + manager.ProtectedRemoveListener(new object(), new CustomWeakEventListener()); + } + + [Fact] + public void ProtectedRemoveListener_InvokeNoSuchSourceNotEmpty_Succcess() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + var source = new object(); + var listener = new CustomWeakEventListener(); + manager.ProtectedAddListener(source, listener); + + // Remove. + manager.ProtectedRemoveListener(new object(), new CustomWeakEventListener()); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + + // Remove again. + manager.ProtectedRemoveListener(new object(), new CustomWeakEventListener()); + list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + } + + [Fact] + public void ProtectedRemoveListener_InvokeNoSuchListener_Success() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + var source = new object(); + var listener = new CustomWeakEventListener(); + manager.ProtectedAddListener(source, listener); + + // Remove. + manager.ProtectedRemoveListener(source, new CustomWeakEventListener()); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + + // Remove again. + manager.ProtectedRemoveListener(source, new CustomWeakEventListener()); + list = Assert.IsType(manager[source]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + } + + [Theory] + [InlineData(1, 0)] + [InlineData(3, 0)] + [InlineData(3, 1)] + [InlineData(3, 2)] + [InlineData(4, 0)] + [InlineData(4, 1)] + [InlineData(4, 2)] + [InlineData(4, 3)] + [InlineData(6, 0)] + [InlineData(6, 1)] + [InlineData(6, 2)] + [InlineData(6, 3)] + [InlineData(6, 4)] + [InlineData(6, 5)] + [InlineData(7, 0)] + [InlineData(7, 1)] + [InlineData(7, 2)] + [InlineData(7, 3)] + [InlineData(7, 4)] + [InlineData(7, 5)] + public void ProtectedRemoveListener_InvokeDifferentSizes_Success(int count, int removeIndex) + { + // Test internal FrugalList implementation. + var listeners = new List(); + for (int i = 0; i < count; i++) + { + listeners.Add(new CustomWeakEventListener()); + } + + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + manager.StopListeningAction += (source) => {}; + + var source = new object(); + for (int i = 0; i < listeners.Count; i++) + { + manager.ProtectedAddListener(source, listeners[i]); + } + + // Remove. + manager.ProtectedRemoveListener(source, listeners[removeIndex]); + + var expectedListeners = new List(listeners); + expectedListeners.RemoveAt(removeIndex); + if (expectedListeners.Count != 0) + { + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source]); + Assert.Equal(expectedListeners.Count, list.Count); + Assert.False(list.IsEmpty); + for (int i = 0; i < expectedListeners.Count; i++) + { + Assert.Equal(expectedListeners[i], list[i]); + } + } + else + { + Assert.Null(manager[source]); + } + + // Remove all. + for (int i = 0; i < expectedListeners.Count; i++) + { + manager.ProtectedRemoveListener(source, expectedListeners[i]); + } + + Assert.Null(manager[source]); + } + + [Fact] + public void ProtectedRemoveListener_NullListener_ThrowsArgumentNullException() + { + var manager = new SubWeakEventManager(); + var source = new object(); + Assert.Throws("listener", () => manager.ProtectedRemoveListener(source, null!)); + } + + [Fact] + public void ProtectedRemoveListener_SourceNotListenerList_ThrowsInvalidCastException() + { + var manager = new SubWeakEventManager(); + var source = new object(); + manager[source] = new object(); + + var listener = new CustomWeakEventListener(); + Assert.Throws(() => manager.ProtectedRemoveListener(source, listener)); + } + + public static IEnumerable Purge_Data_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new object() }; + yield return new object?[] { new ListenerList() }; + } + + [Theory] + [MemberData(nameof(Purge_Data_TestData))] + public void Purge_InvokeNotEmptyPurgeAllWithSource_Success(object data) + { + var manager = new SubWeakEventManager(); + object? expectedSource = null; + int stopListeningCallCount = 0; + manager.StopListeningAction = (source) => + { + Assert.Same(expectedSource, source); + stopListeningCallCount++; + }; + var source = new object(); + var value = new object(); + manager[source] = value; + + // Purge. + expectedSource = source; + Assert.False(manager.Purge(source, data, true)); + Assert.Equal(1, stopListeningCallCount); + Assert.Same(value, manager[source]); + + // Purge again. + expectedSource = source; + Assert.False(manager.Purge(source, data, true)); + Assert.Equal(2, stopListeningCallCount); + Assert.Same(value, manager[source]); + } + + [Fact] + public void Purge_InvokeNotEmptyNoPurgeAllNotEmptyListenerListData_ReturnsTrue() + { + var manager = new SubWeakEventManager(); + var source = new object(); + var value = new object(); + manager[source] = value; + + var list = new ListenerList(); + var listener = new CustomWeakEventListener(); + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + list.Add(listener); + list.AddHandler(handler); + + // Purge. + Assert.False(manager.Purge(source, list, false)); + Assert.False(list.IsEmpty); + Assert.Equal(2, list.Count); + Assert.Same(listener, list[0]); + Assert.Same(target, list[1]); + Assert.Same(value, manager[source]); + } + + [Fact] + public void Purge_InvokeNotEmptyNoPurgeAllEmptyListenerListData_ReturnsTrue() + { + var manager = new SubWeakEventManager(); + object? expectedSource = null; + int stopListeningCallCount = 0; + manager.StopListeningAction = (source) => + { + Assert.Same(expectedSource, source); + stopListeningCallCount++; + }; + var source = new object(); + var value = new object(); + manager[source] = value; + + var list = new ListenerList(); + + // Purge. + expectedSource = source; + Assert.True(manager.Purge(source, list, false)); + Assert.Equal(1, stopListeningCallCount); + Assert.True(list.IsEmpty); + Assert.Equal(0, list.Count); + Assert.Null(manager[source]); + + // Purge again. + expectedSource = source; + Assert.True(manager.Purge(source, list, false)); + Assert.Equal(2, stopListeningCallCount); + Assert.True(list.IsEmpty); + Assert.Equal(0, list.Count); + Assert.Null(manager[source]); + } + + [Fact] + public void Purge_InvokeNotEmptyNoPurgeAllNullData_ThrowsNullReferenceException() + { + var manager = new SubWeakEventManager(); + var source = new object(); + var value = new object(); + manager[source] = value; + + // Purge. + Assert.Throws(() => manager.Purge(source, null!, false)); + Assert.Same(value, manager[source]); + } + + [Fact] + public void Purge_InvokeNotEmptyNoPurgeAllNotListenerListData_ThrowsInvalidCastException() + { + var manager = new SubWeakEventManager(); + var source = new object(); + var value = new object(); + manager[source] = value; + + // Purge. + Assert.Throws(() => manager.Purge(source, new object(), false)); + Assert.Same(value, manager[source]); + } + + [Theory] + [MemberData(nameof(Purge_Data_TestData))] + public void Purge_InvokeNotEmptyPurgeAllWithNoSource_Success(object data) + { + var manager = new SubWeakEventManager(); + var source = new object(); + var value = new object(); + manager[source] = value; + + // Purge. + Assert.False(manager.Purge(null!, data, true)); + Assert.Same(value, manager[source]); + + // Purge again. + Assert.False(manager.Purge(null!, data, true)); + Assert.Same(value, manager[source]); + } + + [Theory] + [MemberData(nameof(Purge_Data_TestData))] + public void Purge_InvokeNotEmptyNoPurgeAllWithNoSource_Success(object data) + { + var manager = new SubWeakEventManager(); + var source = new object(); + var value = new object(); + manager[source] = value; + + // Purge. + Assert.False(manager.Purge(null!, data, true)); + Assert.Same(value, manager[source]); + + // Purge again. + Assert.False(manager.Purge(null!, data, true)); + Assert.Same(value, manager[source]); + } + + [Theory] + [MemberData(nameof(Purge_Data_TestData))] + public void Purge_InvokeEmptyPurgeAllWithSource_Success(object data) + { + var manager = new SubWeakEventManager(); + object? expectedSource = null; + int stopListeningCallCount = 0; + manager.StopListeningAction = (source) => + { + Assert.Same(expectedSource, source); + stopListeningCallCount++; + }; + var source = new object(); + + // Purge. + expectedSource = source; + Assert.False(manager.Purge(source, data, true)); + Assert.Equal(1, stopListeningCallCount); + Assert.Null(manager[source]); + + // Purge again. + expectedSource = source; + Assert.False(manager.Purge(source, data, true)); + Assert.Equal(2, stopListeningCallCount); + Assert.Null(manager[source]); + } + + [Fact] + public void Purge_InvokeEmptyNoPurgeAllNotEmptyListenerListData_ReturnsTrue() + { + var manager = new SubWeakEventManager(); + var source = new object(); + + var list = new ListenerList(); + var listener = new CustomWeakEventListener(); + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + list.Add(listener); + list.AddHandler(handler); + + // Purge. + Assert.False(manager.Purge(source, list, false)); + Assert.False(list.IsEmpty); + Assert.Equal(2, list.Count); + Assert.Same(listener, list[0]); + Assert.Same(target, list[1]); + Assert.Null(manager[source]); + } + + [Fact] + public void Purge_InvokeEmptyNoPurgeAllEmptyListenerListData_ReturnsTrue() + { + var manager = new SubWeakEventManager(); + object? expectedSource = null; + int stopListeningCallCount = 0; + manager.StopListeningAction = (source) => + { + Assert.Same(expectedSource, source); + stopListeningCallCount++; + }; + var source = new object(); + + var list = new ListenerList(); + + // Purge. + expectedSource = source; + Assert.True(manager.Purge(source, list, false)); + Assert.Equal(1, stopListeningCallCount); + Assert.True(list.IsEmpty); + Assert.Equal(0, list.Count); + Assert.Null(manager[source]); + + // Purge again. + expectedSource = source; + Assert.True(manager.Purge(source, list, false)); + Assert.Equal(2, stopListeningCallCount); + Assert.True(list.IsEmpty); + Assert.Equal(0, list.Count); + Assert.Null(manager[source]); + } + + [Fact] + public void Purge_InvokeEmptyNoPurgeAllNullData_ThrowsNullReferenceException() + { + var manager = new SubWeakEventManager(); + var source = new object(); + + // Purge. + Assert.Throws(() => manager.Purge(source, null!, false)); + Assert.Null(manager[source]); + } + + [Fact] + public void Purge_InvokeEmptyNoPurgeAllNotListenerListData_ThrowsInvalidCastException() + { + var manager = new SubWeakEventManager(); + var source = new object(); + + // Purge. + Assert.Throws(() => manager.Purge(source, new object(), false)); + Assert.Null(manager[source]); + } + + [Theory] + [MemberData(nameof(Purge_Data_TestData))] + public void Purge_InvokeEmptyPurgeAllWithNoSource_Success(object data) + { + var manager = new SubWeakEventManager(); + + // Purge. + Assert.False(manager.Purge(null!, data, true)); + + // Purge again. + Assert.False(manager.Purge(null!, data, true)); + } + + [Theory] + [MemberData(nameof(Purge_Data_TestData))] + public void Purge_InvokeEmptyNoPurgeAllWithNoSource_Success(object data) + { + var manager = new SubWeakEventManager(); + + // Purge. + Assert.False(manager.Purge(null!, data, true)); + + // Purge again. + Assert.False(manager.Purge(null!, data, true)); + } + + public static IEnumerable Remove_TestData() + { + yield return new object?[] { new object() }; + yield return new object?[] { 1 }; + } + + [Theory] + [MemberData(nameof(Remove_TestData))] + public void Remove_Invoke_Success(object source) + { + var manager = new SubWeakEventManager(); + + object listener = new object(); + manager[source] = listener; + Assert.Same(listener, manager[source]); + + // Remove. + manager.Remove(source); + Assert.Null(manager[source]); + + // Remove again. + manager.Remove(source); + Assert.Null(manager[source]); + } + + public static IEnumerable Remove_NoSuchSource_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new object() }; + yield return new object?[] { 1 }; + } + + [Theory] + [MemberData(nameof(Remove_NoSuchSource_TestData))] + public void Remove_InvokeNoSuchSource_Success(object source) + { + var manager = new SubWeakEventManager(); + + // Remove. + manager.Remove(source); + + // Remove again. + manager.Remove(source); + } + + [Fact] + public void SetCurrentManager_Invoke_GetReturnsExpected() + { + var newManager = new SubWeakEventManager(); + + // Set. + SubWeakEventManager.SetCurrentManager(typeof(SentinelType2), newManager); + Assert.Same(newManager, SubWeakEventManager.GetCurrentManager(typeof(SentinelType2))); + + // Set same. + SubWeakEventManager.SetCurrentManager(typeof(SentinelType2), newManager); + Assert.Same(newManager, SubWeakEventManager.GetCurrentManager(typeof(SentinelType2))); + + // Set null. + SubWeakEventManager.SetCurrentManager(typeof(SentinelType2), null); + Assert.Null(SubWeakEventManager.GetCurrentManager(typeof(SentinelType2))); + } + + private class SentinelType2 { } + + [Fact] + public void SetCurrentManager_NullSource_ThrowsArgumentNullException() + { + Assert.Throws("key", () => SubWeakEventManager.SetCurrentManager(null!, new SubWeakEventManager())); + } + + [Fact] + public void ScheduleCleanup_InvokeWithListeners_Success() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + + var source1 = new object(); + var source2 = new object(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + manager.ProtectedAddListener(source1, listener1); + manager.ProtectedAddListener(source1, listener2); + manager.ProtectedAddListener(source2, listener3); + + // Schedule. + manager.ScheduleCleanup(); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source1]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + list = Assert.IsType(manager[source2]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener3, list[0]); + + // Schedule again. + manager.ScheduleCleanup(); + list = Assert.IsType(manager[source1]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + list = Assert.IsType(manager[source2]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener3, list[0]); + } + + [Fact] + public void ScheduleCleanup_InvokeWithHandlers_Success() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + + var source1 = new object(); + var source2 = new object(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = (EventHandler)delegate { }; + Delegate handler3 = StaticEventHandler; + manager.ProtectedAddHandler(source1, handler1); + manager.ProtectedAddHandler(source1, handler2); + manager.ProtectedAddHandler(source2, handler3); + + // Schedule. + manager.ScheduleCleanup(); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source1]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Throws(() => list[1]); + list = Assert.IsType(manager[source2]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + + // Schedule again. + manager.ScheduleCleanup(); + list = Assert.IsType(manager[source1]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Throws(() => list[1]); + list = Assert.IsType(manager[source2]); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + } + + [Fact] + public void ScheduleCleanup_InvokeWithListenersAndHandlers_Success() + { + var manager = new SubWeakEventManager(); + manager.StartListeningAction += (source) => {}; + + var source1 = new object(); + var source2 = new object(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = (EventHandler)delegate { }; + Delegate handler3 = StaticEventHandler; + manager.ProtectedAddListener(source1, listener1); + manager.ProtectedAddHandler(source1, handler1); + manager.ProtectedAddListener(source1, listener2); + manager.ProtectedAddHandler(source1, handler2); + manager.ProtectedAddListener(source2, listener3); + manager.ProtectedAddHandler(source2, handler3); + + // Schedule. + manager.ScheduleCleanup(); + SubWeakEventManager.ListenerList list = Assert.IsType(manager[source1]); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(target1, list[1]); + Assert.Same(listener2, list[2]); + Assert.Throws(() => list[3]); + list = Assert.IsType(manager[source2]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener3, list[0]); + Assert.Throws(() => list[1]); + + // Schedule again. + manager.ScheduleCleanup(); + list = Assert.IsType(manager[source1]); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(target1, list[1]); + Assert.Same(listener2, list[2]); + Assert.Throws(() => list[3]); + list = Assert.IsType(manager[source2]); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener3, list[0]); + Assert.Throws(() => list[1]); + } + + [Fact] + public void ScheduleCleanup_InvokeEmpty_Success() + { + var manager = new SubWeakEventManager(); + + // Schedule + manager.ScheduleCleanup(); + + // Schedule again. + manager.ScheduleCleanup(); + } + + public class ListenerListTests : WeakEventManager + { + [Fact] + public void Ctor_Default() + { + var list = new SubListenerList(); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(5)] + [InlineData(6)] + [InlineData(7)] + public void Ctor_Int(int capacity) + { + var list = new SubListenerList(capacity); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void Empty_Get_Success() + { + ListenerList list = ListenerList.Empty; + Assert.NotNull(list); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + Assert.Same(list, ListenerList.Empty); + } + + [Fact] + public void PrepareForWriting_InvokeEmptyInUse_ClonesList() + { + var originalList = new ListenerList(); + ListenerList list = originalList; + + // Begin use. + list.BeginUse(); + + // Invoke. + Assert.True(ListenerList.PrepareForWriting(ref list)); + Assert.NotSame(originalList, list); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + + // Invoke again. + ListenerList newList = list; + Assert.False(ListenerList.PrepareForWriting(ref list)); + Assert.NotSame(originalList, list); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void PrepareForWriting_InvokeEmptyNotInUse_DoesNotCloneList() + { + var originalList = new ListenerList(); + + // Invoke. + ListenerList list = originalList; + Assert.False(ListenerList.PrepareForWriting(ref list)); + Assert.Same(originalList, list); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + + // Invoke again. + ListenerList newList = list; + Assert.False(ListenerList.PrepareForWriting(ref list)); + Assert.Same(originalList, list); + Assert.Same(newList, list); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void PrepareForWriting_InvokeEmptyNoLongerInUse_DoesNotCloneList() + { + var originalList = new ListenerList(); + ListenerList list = originalList; + + // Begin use. + list.BeginUse(); + + // End use. + list.EndUse(); + + // Invoke. + Assert.False(ListenerList.PrepareForWriting(ref list)); + Assert.Same(originalList, list); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + + // Invoke again. + ListenerList newList = list; + Assert.False(ListenerList.PrepareForWriting(ref list)); + Assert.Same(originalList, list); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void PrepareForWriting_InvokeNotEmptyNotInUse_DoesNotCloneList() + { + var originalList = new ListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + + originalList.Add(listener1); + originalList.Add(listener2); + + // Invoke. + ListenerList list = originalList; + Assert.False(ListenerList.PrepareForWriting(ref list)); + Assert.Same(originalList, list); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + + // Invoke again. + ListenerList newList = list; + Assert.False(ListenerList.PrepareForWriting(ref list)); + Assert.Same(originalList, list); + Assert.Same(newList, list); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + } + + [Fact] + public void PrepareForWriting_InvokeNotEmptyInUse_ClonesList() + { + var originalList = new ListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + + originalList.Add(listener1); + originalList.Add(listener2); + + // Begin use. + originalList.BeginUse(); + + // Invoke. + ListenerList list = originalList; + Assert.True(ListenerList.PrepareForWriting(ref list)); + Assert.NotSame(originalList, list); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + + // Invoke again. + ListenerList newList = list; + Assert.False(ListenerList.PrepareForWriting(ref list)); + Assert.Same(newList, list); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + } + + [Fact] + public void PrepareForWriting_InvokeNotEmptyNoLongerInUse_DoesNotCloneList() + { + var originalList = new ListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + + originalList.Add(listener1); + originalList.Add(listener2); + + // Begin use. + originalList.BeginUse(); + + // End use. + originalList.EndUse(); + + // Invoke. + ListenerList list = originalList; + Assert.False(ListenerList.PrepareForWriting(ref list)); + Assert.Same(originalList, list); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + + // Invoke again. + ListenerList newList = list; + Assert.False(ListenerList.PrepareForWriting(ref list)); + Assert.Same(originalList, list); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + } + + public static IEnumerable PrepareForWriting_CustomClone_TestData() + { + yield return new object?[] { null }; + yield return new object?[] { new SubListenerList() }; + } + + [Theory] + [MemberData(nameof(PrepareForWriting_CustomClone_TestData))] + public void PrepareForWriting_CustomCloneInUse_Success(object result) + { + var originalList = new CustomListenerList(); + int cloneCallCount = 0; + originalList.CloneAction = () => + { + cloneCallCount++; + return (ListenerList)result; + }; + + ListenerList list = originalList; + list.BeginUse(); + + // Invoke. + Assert.True(ListenerList.PrepareForWriting(ref list)); + Assert.NotSame(originalList, list); + Assert.Same(result, list); + Assert.Equal(1, cloneCallCount); + } + + [Fact] + public void PrepareForWriting_NullList_ThrowsArgumentNullException() + { + ListenerList? list = null; + // TODO: this should throw ANE. + //Assert.Throws("list", () => ListenerList.PrepareForWriting(ref list)); + Assert.Throws(() => ListenerList.PrepareForWriting(ref list)); + } + + [Fact] + public void Item_GetListener_ReturnsExpected() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + list.Add(listener1); + Assert.Same(listener1, list[0]); + } + + [Fact] + public void Item_GetHandlerIWeakEventListenerTarget_ReturnsExpected() + { + var list = new SubListenerList(); + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + Assert.NotNull(handler.Target); + Assert.IsAssignableFrom(handler.Target); + + list.AddHandler(handler); + Assert.Same(target, list[0]); + } + + [Fact] + public void Item_GetHandlerStatic_ThrowsInvalidCastException() + { + var list = new SubListenerList(); + static void Handler(object sender, EventArgs e) { } + Delegate handler = Handler; + Assert.Null(handler.Target); + + list.AddHandler(handler); + Assert.Throws(() => list[0]); + } + + [Fact] + public void Item_GetHandlerNotIWeakEventListenerTarget_ThrowsInvalidCastException() + { + var list = new SubListenerList(); + Delegate handler = (EventHandler)delegate { }; + Assert.NotNull(handler.Target); + Assert.False(handler.Target is IWeakEventListener); + + list.AddHandler(handler); + Assert.Throws(() => list[0]); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(5)] + [InlineData(6)] + [InlineData(7)] + public void Item_GetWithCapacity_ReturnsExpected(int capacity) + { + var list = new SubListenerList(capacity); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + list.Add(listener1); + Assert.Same(listener1, list[0]); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + public void Item_GetInvalidIndexEmpty_ThrowsArgumentOutOfRangeException(int index) + { + var list = new SubListenerList(); + Assert.Throws("index", () => list[index]); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(5)] + [InlineData(6)] + [InlineData(7)] + public void Item_GetInvalidIndexEmptyWithCapacity_ReturnsExpected(int capacity) + { + var list = new SubListenerList(capacity); + Assert.Throws("index", () => list[-1]); + Assert.Throws("index", () => list[0]); + Assert.Throws("index", () => list[1]); + } + + [Theory] + [InlineData(-1)] + [InlineData(1)] + [InlineData(2)] + public void Item_GetInvalidIndexNotEmpty_ThrowsArgumentOutOfRangeException(int index) + { + var list = new SubListenerList(); + list.Add(new CustomWeakEventListener()); + Assert.Throws("index", () => list[index]); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(5)] + [InlineData(6)] + [InlineData(7)] + public void Item_GetInvalidIndexNotEmptyWithCapacity_ThrowsArgumentOutOfRangeException(int capacity) + { + var list = new SubListenerList(capacity); + list.Add(new CustomWeakEventListener()); + Assert.Throws("index", () => list[-1]); + Assert.Throws("index", () => list[1]); + Assert.Throws("index", () => list[2]); + } + + [Fact] + public void Add_Invoke_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + var listener4 = new CustomWeakEventListener(); + var listener5 = new CustomWeakEventListener(); + var listener6 = new CustomWeakEventListener(); + + // Add once. + list.Add(listener1); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + + // Add again. + list.Add(listener1); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener1, list[1]); + + // Add third. + list.Add(null); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener1, list[1]); + Assert.Throws(() => list[2]); + + // Add fourth. + list.Add(listener2); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener1, list[1]); + Assert.Throws(() => list[2]); + Assert.Same(listener2, list[3]); + + // Add fifth. + list.Add(listener3); + Assert.Equal(5, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener1, list[1]); + Assert.Throws(() => list[2]); + Assert.Same(listener2, list[3]); + Assert.Same(listener3, list[4]); + + // Add sixth. + list.Add(listener4); + Assert.Equal(6, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener1, list[1]); + Assert.Throws(() => list[2]); + Assert.Same(listener2, list[3]); + Assert.Same(listener3, list[4]); + Assert.Same(listener4, list[5]); + + // Add seventh. + list.Add(listener5); + Assert.Equal(7, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener1, list[1]); + Assert.Throws(() => list[2]); + Assert.Same(listener2, list[3]); + Assert.Same(listener3, list[4]); + Assert.Same(listener4, list[5]); + Assert.Same(listener5, list[6]); + + // Add eighth. + list.Add(listener6); + Assert.Equal(8, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener1, list[1]); + Assert.Throws(() => list[2]); + Assert.Same(listener2, list[3]); + Assert.Same(listener3, list[4]); + Assert.Same(listener4, list[5]); + Assert.Same(listener5, list[6]); + Assert.Same(listener6, list[7]); + } + + // TODO: this causes a crash. +#if false + [Fact] + public void Add_InvokeInUse_Success() + { + var list = new SubListenerList(); + var listener = new SubListener(); + + // Begin use. + Assert.False(list.BeginUse()); + + // Add. + list.Add(listener); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + } +#endif + + [Fact] + public void Add_InvokeNoLongerInUse_Success() + { + var list = new SubListenerList(); + var listener = new CustomWeakEventListener(); + + // Begin use. + Assert.False(list.BeginUse()); + + // End use. + list.EndUse(); + + // Add. + list.Add(listener); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + } + + [Theory] + [InlineData(1)] + [InlineData(3)] + [InlineData(4)] + [InlineData(6)] + [InlineData(7)] + public void Add_InvokeDifferentSizes_Success(int count) + { + // Test internal FrugalList implementation. + var listeners = new List(); + for (int i = 0; i < count; i++) + { + listeners.Add(new CustomWeakEventListener()); + } + + // Add. + var list = new SubListenerList(); + for (int i = 0; i < count; i++) + { + list.Add(listeners[i]); + Assert.Equal(i + 1, list.Count); + Assert.False(list.IsEmpty); + for (int j = 0; j <= i; j++) + { + Assert.Same(listeners[j], list[j]); + } + } + } + + [Fact] + public void AddHandler_Invoke_Success() + { + var list = new SubListenerList(); + var target1 = new CustomWeakEventListener(); + var target2 = new CustomWeakEventListener(); + var target3 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = WeakEventManagerTests.StaticEventHandler; + Delegate handler3 = (EventHandler)delegate { }; + Delegate handler4 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + Delegate handler5 = Delegate.CreateDelegate(typeof(EventHandler), target3, nameof(CustomWeakEventListener.Handler)); + + // Add once. + list.AddHandler(handler1); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + + // Add again. + list.AddHandler(handler1); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target1, list[1]); + + // Add third. + list.AddHandler(handler2); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target1, list[1]); + Assert.Throws(() => list[2]); + + // Add again. + list.AddHandler(handler2); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target1, list[1]); + Assert.Throws(() => list[2]); + Assert.Throws(() => list[3]); + + // Add fifth. + list.AddHandler(handler3); + Assert.Equal(5, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target1, list[1]); + Assert.Throws(() => list[2]); + Assert.Throws(() => list[3]); + Assert.Throws(() => list[4]); + + // Add again. + list.AddHandler(handler3); + Assert.Equal(6, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target1, list[1]); + Assert.Throws(() => list[2]); + Assert.Throws(() => list[3]); + Assert.Throws(() => list[4]); + Assert.Throws(() => list[5]); + + // Add seventh. + list.AddHandler(handler4); + Assert.Equal(7, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target1, list[1]); + Assert.Throws(() => list[2]); + Assert.Throws(() => list[3]); + Assert.Throws(() => list[4]); + Assert.Throws(() => list[5]); + Assert.Same(target2, list[6]); + + // Add eighth. + list.AddHandler(handler5); + Assert.Equal(8, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target1, list[1]); + Assert.Throws(() => list[2]); + Assert.Throws(() => list[3]); + Assert.Throws(() => list[4]); + Assert.Throws(() => list[5]); + Assert.Same(target2, list[6]); + Assert.Same(target3, list[7]); + } + + [Fact] + public void AddHandler_InvokeSameTargetMultipleTimes_Success() + { + var list = new SubListenerList(); + + var target1 = new CustomWeakEventListener(); + var target2 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + Delegate handler3 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.SecondHandler)); + Delegate handler4 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + Delegate handler5 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.SecondHandler)); + Delegate handler6 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.SecondHandler)); + Delegate handler7 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + + // Add first. + list.AddHandler(handler1); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + + // Add second. + list.AddHandler(handler2); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target2, list[1]); + + // Add third. + list.AddHandler(handler3); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target2, list[1]); + Assert.Same(target1, list[2]); + + // Add fourth. + list.AddHandler(handler4); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target2, list[1]); + Assert.Same(target1, list[2]); + Assert.Same(target1, list[3]); + + // Add fifth. + list.AddHandler(handler5); + Assert.Equal(5, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target2, list[1]); + Assert.Same(target1, list[2]); + Assert.Same(target1, list[3]); + Assert.Same(target1, list[4]); + + // Add sixth. + list.AddHandler(handler6); + Assert.Equal(6, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target2, list[1]); + Assert.Same(target1, list[2]); + Assert.Same(target1, list[3]); + Assert.Same(target1, list[4]); + Assert.Same(target2, list[5]); + + // Add seventh. + list.AddHandler(handler7); + Assert.Equal(7, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target2, list[1]); + Assert.Same(target1, list[2]); + Assert.Same(target1, list[3]); + Assert.Same(target1, list[4]); + Assert.Same(target2, list[5]); + Assert.Same(target2, list[6]); + } + + [Fact] + public void AddHandler_InvokeMultipleTimesIWeakEventListenerTarget_Success() + { + var list = new SubListenerList(); + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + + // Add once. + list.AddHandler(handler); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target, list[0]); + + // Add twice. + list.AddHandler(handler); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target, list[0]); + Assert.Same(target, list[1]); + + // Add three times. + list.AddHandler(handler); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target, list[0]); + Assert.Same(target, list[1]); + Assert.Same(target, list[2]); + } + + [Fact] + public void AddHandler_InvokeMultipleTimesStaticTarget_Success() + { + var list = new SubListenerList(); + Delegate handler = WeakEventManagerTests.StaticEventHandler; + + // Add once. + list.AddHandler(handler); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + + // Add twice. + list.AddHandler(handler); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Throws(() => list[1]); + + // Add three times. + list.AddHandler(handler); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Throws(() => list[1]); + Assert.Throws(() => list[2]); + } + + [Fact] + public void AddHandler_InvokeMultipleTimesNotIWeakEventListenerTarget_Success() + { + var list = new SubListenerList(); + Delegate handler = (EventHandler)delegate { }; + + // Add once. + list.AddHandler(handler); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + + // Add twice. + list.AddHandler(handler); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Throws(() => list[1]); + + // Add three times. + list.AddHandler(handler); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Throws(() => list[1]); + Assert.Throws(() => list[2]); + } + + // TODO: this causes a crash. +#if false + [Fact] + public void AddHandler_InvokeInUse_Success() + { + var list = new SubListenerList(); + Delegate handler = (EventHandler)delegate { }; + + // Begin use. + Assert.False(list.BeginUse()); + + // Add. + list.AddHandler(handler); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + } +#endif + + [Fact] + public void AddHandler_InvokeNoLongerInUse_Success() + { + var list = new SubListenerList(); + Delegate handler = (EventHandler)delegate { }; + + // Begin use. + Assert.False(list.BeginUse()); + + // End use. + list.EndUse(); + + // Add. + list.AddHandler(handler); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + } + + [Theory] + [InlineData(1)] + [InlineData(3)] + [InlineData(4)] + [InlineData(6)] + [InlineData(7)] + public void AddHandler_InvokeDifferentSizes_Success(int count) + { + // Test internal FrugalList implementation. + var handlers = new List(); + for (int i = 0; i < count; i++) + { + handlers.Add(Delegate.CreateDelegate(typeof(EventHandler), new CustomWeakEventListener(), nameof(CustomWeakEventListener.Handler))); + } + + // Add. + var list = new SubListenerList(); + for (int i = 0; i < count; i++) + { + list.AddHandler(handlers[i]); + Assert.Equal(i + 1, list.Count); + Assert.False(list.IsEmpty); + for (int j = 0; j <= i; j++) + { + Assert.Same(handlers[j].Target, list[j]); + } + } + } + + [Fact] + public void AddHandler_NullHandler_ThrowsArgumentNullException() + { + var list = new SubListenerList(); + // TODO: this should throw ANE. + //Assert.Throws("handler", () => list.AddHandler(null)); + Assert.Throws(() => list.AddHandler(null)); + } + + [Fact] + public void BeginUse_Invoke_Success() + { + var list = new SubListenerList(); + + // Call once. + Assert.False(list.BeginUse()); + + // Call again. + Assert.True(list.BeginUse()); + } + + [Fact] + public void Clone_InvokeEmptyToEmpty_Success() + { + var list = new SubListenerList(); + + ListenerList newList = Assert.IsType(list.Clone()); + Assert.NotSame(list, newList); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + Assert.Equal(0, newList.Count); + Assert.True(newList.IsEmpty); + } + + [Fact] + public void Clone_NotEmptyWithListenersToEmpty_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + list.Add(listener1); + list.Add(listener2); + + ListenerList newList = Assert.IsType(list.Clone()); + Assert.NotSame(list, newList); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + Assert.Equal(2, newList.Count); + Assert.False(newList.IsEmpty); + Assert.Same(listener1, newList[0]); + Assert.Same(listener2, newList[1]); + } + + [Fact] + public void Clone_NotEmptyWithHandlersToEmpty_Success() + { + var list = new SubListenerList(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + Delegate handler3 = (EventHandler)delegate { }; + Delegate handler4 = WeakEventManagerTests.StaticEventHandler; + + list.AddHandler(handler1); + list.AddHandler(handler2); + list.AddHandler(handler3); + list.AddHandler(handler4); + + ListenerList newList = Assert.IsType(list.Clone()); + Assert.NotSame(list, newList); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target2, list[1]); + Assert.Throws(() => list[2]); + Assert.Throws(() => list[3]); + Assert.Equal(4, newList.Count); + Assert.False(newList.IsEmpty); + Assert.Same(target1, newList[0]); + Assert.Same(target2, newList[1]); + Assert.Throws(() => newList[2]); + Assert.Throws(() => newList[3]); + } + + [Fact] + public void Clone_NotEmptyWithListenersAndHandlersToEmpty_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + Delegate handler3 = (EventHandler)delegate { }; + Delegate handler4 = WeakEventManagerTests.StaticEventHandler; + + list.Add(listener1); + list.AddHandler(handler1); + list.Add(listener2); + list.AddHandler(handler2); + list.AddHandler(handler3); + list.AddHandler(handler4); + + ListenerList newList = Assert.IsType(list.Clone()); + Assert.NotSame(list, newList); + Assert.Equal(6, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(target1, list[1]); + Assert.Same(listener2, list[2]); + Assert.Same(target2, list[3]); + Assert.Throws(() => list[4]); + Assert.Throws(() => list[5]); + Assert.Equal(6, newList.Count); + Assert.False(newList.IsEmpty); + Assert.Same(listener1, newList[0]); + Assert.Same(target1, newList[1]); + Assert.Same(listener2, newList[2]); + Assert.Same(target2, newList[3]); + Assert.Throws(() => newList[4]); + Assert.Throws(() => newList[5]); + } + + [Fact] + public void CopyTo_InvokeEmptyToEmpty_Success() + { + var list = new SubListenerList(); + var newList = new ListenerList(); + + list.CopyTo(newList); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + Assert.Equal(0, newList.Count); + Assert.True(newList.IsEmpty); + } + + [Fact] + public void CopyTo_InvokeEmptyToNotEmptyWithListeners_Success() + { + var list = new SubListenerList(); + var newList = new ListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + + newList.Add(listener1); + newList.Add(listener2); + + list.CopyTo(newList); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + Assert.Equal(2, newList.Count); + Assert.False(newList.IsEmpty); + Assert.Equal(listener1, newList[0]); + Assert.Equal(listener2, newList[1]); + } + + [Fact] + public void CopyTo_InvokeEmptyToNotEmptyWithHandlers_Success() + { + var list = new SubListenerList(); + var newList = new ListenerList(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + Delegate handler3 = (EventHandler)delegate { }; + Delegate handler4 = WeakEventManagerTests.StaticEventHandler; + + newList.AddHandler(handler1); + newList.AddHandler(handler2); + newList.AddHandler(handler3); + newList.AddHandler(handler4); + + list.CopyTo(newList); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + Assert.Equal(4, newList.Count); + Assert.False(newList.IsEmpty); + Assert.Equal(target1, newList[0]); + Assert.Equal(target2, newList[1]); + Assert.Throws(() => newList[2]); + Assert.Throws(() => newList[3]); + } + + [Fact] + public void CopyTo_InvokeEmptyToNotEmptyWithListenersAndHandlers_Success() + { + var list = new SubListenerList(); + var newList = new ListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + Delegate handler3 = (EventHandler)delegate { }; + Delegate handler4 = WeakEventManagerTests.StaticEventHandler; + + newList.Add(listener1); + newList.AddHandler(handler1); + newList.Add(listener2); + newList.AddHandler(handler2); + newList.AddHandler(handler3); + newList.AddHandler(handler4); + + list.CopyTo(newList); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + Assert.Equal(6, newList.Count); + Assert.False(newList.IsEmpty); + Assert.Same(listener1, newList[0]); + Assert.Equal(target1, newList[1]); + Assert.Same(listener2, newList[2]); + Assert.Equal(target2, newList[3]); + Assert.Throws(() => newList[4]); + Assert.Throws(() => newList[5]); + } + + [Fact] + public void CopyTo_InvokeNotEmptyWithListenersToEmpty_Success() + { + var list = new SubListenerList(); + var newList = new ListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + list.Add(listener1); + list.Add(listener2); + + list.CopyTo(newList); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + Assert.Equal(2, newList.Count); + Assert.False(newList.IsEmpty); + Assert.Same(listener1, newList[0]); + Assert.Same(listener2, newList[1]); + } + + [Fact] + public void CopyTo_InvokeNotEmptyWithHandlersToEmpty_Success() + { + var list = new SubListenerList(); + var newList = new ListenerList(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + Delegate handler3 = (EventHandler)delegate { }; + Delegate handler4 = WeakEventManagerTests.StaticEventHandler; + + list.AddHandler(handler1); + list.AddHandler(handler2); + list.AddHandler(handler3); + list.AddHandler(handler4); + + list.CopyTo(newList); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target2, list[1]); + Assert.Throws(() => list[2]); + Assert.Throws(() => list[3]); + Assert.Equal(4, newList.Count); + Assert.False(newList.IsEmpty); + Assert.Same(target1, newList[0]); + Assert.Same(target2, newList[1]); + Assert.Throws(() => newList[2]); + Assert.Throws(() => newList[3]); + } + + [Fact] + public void CopyTo_InvokeNotEmptyWithListenersAndHandlersToEmpty_Success() + { + var list = new SubListenerList(); + var newList = new ListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + Delegate handler3 = (EventHandler)delegate { }; + Delegate handler4 = WeakEventManagerTests.StaticEventHandler; + + list.Add(listener1); + list.AddHandler(handler1); + list.Add(listener2); + list.AddHandler(handler2); + list.AddHandler(handler3); + list.AddHandler(handler4); + + list.CopyTo(newList); + Assert.Equal(6, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(target1, list[1]); + Assert.Same(listener2, list[2]); + Assert.Same(target2, list[3]); + Assert.Throws(() => list[4]); + Assert.Throws(() => list[5]); + Assert.Equal(6, newList.Count); + Assert.False(newList.IsEmpty); + Assert.Same(listener1, newList[0]); + Assert.Same(target1, newList[1]); + Assert.Same(listener2, newList[2]); + Assert.Same(target2, newList[3]); + Assert.Throws(() => newList[4]); + Assert.Throws(() => newList[5]); + } + + [Fact] + public void CopyTo_InvokeNotEmptyToNotEmpty_Success() + { + var list = new SubListenerList(); + var newList = new ListenerList(); + var listener1 = new CustomWeakEventListener(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var listener2 = new CustomWeakEventListener(); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + + list.Add(listener1); + list.AddHandler(handler1); + newList.Add(listener2); + newList.AddHandler(handler2); + + list.CopyTo(newList); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(target1, list[1]); + Assert.Equal(4, newList.Count); + Assert.False(newList.IsEmpty); + Assert.Same(listener2, newList[0]); + Assert.Same(target2, newList[1]); + Assert.Same(listener1, newList[2]); + Assert.Same(target1, newList[3]); + } + + [Fact] + public void CopyTo_InvokeSameNewListEmpty_Success() + { + var list = new SubListenerList(); + list.CopyTo(list); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void CopyTo_InvokeSameNewListNotEmpty_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + list.Add(listener1); + + list.CopyTo(list); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener1, list[1]); + } + + [Fact] + public void CopyTo_NullNewListEmpty_ThrowsArgumentNullException() + { + var list = new SubListenerList(); + // TODO: this should throw ANE. + //Assert.Throws("newList", () => list.CopyTo(null!)); + list.CopyTo(null!); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void CopyTo_NullNewListNotEmpty_ThrowsArgumentNullException() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + list.Add(listener1); + + // TODO: this should throw ANE. + //Assert.Throws("newList", () => list.CopyTo(null!)); + Assert.Throws(() => list.CopyTo(null!)); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + } + + public static IEnumerable DeliverEvent_TestData() + { + yield return new object?[] { null, null, null }; + yield return new object?[] { new object(), new EventArgs(), typeof(int) }; + yield return new object?[] { new object(), EventArgs.Empty, typeof(int) }; + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithListeners_Success(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(managerType, t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener1"); + return true; + }; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(managerType, t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener2"); + return true; + }; + list.Add(listener1); + list.Add(listener2); + + Assert.False(list.DeliverEvent(sender, args, managerType)); + Assert.Equal(new string[] { "listener1", "listener2" }, events); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithHandlers_Success(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.HandlerAction = (s, e) => + { + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("handler1"); + }; + var listener2 = new CustomWeakEventListener(); + listener2.HandlerAction = (s, e) => + { + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("handler2"); + }; + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), listener1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), listener2, nameof(CustomWeakEventListener.Handler)); + list.AddHandler(handler1); + list.AddHandler(handler2); + + Assert.False(list.DeliverEvent(sender, args, managerType)); + Assert.Equal(new string[] { "handler1", "handler2" }, events); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithListenersAndHandlers_Success(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.HandlerAction = (s, e) => + { + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("handler1"); + }; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(managerType, t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener1"); + return true; + }; + var listener3 = new CustomWeakEventListener(); + listener3.HandlerAction = (s, e) => + { + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("handler2"); + }; + var listener4 = new CustomWeakEventListener(); + listener4.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(managerType, t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener2"); + return true; + }; + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), listener1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), listener3, nameof(CustomWeakEventListener.Handler)); + list.AddHandler(handler1); + list.Add(listener2); + list.AddHandler(handler2); + list.Add(listener4); + + Assert.False(list.DeliverEvent(sender, args, managerType)); + Assert.Equal(new string[] { "handler1", "listener1", "handler2", "listener2" }, events); + } + + // TODO: this causes a crash. +#if false + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithListenersNotHandled_Success(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(managerType, t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener1"); + return false; + }; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(managerType, t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener2"); + return true; + }; + list.Add(listener1); + list.Add(listener2); + + Assert.False(list.DeliverEvent(sender, args, managerType)); + Assert.Equal(new string[] { "listener1", "listener2" }, events); + } +#endif + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithInvalidHandlerAction_ThrowsInvalidCastException(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + Delegate handler = () => {}; + list.AddHandler(handler); + + Assert.Throws(() => list.DeliverEvent(sender, args, managerType)); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithInvalidHandlerFunc_ThrowsInvalidCastException(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + bool EventHandler(object sender, EventArgs args) => true; + list.AddHandler(EventHandler); + + Assert.Throws(() => list.DeliverEvent(sender, args, managerType)); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithInvalidHandlerGenericEventHandler_ThrowsInvalidCastException(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + list.AddHandler(handler); + + Assert.Throws(() => list.DeliverEvent(sender, args, managerType)); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeEmpty_ReturnsFalse(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + Assert.False(list.DeliverEvent(sender, args, managerType)); + } + + [Fact] + public void EndUse_InvokeBegan_Success() + { + var list = new SubListenerList(); + + // Begin. + Assert.False(list.BeginUse()); + + // End. + list.EndUse(); + + // Begin. + Assert.False(list.BeginUse()); + + // Begin. + Assert.True(list.BeginUse()); + } + + [Fact] + public void EndUse_InvokeNotBegan_Success() + { + var list = new SubListenerList(); + + // Call once. + list.EndUse(); + + // Call again. + list.EndUse(); + + // Begin. + Assert.True(list.BeginUse()); + + // Begin. + Assert.True(list.BeginUse()); + + // Begin. + Assert.False(list.BeginUse()); + + // Begin. + Assert.True(list.BeginUse()); + } + + [Fact] + public void Purge_InvokeEmpty_Success() + { + var list = new SubListenerList(); + + Assert.False(list.Purge()); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + // TODO: this causes a crash. +#if false + [Fact] + public void Purge_InvokeEmptyInUse_Success() + { + var list = new SubListenerList(); + Assert.False(list.BeginUse()); + + Assert.False(list.Purge()); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } +#endif + + [Fact] + public void Purge_InvokeNotEmptyWithListeners_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + list.Add(listener1); + list.Add(listener2); + + Assert.False(list.Purge()); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + } + + [Fact] + public void Purge_InvokeNotEmptyWithHandlers_Success() + { + var list = new SubListenerList(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = (EventHandler)delegate { }; + Delegate handler3 = WeakEventManagerTests.StaticEventHandler; + list.AddHandler(handler1); + list.AddHandler(handler2); + list.AddHandler(handler3); + + Assert.False(list.Purge()); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target1, list[0]); + Assert.Same(target1, list[0]); + Assert.Throws(() => list[1]); + Assert.Throws(() => list[2]); + } + + [Fact] + public void Purge_InvokeNotEmptyWithListenersAndHandlers_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = (EventHandler)delegate { }; + Delegate handler3 = WeakEventManagerTests.StaticEventHandler; + list.Add(listener1); + list.AddHandler(handler1); + list.Add(listener2); + list.AddHandler(handler2); + list.AddHandler(handler3); + + Assert.False(list.Purge()); + Assert.Equal(5, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(target1, list[1]); + Assert.Same(listener2, list[2]); + Assert.Throws(() => list[3]); + Assert.Throws(() => list[4]); + } + + // TODO: this causes a crash. +#if false + [Fact] + public void Purge_InvokeNotEmptyInUse_Success() + { + var list = new SubListenerList(); + var listener1 = new SubListener(); + list.Add(listener1); + Assert.False(list.BeginUse()); + + Assert.False(list.Purge()); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + } +#endif + + + [Fact] + public void Remove_InvokeEmptyList_Success() + { + var list = new SubListenerList(); + var listener = new CustomWeakEventListener(); + + list.Remove(listener); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void Remove_InvokeOneItemList_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + + list.Add(listener1); + + // Remove none. + list.Remove(listener2); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + + // Remove last. + list.Remove(listener1); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + + // Remove again. + list.Remove(listener1); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void Remove_InvokeOneItemListWithNull_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + + list.Add(null); + + // Remove none. + list.Remove(listener1); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + + // Remove last. + list.Remove(null); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + + // Remove again. + list.Remove(null); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + } + + [Fact] + public void Remove_InvokeThreeItemList_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + var listener4 = new CustomWeakEventListener(); + + list.Add(listener1); + list.Add(listener2); + list.Add(listener3); + + // Remove none. + list.Remove(listener4); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + Assert.Same(listener3, list[2]); + + // Remove last. + list.Remove(listener3); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + + // Remove first. + list.Remove(listener1); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener2, list[0]); + + // Remove last. + list.Remove(listener2); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + + // Remove again. + list.Remove(listener2); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void Remove_InvokeThreeItemListWithNull_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + + list.Add(listener1); + list.Add(null); + list.Add(listener2); + + // Remove none. + list.Remove(listener3); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Throws(() => list[1]); + Assert.Same(listener2, list[2]); + + // Remove last. + list.Remove(listener2); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Throws(() => list[1]); + + // Remove first. + list.Remove(listener1); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + + // Remove null. + list.Remove(null); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + + // Remove again. + list.Remove(null); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + } + + [Fact] + public void Remove_InvokeFourItemList_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + var listener4 = new CustomWeakEventListener(); + var listener5 = new CustomWeakEventListener(); + + list.Add(listener1); + list.Add(listener2); + list.Add(listener3); + list.Add(listener4); + + // Remove none. + list.Remove(listener5); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + Assert.Same(listener3, list[2]); + Assert.Same(listener4, list[3]); + + // Remove last. + list.Remove(listener4); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + Assert.Same(listener3, list[2]); + + // Remove middle. + list.Remove(listener2); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener3, list[1]); + + // Remove first. + list.Remove(listener1); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener3, list[0]); + + // Remove last. + list.Remove(listener3); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + + // Remove again. + list.Remove(listener3); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void Remove_InvokeFourItemListWithNull_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + var listener4 = new CustomWeakEventListener(); + + list.Add(listener1); + list.Add(null); + list.Add(listener2); + list.Add(listener3); + + // Remove none. + list.Remove(listener4); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Throws(() => list[1]); + Assert.Same(listener2, list[2]); + + // Remove last. + list.Remove(listener3); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Throws(() => list[1]); + Assert.Same(listener2, list[2]); + + // Remove first. + list.Remove(listener1); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Same(listener2, list[1]); + + // Remove null. + list.Remove(null); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Same(listener2, list[1]); + + // Remove last. + list.Remove(listener2); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + + // Remove again. + list.Remove(listener2); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + } + + [Fact] + public void Remove_InvokeSixItemList_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + var listener4 = new CustomWeakEventListener(); + var listener5 = new CustomWeakEventListener(); + var listener6 = new CustomWeakEventListener(); + var listener7 = new CustomWeakEventListener(); + + list.Add(listener1); + list.Add(listener2); + list.Add(listener3); + list.Add(listener4); + list.Add(listener5); + list.Add(listener6); + + // Remove none. + list.Remove(listener7); + Assert.Equal(6, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + Assert.Same(listener3, list[2]); + Assert.Same(listener4, list[3]); + Assert.Same(listener5, list[4]); + Assert.Same(listener6, list[5]); + + // Remove last. + list.Remove(listener6); + Assert.Equal(5, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + Assert.Same(listener3, list[2]); + Assert.Same(listener4, list[3]); + Assert.Same(listener5, list[4]); + + // Remove middle. + list.Remove(listener2); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener3, list[1]); + Assert.Same(listener4, list[2]); + Assert.Same(listener5, list[3]); + + // Remove first. + list.Remove(listener1); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener3, list[0]); + Assert.Same(listener4, list[1]); + Assert.Same(listener5, list[2]); + + // Remove all. + list.Remove(listener3); + list.Remove(listener4); + list.Remove(listener5); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + + // Remove again. + list.Remove(listener5); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void Remove_InvokeSixItemListWithNull_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + var listener4 = new CustomWeakEventListener(); + var listener5 = new CustomWeakEventListener(); + var listener6 = new CustomWeakEventListener(); + + list.Add(listener1); + list.Add(null); + list.Add(listener2); + list.Add(listener3); + list.Add(listener4); + list.Add(listener5); + + // Remove none. + list.Remove(listener6); + Assert.Equal(6, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Throws(() => list[1]); + Assert.Same(listener2, list[2]); + Assert.Same(listener3, list[3]); + Assert.Same(listener4, list[4]); + Assert.Same(listener5, list[5]); + + // Remove last. + list.Remove(listener5); + Assert.Equal(5, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Throws(() => list[1]); + Assert.Same(listener2, list[2]); + Assert.Same(listener3, list[3]); + Assert.Same(listener4, list[4]); + + // Remove middle. + list.Remove(listener2); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Throws(() => list[1]); + Assert.Same(listener3, list[2]); + Assert.Same(listener4, list[3]); + + // Remove first. + list.Remove(listener1); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Same(listener3, list[1]); + Assert.Same(listener4, list[2]); + + // Remove null. + list.Remove(null); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Same(listener3, list[1]); + Assert.Same(listener4, list[2]); + + // Remove all. + list.Remove(listener3); + list.Remove(listener4); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + + // Remove again. + list.Remove(listener4); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + } + + [Fact] + public void Remove_InvokeSevenItemList_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + var listener4 = new CustomWeakEventListener(); + var listener5 = new CustomWeakEventListener(); + var listener6 = new CustomWeakEventListener(); + var listener7 = new CustomWeakEventListener(); + var listener8 = new CustomWeakEventListener(); + + list.Add(listener1); + list.Add(listener2); + list.Add(listener3); + list.Add(listener4); + list.Add(listener5); + list.Add(listener6); + list.Add(listener7); + + // Remove none. + list.Remove(listener8); + Assert.Equal(7, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + Assert.Same(listener3, list[2]); + Assert.Same(listener4, list[3]); + Assert.Same(listener5, list[4]); + Assert.Same(listener6, list[5]); + Assert.Same(listener7, list[6]); + + // Remove last. + list.Remove(listener7); + Assert.Equal(6, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + Assert.Same(listener3, list[2]); + Assert.Same(listener4, list[3]); + Assert.Same(listener5, list[4]); + Assert.Same(listener6, list[5]); + + // Remove middle. + list.Remove(listener2); + Assert.Equal(5, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener3, list[1]); + Assert.Same(listener4, list[2]); + Assert.Same(listener5, list[3]); + Assert.Same(listener6, list[4]); + + // Remove first. + list.Remove(listener1); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener3, list[0]); + Assert.Same(listener4, list[1]); + Assert.Same(listener5, list[2]); + Assert.Same(listener6, list[3]); + + // Remove all. + list.Remove(listener3); + list.Remove(listener4); + list.Remove(listener5); + list.Remove(listener6); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + + // Remove again. + list.Remove(listener6); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void Remove_InvokeSevenItemListWithNull_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var listener3 = new CustomWeakEventListener(); + var listener4 = new CustomWeakEventListener(); + var listener5 = new CustomWeakEventListener(); + var listener6 = new CustomWeakEventListener(); + var listener7 = new CustomWeakEventListener(); + + list.Add(listener1); + list.Add(null); + list.Add(listener2); + list.Add(listener3); + list.Add(listener4); + list.Add(listener5); + list.Add(listener6); + + // Remove none. + list.Remove(listener7); + Assert.Equal(7, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Throws(() => list[1]); + Assert.Same(listener2, list[2]); + Assert.Same(listener3, list[3]); + Assert.Same(listener4, list[4]); + Assert.Same(listener5, list[5]); + Assert.Same(listener6, list[6]); + + // Remove last. + list.Remove(listener6); + Assert.Equal(6, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Throws(() => list[1]); + Assert.Same(listener2, list[2]); + Assert.Same(listener3, list[3]); + Assert.Same(listener4, list[4]); + Assert.Same(listener5, list[5]); + + // Remove middle. + list.Remove(listener2); + Assert.Equal(5, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Throws(() => list[1]); + Assert.Same(listener3, list[2]); + Assert.Same(listener4, list[3]); + Assert.Same(listener5, list[4]); + + // Remove first. + list.Remove(listener1); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Same(listener3, list[1]); + Assert.Same(listener4, list[2]); + Assert.Same(listener5, list[3]); + + // Remove null. + list.Remove(null); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + Assert.Same(listener3, list[1]); + Assert.Same(listener4, list[2]); + Assert.Same(listener5, list[3]); + + // Remove all. + list.Remove(listener3); + list.Remove(listener4); + list.Remove(listener5); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + + // Remove again. + list.Remove(listener5); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Throws(() => list[0]); + } + + // TODO: this causes a crash. +#if false + [Fact] + public void Remove_InvokeInUse_Success() + { + var list = new SubListenerList(); + var listener = new SubListener(); + list.Add(listener); + + // Begin use. + Assert.False(list.BeginUse()); + + // Add. + list.Remove(listener); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } +#endif + + [Fact] + public void Remove_InvokeNoLongerInUse_Success() + { + var list = new SubListenerList(); + var listener = new CustomWeakEventListener(); + list.Add(listener); + + // Begin use. + Assert.False(list.BeginUse()); + + // End use. + list.EndUse(); + + // Add. + list.Remove(listener); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void Remove_InvokeNoSuchListenerEmpty_Success() + { + var list = new SubListenerList(); + + // Remove. + list.Remove(new CustomWeakEventListener()); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + + // Remove again. + list.Remove(new CustomWeakEventListener()); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + + // Remove null. + list.Remove(null); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void Remove_InvokeNoSuchListenerNotEmpty_Success() + { + var list = new SubListenerList(); + var listener = new CustomWeakEventListener(); + list.Add(listener); + + // Remove. + list.Remove(new CustomWeakEventListener()); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + + // Remove again. + list.Remove(new CustomWeakEventListener()); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + + // Remove null. + list.Remove(null); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener, list[0]); + } + + [Theory] + [InlineData(1, 0)] + [InlineData(3, 0)] + [InlineData(3, 1)] + [InlineData(3, 2)] + [InlineData(4, 0)] + [InlineData(4, 1)] + [InlineData(4, 2)] + [InlineData(4, 3)] + [InlineData(6, 0)] + [InlineData(6, 1)] + [InlineData(6, 2)] + [InlineData(6, 3)] + [InlineData(6, 4)] + [InlineData(6, 5)] + [InlineData(7, 0)] + [InlineData(7, 1)] + [InlineData(7, 2)] + [InlineData(7, 3)] + [InlineData(7, 4)] + [InlineData(7, 5)] + public void Remove_InvokeDifferentSizes_Success(int count, int removeIndex) + { + // Test internal FrugalList implementation. + var listeners = new List(); + for (int i = 0; i < count; i++) + { + listeners.Add(new CustomWeakEventListener()); + } + + var list = new SubListenerList(); + for (int i = 0; i < listeners.Count; i++) + { + list.Add(listeners[i]); + } + + // Remove. + list.Remove(listeners[removeIndex]); + + var expectedListeners = new List(listeners); + expectedListeners.RemoveAt(removeIndex); + Assert.Equal(expectedListeners.Count, list.Count); + Assert.Equal(expectedListeners.Count == 0, list.IsEmpty); + for (int i = 0; i < expectedListeners.Count; i++) + { + Assert.Equal(expectedListeners[i], list[i]); + } + + // Remove all. + for (int i = 0; i < expectedListeners.Count; i++) + { + list.Remove(expectedListeners[i]); + } + + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void RemoveHandler_Invoke_Success() + { + var list = new SubListenerList(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + var target3 = new CustomWeakEventListener(); + Delegate handler3 = Delegate.CreateDelegate(typeof(EventHandler), target3, nameof(CustomWeakEventListener.SecondHandler)); + + list.AddHandler(handler1); + list.AddHandler(handler2); + list.AddHandler(handler3); + + // Remove handler2. + list.RemoveHandler(handler2); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target1, list[0]); + Assert.Equal(target3, list[1]); + + // Remove again. + list.RemoveHandler(handler2); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.False(list.IsEmpty); + Assert.Equal(target1, list[0]); + Assert.Equal(target3, list[1]); + + // Remove handler3. + list.RemoveHandler(handler3); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target1, list[0]); + + // Remove handler1. + list.RemoveHandler(handler1); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void Remove_InvokeSameTargetMultipleTimesSuccess() + { + var list = new SubListenerList(); + + var target1 = new CustomWeakEventListener(); + var target2 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + Delegate handler3 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.SecondHandler)); + Delegate handler4 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + Delegate handler5 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.SecondHandler)); + Delegate handler6 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.SecondHandler)); + Delegate handler7 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + + list.AddHandler(handler1); + list.AddHandler(handler2); + list.AddHandler(handler3); + list.AddHandler(handler4); + list.AddHandler(handler5); + list.AddHandler(handler6); + list.AddHandler(handler7); + + // Remove handler1. + list.RemoveHandler(handler1); + Assert.Equal(6, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target1, list[0]); + Assert.Equal(target2, list[1]); + Assert.Equal(target1, list[2]); + Assert.Equal(target1, list[3]); + Assert.Equal(target2, list[4]); + Assert.Equal(target2, list[5]); + + // Remove handler2. + list.RemoveHandler(handler2); + Assert.Equal(5, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target1, list[0]); + Assert.Equal(target2, list[1]); + Assert.Equal(target1, list[2]); + Assert.Equal(target1, list[3]); + Assert.Equal(target2, list[4]); + + // Remove handler3. + list.RemoveHandler(handler3); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target1, list[0]); + Assert.Equal(target2, list[1]); + Assert.Equal(target1, list[2]); + Assert.Equal(target2, list[3]); + + // Remove handler4. + list.RemoveHandler(handler4); + Assert.Equal(3, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target2, list[0]); + Assert.Equal(target1, list[1]); + Assert.Equal(target2, list[2]); + + // Remove handler5. + list.RemoveHandler(handler5); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target2, list[0]); + Assert.Equal(target2, list[1]); + + // Remove handler6. + list.RemoveHandler(handler6); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target2, list[0]); + + // Remove handler7. + list.RemoveHandler(handler7); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + // TODO: this causes a crash. +#if false + [Fact] + public void RemoveHandler_InvokeInUse_Success() + { + var list = new SubListenerList(); + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + list.AddHandler(handler); + + // Begin use. + Assert.False(list.BeginUse()); + + // Add. + list.RemoveHandler(handler); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } +#endif + + [Fact] + public void RemoveHandler_InvokeNoLongerInUse_Success() + { + var list = new SubListenerList(); + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + list.AddHandler(handler); + + // Begin use. + Assert.False(list.BeginUse()); + + // End use. + list.EndUse(); + + // Add. + list.RemoveHandler(handler); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void RemoveHandler_InvokeNoSuchHandlerEmpty_Success() + { + var list = new SubListenerList(); + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + + // Remove. + list.RemoveHandler(handler); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + + // Remove again. + list.RemoveHandler(handler); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void RemoveHandler_InvokeNoSuchHandlerNotEmpty_Success() + { + var list = new SubListenerList(); + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + list.AddHandler(handler); + + // Remove. + list.RemoveHandler(Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.SecondHandler))); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target, list[0]); + + // Remove again. + list.RemoveHandler(Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.SecondHandler))); + Assert.Equal(1, list.Count); + Assert.False(list.IsEmpty); + Assert.Equal(target, list[0]); + } + + [Theory] + [InlineData(1, 0)] + [InlineData(3, 0)] + [InlineData(3, 1)] + [InlineData(3, 2)] + [InlineData(4, 0)] + [InlineData(4, 1)] + [InlineData(4, 2)] + [InlineData(4, 3)] + [InlineData(6, 0)] + [InlineData(6, 1)] + [InlineData(6, 2)] + [InlineData(6, 3)] + [InlineData(6, 4)] + [InlineData(6, 5)] + [InlineData(7, 0)] + [InlineData(7, 1)] + [InlineData(7, 2)] + [InlineData(7, 3)] + [InlineData(7, 4)] + [InlineData(7, 5)] + public void RemoveHandler_InvokeDifferentSizes_Success(int count, int removeIndex) + { + // Test internal FrugalList implementation. + var handlers = new List(); + for (int i = 0; i < count; i++) + { + handlers.Add(Delegate.CreateDelegate(typeof(EventHandler), new CustomWeakEventListener(), nameof(CustomWeakEventListener.Handler))); + } + + var list = new SubListenerList(); + for (int i = 0; i < count; i++) + { + list.AddHandler(handlers[i]); + } + + // Remove. + list.RemoveHandler(handlers[removeIndex]); + + var expectedHandlers = new List(handlers); + expectedHandlers.RemoveAt(removeIndex); + Assert.Equal(expectedHandlers.Count, list.Count); + Assert.Equal(expectedHandlers.Count == 0, list.IsEmpty); + for (int i = 0; i < expectedHandlers.Count; i++) + { + Assert.Same(expectedHandlers[i].Target, list[i]); + } + + // Remove all. + for (int i = 0; i < expectedHandlers.Count; i++) + { + list.RemoveHandler(expectedHandlers[i]); + } + + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void RemoveHandler_NullHandler_ThrowsArgumentNullException() + { + var list = new SubListenerList(); + // TODO: this should throw ANE. + //Assert.Throws("handler", () => list.RemoveHandler(null)); + Assert.Throws(() => list.RemoveHandler(null)); + } + + private class SubListenerList : ListenerList + { + public SubListenerList() : base() + { + } + + public SubListenerList(int capacity) : base(capacity) + { + } + + public new void CopyTo(ListenerList newList) => base.CopyTo(newList); + } + + protected override void StartListening(object source) => throw new NotImplementedException(); + + protected override void StopListening(object source) => throw new NotImplementedException(); + } + + public class ListenerListTEventArgsTests : WeakEventManager + { + [Fact] + public void Ctor_Default() + { + var list = new SubListenerList(); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Theory] + [InlineData(-1)] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(5)] + [InlineData(6)] + [InlineData(7)] + public void Ctor_Int(int capacity) + { + var list = new SubListenerList(capacity); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + } + + [Fact] + public void Clone_InvokeEmptyToEmpty_Success() + { + var list = new SubListenerList(); + + ListenerList newList = Assert.IsType>(list.Clone()); + Assert.NotSame(list, newList); + Assert.Equal(0, list.Count); + Assert.True(list.IsEmpty); + Assert.Equal(0, newList.Count); + Assert.True(newList.IsEmpty); + } + + [Fact] + public void Clone_NotEmptyWithListenersToEmpty_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + list.Add(listener1); + list.Add(listener2); + + ListenerList newList = Assert.IsType>(list.Clone()); + Assert.NotSame(list, newList); + Assert.Equal(2, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(listener2, list[1]); + Assert.Equal(2, newList.Count); + Assert.False(newList.IsEmpty); + Assert.Same(listener1, newList[0]); + Assert.Same(listener2, newList[1]); + } + + [Fact] + public void Clone_NotEmptyWithHandlersToEmpty_Success() + { + var list = new SubListenerList(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + Delegate handler3 = (EventHandler)delegate { }; + Delegate handler4 = WeakEventManagerTests.StaticEventHandler; + + list.AddHandler(handler1); + list.AddHandler(handler2); + list.AddHandler(handler3); + list.AddHandler(handler4); + + ListenerList newList = Assert.IsType>(list.Clone()); + Assert.NotSame(list, newList); + Assert.Equal(4, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(target1, list[0]); + Assert.Same(target2, list[1]); + Assert.Throws(() => list[2]); + Assert.Throws(() => list[3]); + Assert.Equal(4, newList.Count); + Assert.False(newList.IsEmpty); + Assert.Same(target1, newList[0]); + Assert.Same(target2, newList[1]); + Assert.Throws(() => newList[2]); + Assert.Throws(() => newList[3]); + } + + [Fact] + public void Clone_NotEmptyWithListenersAndHandlersToEmpty_Success() + { + var list = new SubListenerList(); + var listener1 = new CustomWeakEventListener(); + var listener2 = new CustomWeakEventListener(); + var target1 = new CustomWeakEventListener(); + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), target1, nameof(CustomWeakEventListener.Handler)); + var target2 = new CustomWeakEventListener(); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), target2, nameof(CustomWeakEventListener.Handler)); + Delegate handler3 = (EventHandler)delegate { }; + Delegate handler4 = WeakEventManagerTests.StaticEventHandler; + + list.Add(listener1); + list.AddHandler(handler1); + list.Add(listener2); + list.AddHandler(handler2); + list.AddHandler(handler3); + list.AddHandler(handler4); + + ListenerList newList = Assert.IsType>(list.Clone()); + Assert.NotSame(list, newList); + Assert.Equal(6, list.Count); + Assert.False(list.IsEmpty); + Assert.Same(listener1, list[0]); + Assert.Same(target1, list[1]); + Assert.Same(listener2, list[2]); + Assert.Same(target2, list[3]); + Assert.Throws(() => list[4]); + Assert.Throws(() => list[5]); + Assert.Equal(6, newList.Count); + Assert.False(newList.IsEmpty); + Assert.Same(listener1, newList[0]); + Assert.Same(target1, newList[1]); + Assert.Same(listener2, newList[2]); + Assert.Same(target2, newList[3]); + Assert.Throws(() => newList[4]); + Assert.Throws(() => newList[5]); + } + + public static IEnumerable DeliverEvent_TestData() + { + yield return new object?[] { null, null, null }; + yield return new object?[] { new object(), new EventArgs(), typeof(int) }; + yield return new object?[] { new object(), EventArgs.Empty, typeof(int) }; + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithListeners_Success(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(managerType, t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener1"); + return true; + }; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(managerType, t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener2"); + return true; + }; + list.Add(listener1); + list.Add(listener2); + + Assert.False(list.DeliverEvent(sender, args, managerType)); + Assert.Equal(new string[] { "listener1", "listener2" }, events); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithHandlers_Success(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.HandlerAction = (s, e) => + { + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("handler1"); + }; + var listener2 = new CustomWeakEventListener(); + listener2.HandlerAction = (s, e) => + { + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("handler2"); + }; + var listener3 = new CustomWeakEventListener(); + listener3.HandlerAction = (s, e) => + { + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("handler3"); + }; + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), listener1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), listener2, nameof(CustomWeakEventListener.Handler)); + Delegate handler3 = Delegate.CreateDelegate(typeof(EventHandler), listener3, nameof(CustomWeakEventListener.Handler)); + list.AddHandler(handler1); + list.AddHandler(handler2); + list.AddHandler(handler3); + + Assert.False(list.DeliverEvent(sender, args, managerType)); + Assert.Equal(new string[] { "handler1", "handler2", "handler3" }, events); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithListenersAndHandlers_Success(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.HandlerAction = (s, e) => + { + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("handler1"); + }; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(managerType, t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener1"); + return true; + }; + var listener3 = new CustomWeakEventListener(); + listener3.HandlerAction = (s, e) => + { + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("handler2"); + }; + var listener4 = new CustomWeakEventListener(); + listener4.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(managerType, t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener2"); + return true; + }; + var listener5 = new CustomWeakEventListener(); + listener5.HandlerAction = (s, e) => + { + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("handler3"); + }; + Delegate handler1 = Delegate.CreateDelegate(typeof(EventHandler), listener1, nameof(CustomWeakEventListener.Handler)); + Delegate handler2 = Delegate.CreateDelegate(typeof(EventHandler), listener3, nameof(CustomWeakEventListener.Handler)); + Delegate handler3 = Delegate.CreateDelegate(typeof(EventHandler), listener5, nameof(CustomWeakEventListener.Handler)); + list.AddHandler(handler1); + list.Add(listener2); + list.AddHandler(handler2); + list.Add(listener4); + list.AddHandler(handler3); + + Assert.False(list.DeliverEvent(sender, args, managerType)); + Assert.Equal(new string[] { "handler1", "listener1", "handler2", "listener2", "handler3" }, events); + } + + // TODO: this causes a crash. +#if false + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithListenersNotHandled_Success(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + var events = new List(); + var listener1 = new CustomWeakEventListener(); + listener1.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(managerType, t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener1"); + return false; + }; + var listener2 = new CustomWeakEventListener(); + listener2.ReceiveWeakEventAction = (t, s, e) => + { + Assert.Same(managerType, t); + Assert.Same(sender, s); + Assert.Same(args, e); + events.Add("listener2"); + return true; + }; + list.Add(listener1); + list.Add(listener2); + + Assert.False(list.DeliverEvent(sender, args, managerType)); + Assert.Equal(new string[] { "listener1", "listener2" }, events); + } +#endif + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithInvalidHandlerAction_ThrowsInvalidCastException(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + Delegate handler = () => {}; + list.AddHandler(handler); + + Assert.Throws(() => list.DeliverEvent(sender, args, managerType)); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithInvalidHandlerFunc_ThrowsInvalidCastException(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + bool EventHandler(object sender, EventArgs args) => true; + list.AddHandler(EventHandler); + + Assert.Throws(() => list.DeliverEvent(sender, args, managerType)); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeWithInvalidHandlerEventHandler_ThrowsInvalidCastException(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + var target = new CustomWeakEventListener(); + Delegate handler = Delegate.CreateDelegate(typeof(EventHandler), target, nameof(CustomWeakEventListener.Handler)); + list.AddHandler(handler); + + Assert.Throws(() => list.DeliverEvent(sender, args, managerType)); + } + + [Theory] + [MemberData(nameof(DeliverEvent_TestData))] + public void DeliverEvent_InvokeEmpty_ReturnsFalse(object sender, EventArgs args, Type managerType) + { + var list = new SubListenerList(); + Assert.False(list.DeliverEvent(sender, args, managerType)); + } + + private class SubListenerList : ListenerList where TEventArgs : EventArgs + { + public SubListenerList() : base() + { + } + + public SubListenerList(int capacity) : base(capacity) + { + } + } + + protected override void StartListening(object source) => throw new NotImplementedException(); + + protected override void StopListening(object source) => throw new NotImplementedException(); + } + + private static void StaticEventHandler(object sender, EventArgs e) + { + } + + private class CustomListenerList : ListenerList + { + public Func? CloneAction { get; set; } + + public override ListenerList Clone() => CloneAction!.Invoke(); + + public Func? DeliverEventAction { get; set; } + + public override bool DeliverEvent(object sender, EventArgs e, Type managerType) + { + if (DeliverEventAction is null) + { + return base.DeliverEvent(sender, e, managerType); + } + + return DeliverEventAction(sender, e, managerType); + } + } + + private class CustomWeakEventListener : IWeakEventListener + { + public Func? ReceiveWeakEventAction { get; set; } + + public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e) + { + if (ReceiveWeakEventAction is null) + { + throw new NotImplementedException(); + } + + return ReceiveWeakEventAction(managerType, sender, e); + } + + public Action? HandlerAction { get; set; } + + public void Handler(object sender, EventArgs e) + { + if (HandlerAction is null) + { + throw new NotImplementedException(); + } + + HandlerAction(sender, e); + } + + public void SecondHandler(object sender, EventArgs e) => throw new NotImplementedException(); + } + + private class SubWeakEventManager : WeakEventManager + { + public SubWeakEventManager() + { + } + + public new IDisposable ReadLock => base.ReadLock; + + public new IDisposable WriteLock => base.WriteLock; + + public new object this[object source] + { + get => base[source]; + set => base[source] = value; + } + + public new void DeliverEvent(object sender, EventArgs args) => base.DeliverEvent(sender, args); + + public new void DeliverEventToList(object sender, EventArgs args, ListenerList list) => base.DeliverEventToList(sender, args, list); + + public new static WeakEventManager GetCurrentManager(Type managerType) => WeakEventManager.GetCurrentManager(managerType); + + public new ListenerList NewListenerList() => base.NewListenerList(); + + public new void ProtectedAddHandler(object source, Delegate handler) => base.ProtectedAddHandler(source, handler); + + public new void ProtectedAddListener(object source, IWeakEventListener listener) => base.ProtectedAddListener(source, listener); + + public new void ProtectedRemoveHandler(object source, Delegate handler) => base.ProtectedRemoveHandler(source, handler); + + public new void ProtectedRemoveListener(object source, IWeakEventListener listener) => base.ProtectedRemoveListener(source, listener); + + public new bool Purge(object source, object data, bool purgeAll) => base.Purge(source, data, purgeAll); + + public new void Remove(object source) => base.Remove(source); + + public new static void SetCurrentManager(Type managerType, WeakEventManager? newManager) => WeakEventManager.SetCurrentManager(managerType, newManager); + + public new void ScheduleCleanup() => base.ScheduleCleanup(); + + public Action? StartListeningAction { get; set; } + + protected override void StartListening(object source) + { + if (StartListeningAction is null) + { + throw new NotImplementedException(); + } + + StartListeningAction(source); + } + + public Action? StopListeningAction { get; set; } + + protected override void StopListening(object source) + { + if (StopListeningAction is null) + { + throw new NotImplementedException(); + } + + StopListeningAction(source); + } + } + + protected override void StartListening(object source) => throw new NotImplementedException(); + + protected override void StopListening(object source) => throw new NotImplementedException(); +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/Tests to Add.md b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/Tests to Add.md new file mode 100644 index 00000000000..71583514a63 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/Tests to Add.md @@ -0,0 +1,25 @@ +# Tests to add +- System.ComponentModel.DependencyPropertyDescriptor +- System.IO.Packaging.EncryptedPackageEnvelope +- System.IO.Packaging.PackageDigitalSignature +- System.IO.Packaging.PackageDigitalSignatureManager +- System.IO.Packaging.RightsManagementInformation +- System.Security.RightsManagement.CryptoProvider +- System.Security.RightsManagement.PublishLicense +- System.Security.RightsManagement.SecureEnvironment +- System.Security.RightsManagement.UnsignedPublishLicense +- System.Security.RightsManagement.UseLicense +- System.Windows.Interop.ComponentDispatcher +- System.Windows.Threading.Dispatcher +- System.Windows.Threading.DispatcherEventArgs +- System.Windows.Threading.DispatcherUnhandledExceptionEventArgs +- System.Windows.Threading.DispatcherUnhandledExceptionFilterEventArgs +- System.Windows.Threading.DispatcherFrame +- System.Windows.Threading.DispatcherHooks +- System.Windows.Threading.DispatcherOperation +- System.Windows.Threading.DispatcherSynchronizationContext +- System.Windows.Threading.DispatcherTimer +- System.Windows.AttachedPropertyBrowsableForTypeAttribute +- System.Windows.AttachedPropertyBrowsableWhenAttributePresentAttribute +- System.Windows.DependencyProperty +- System.Windows.SplashScreen diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/Usings.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/Usings.cs new file mode 100644 index 00000000000..9dc6920eea2 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/Usings.cs @@ -0,0 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +global using System.Collections.Generic; +global using Xunit; diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/WindowsBase.Tests.csproj b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/WindowsBase.Tests.csproj new file mode 100644 index 00000000000..e0408eecb93 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/WindowsBase.Tests.csproj @@ -0,0 +1,23 @@ + + + + WindowsBase.Tests + true + true + enable + + + + + + + + + + + + + + + +