diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/PresentationCore.Tests.csproj b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/PresentationCore.Tests.csproj index 8e587cb354e..33286c20684 100644 --- a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/PresentationCore.Tests.csproj +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/PresentationCore.Tests.csproj @@ -10,6 +10,7 @@ $(NoWarn);SYSLIB5005 true + $(TargetFramework)-windows @@ -19,6 +20,7 @@ + @@ -29,6 +31,7 @@ + diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/System/Windows/ClipboardTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/System/Windows/ClipboardTests.cs new file mode 100644 index 00000000000..d1c85eea300 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/System/Windows/ClipboardTests.cs @@ -0,0 +1,245 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Specialized; +using System.Runtime.InteropServices; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Color = System.Windows.Media.Color; + +namespace System.Windows; + +// Note: each registered Clipboard format is an OS singleton +// and we should not run this test at the same time as other tests using the same format. +[Collection("Sequential")] +[UISettings(MaxAttempts = 3)] +public class ClipboardTests +{ + [WpfFact] + public void SetText_InvokeString_GetReturnsExpected() + { + Clipboard.SetText("text"); + Clipboard.GetText().Should().Be("text"); + Clipboard.ContainsText().Should().BeTrue(); + } + + [WpfFact] + public void SetAudio_InvokeByteArray_GetReturnsExpected() + { + byte[] audioBytes = [1, 2, 3]; + Clipboard.SetAudio(audioBytes); + + Clipboard.GetAudioStream().Should().BeOfType().Which.ToArray().Should().Equal(audioBytes); + Clipboard.GetData(DataFormats.WaveAudio).Should().BeOfType().Which.ToArray().Should().Equal(audioBytes); + Clipboard.ContainsAudio().Should().BeTrue(); + Clipboard.ContainsData(DataFormats.WaveAudio).Should().BeTrue(); + } + + [WpfFact(Skip = "WinForms difference")] + public void SetAudio_InvokeEmptyByteArray_GetReturnsExpected() + { + byte[] audioBytes = Array.Empty(); + Clipboard.SetAudio(audioBytes); + + // Currently fails with CLIPBRD_E_BAD_DATA + Clipboard.GetAudioStream().Should().BeNull(); + Clipboard.GetData(DataFormats.WaveAudio).Should().BeNull(); + Clipboard.ContainsAudio().Should().BeTrue(); + Clipboard.ContainsData(DataFormats.WaveAudio).Should().BeTrue(); + } + + [WpfFact] + public void SetAudio_NullAudioBytes_ThrowsArgumentNullException() + { + Action action = () => Clipboard.SetAudio((byte[])null!); + action.Should().Throw().WithParameterName("audioBytes"); + } + + [WpfFact] + public void Clipboard_SetAudio_InvokeStream_GetReturnsExpected() + { + byte[] audioBytes = [1, 2, 3]; + using MemoryStream audioStream = new(audioBytes); + Clipboard.SetAudio(audioStream); + + Clipboard.GetAudioStream().Should().BeOfType().Which.ToArray().Should().Equal(audioBytes); + Clipboard.GetData(DataFormats.WaveAudio).Should().BeOfType().Which.ToArray().Should().Equal(audioBytes); + Clipboard.ContainsAudio().Should().BeTrue(); + Clipboard.ContainsData(DataFormats.WaveAudio).Should().BeTrue(); + } + + [WpfFact(Skip = "WinForms difference")] + public void SetAudio_InvokeEmptyStream_GetReturnsExpected() + { + using MemoryStream audioStream = new(); + Clipboard.SetAudio(audioStream); + + // Currently fails with CLIPBRD_E_BAD_DATA + Clipboard.GetAudioStream().Should().BeNull(); + Clipboard.GetData(DataFormats.WaveAudio).Should().BeNull(); + Clipboard.ContainsAudio().Should().BeTrue(); + Clipboard.ContainsData(DataFormats.WaveAudio).Should().BeTrue(); + } + + [WpfFact] + public void SetAudio_NullAudioStream_ThrowsArgumentNullException() + { + Action action = () => Clipboard.SetAudio((Stream)null!); + action.Should().Throw().WithParameterName("audioStream"); + } + + [WpfTheory] + // These three fail in WinForms, should probably fail in WPF as well. + // [InlineData("")] + // [InlineData(" ")] + // [InlineData("\t")] + [InlineData(null)] + public void SetData_EmptyOrWhitespaceFormat_ThrowsArgumentException(string? format) + { + Action action = () => Clipboard.SetData(format!, "data"); + action.Should().Throw().WithParameterName("format"); + } + + [WpfFact] + public void SetData_Null_Throws() + { + Action action = () => Clipboard.SetData("MyData", data: null!); + action.Should().Throw().WithParameterName("data"); + } + + [WpfFact] + public void SetData_NullData_ThrowsArgumentNullException() + { + Action action = () => Clipboard.SetData("MyData", data: null!); + action.Should().Throw().WithParameterName("data"); + } + + [WpfFact] + public void SetData_Int_GetReturnsExpected() + { + Clipboard.SetData("format", 1); + Clipboard.GetData("format").Should().Be(1); + Clipboard.ContainsData("format").Should().BeTrue(); + } + + [WpfFact] + public void SetFileDropList_Invoke_GetReturnsExpected() + { + StringCollection filePaths = + [ + "filePath", + "filePath2" + ]; + + Clipboard.SetFileDropList(filePaths); + + Clipboard.GetFileDropList().Should().BeEquivalentTo(filePaths); + Clipboard.ContainsFileDropList().Should().BeTrue(); + } + + [WpfFact] + public void SetFileDropList_NullFilePaths_ThrowsArgumentNullException() + { + Action action = () => Clipboard.SetFileDropList(null!); + // Note: The name will change with the WinForms shared code. + action.Should().Throw().WithParameterName("fileDropList"); + } + + [WpfFact] + public void SetFileDropList_EmptyFilePaths_ThrowsArgumentException() + { + Action action = static () => Clipboard.SetFileDropList([]); + action.Should().Throw(); + } + + [WpfTheory] + [InlineData("")] + [InlineData("\0")] + public void SetFileDropList_InvalidFileInPaths_ThrowsArgumentException(string filePath) + { + StringCollection filePaths = + [ + filePath + ]; + + Action action = () => Clipboard.SetFileDropList(filePaths); + action.Should().Throw(); + } + + [WpfFact] + public unsafe void SetImage_InvokeBitmap_VerifyPixelColor() + { + WriteableBitmap bitmap = new(10, 10, 96, 96, PixelFormats.Bgra32, palette: null); + + // Set a specific pixel to a given color (e.g., set pixel at (1, 2) to red) + Color color = Colors.Red; + byte[] colorData = [color.B, color.G, color.R, color.A]; + bitmap.WritePixels(new Int32Rect(1, 2, 1, 1), colorData, 4, 0); + + Clipboard.SetImage(bitmap); + Clipboard.ContainsImage().Should().BeTrue(); + InteropBitmap result = Clipboard.GetImage().Should().BeOfType().Subject; + + // Verify the pixel color + byte[] resultColorData = new byte[4]; + result.CopyPixels(new Int32Rect(1, 2, 1, 1), resultColorData, 4, 0); + resultColorData.Should().Equal(colorData); + + // Set back the image we just got from the clipboard + Clipboard.SetImage(result); + Clipboard.ContainsImage().Should().BeTrue(); + result = Clipboard.GetImage().Should().BeOfType().Subject; + + // Verify the pixel color + result.CopyPixels(new Int32Rect(1, 2, 1, 1), resultColorData, 4, 0); + resultColorData.Should().Equal(colorData); + } + + [WpfTheory] + [BoolData] + public void SetDataObject_WithMultipleData(bool copy) + { + string testData1 = "test data one"; + int testData2 = 42; + DataObject data = new(); + data.SetData("testData1", testData1); + data.SetData("testData2", testData2); + Clipboard.SetDataObject(data, copy); + + object? result1 = Clipboard.GetData("testData1"); + result1.Should().Be(testData1); + object? result2 = Clipboard.GetData("testData2"); + result2.Should().Be(testData2); + } + + [WpfFact] + public void SetData_Text_Format_AllUpper() + { + Clipboard.SetData("TEXT", "Hello, World!"); + Clipboard.ContainsText().Should().BeTrue(); + Clipboard.ContainsData("TEXT").Should().BeTrue(); + Clipboard.ContainsData(DataFormats.Text).Should().BeTrue(); + Clipboard.ContainsData(DataFormats.UnicodeText).Should().BeTrue(); + + IDataObject dataObject = Clipboard.GetDataObject().Should().BeAssignableTo().Subject; + string[] formats = dataObject.GetFormats(); + formats.Should().BeEquivalentTo(["System.String", "UnicodeText", "Text"]); + + formats = dataObject.GetFormats(autoConvert: false); + formats.Should().BeEquivalentTo(["Text"]); + + // CLIPBRD_E_BAD_DATA returned when trying to get clipboard data. This will no longer throw when using + // the shared clipboard code. + Action action = () => Clipboard.GetText().Should().BeEmpty(); + action.Should().Throw(); + action = () => Clipboard.GetText(TextDataFormat.Text).Should().BeEmpty(); + action.Should().Throw(); + action = () => Clipboard.GetText(TextDataFormat.UnicodeText).Should().BeEmpty(); + action.Should().Throw(); + + Clipboard.GetData("System.String").Should().BeNull(); + action = () => Clipboard.GetData("TEXT").Should().BeNull(); + action.Should().Throw(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/TestUtilities/AppContextSwitchNames.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/TestUtilities/AppContextSwitchNames.cs deleted file mode 100644 index caaa017208d..00000000000 --- a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/TestUtilities/AppContextSwitchNames.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.Serialization.Formatters.Binary; - -namespace System; - -public static class AppContextSwitchNames -{ - /// - /// The switch that controls whether or not the is enabled. - /// - public static string EnableUnsafeBinaryFormatterSerialization { get; } - = "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization"; - - /// - /// Switch that controls switch caching. - /// - public static string LocalAppContext_DisableCaching { get; } - = "TestSwitch.LocalAppContext.DisableCaching"; -} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/TestUtilities/AppContextSwitchScope.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/TestUtilities/AppContextSwitchScope.cs deleted file mode 100644 index 65087fda71e..00000000000 --- a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/TestUtilities/AppContextSwitchScope.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System; - -/// -/// Scope for temporarily setting an switch. Use in a statement. -/// -/// -/// -/// It is recommended to create wrappers for this struct for both simplicity and to allow adding synchronization. -/// See for an example of doing this. -/// -/// -public readonly ref struct AppContextSwitchScope -{ - private readonly string _switchName; - private readonly bool _originalState; - - public AppContextSwitchScope(string switchName, bool enable) - { - if (!AppContext.TryGetSwitch(AppContextSwitchNames.LocalAppContext_DisableCaching, out bool isEnabled) - || !isEnabled) - { - // It doesn't make sense to try messing with AppContext switches if they are going to be cached. - throw new InvalidOperationException("LocalAppContext switch caching is not disabled."); - } - - AppContext.TryGetSwitch(switchName, out _originalState); - - AppContext.SetSwitch(switchName, enable); - if (!AppContext.TryGetSwitch(switchName, out isEnabled) || isEnabled != enable) - { - throw new InvalidOperationException($"Could not set {switchName} to {enable}."); - } - - _switchName = switchName; - } - - public void Dispose() - { - AppContext.SetSwitch(_switchName, _originalState); - if (!AppContext.TryGetSwitch(_switchName, out bool isEnabled) || isEnabled != _originalState) - { - throw new InvalidOperationException($"Could not reset {_switchName} to {_originalState}."); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/TestUtilities/BinaryFormatterScope.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/TestUtilities/BinaryFormatterScope.cs deleted file mode 100644 index 3c73e69c7cf..00000000000 --- a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/TestUtilities/BinaryFormatterScope.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.Serialization.Formatters.Binary; - -namespace System; - -/// -/// Scope for enabling / disabling the . Use in a statement. -/// -public readonly ref struct BinaryFormatterScope -{ - private readonly AppContextSwitchScope _switchScope; - - public BinaryFormatterScope(bool enable) - { - // Prevent multiple BinaryFormatterScopes from running simultaneously. Using Monitor to allow recursion on - // the same thread. - Monitor.Enter(typeof(BinaryFormatterScope)); - _switchScope = new AppContextSwitchScope(AppContextSwitchNames.EnableUnsafeBinaryFormatterSerialization, enable); - } - - public void Dispose() - { - try - { - _switchScope.Dispose(); - } - finally - { - Monitor.Exit(typeof(BinaryFormatterScope)); - } - } - - static BinaryFormatterScope() - { - // Need to explicitly set the switch to whatever the default is as its default value is in transition. - -#pragma warning disable SYSLIB0011 // Type or member is obsolete - BinaryFormatter formatter = new(); -#pragma warning restore SYSLIB0011 // Type or member is obsolete - try - { -#pragma warning disable SYSLIB0011 // Type or member is obsolete - formatter.Serialize(null!, null!); -#pragma warning restore SYSLIB0011 - } - catch (NotSupportedException) - { - AppContext.SetSwitch(AppContextSwitchNames.EnableUnsafeBinaryFormatterSerialization, false); - return; - } - catch (ArgumentNullException) - { - AppContext.SetSwitch(AppContextSwitchNames.EnableUnsafeBinaryFormatterSerialization, true); - return; - } - - throw new InvalidOperationException(); - } -} \ No newline at end of file