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