Skip to content

Commit

Permalink
Adding custom DataObject Tests (dotnet#12807)
Browse files Browse the repository at this point in the history
Co-authored-by: Jeremy Kuhne <[email protected]>
  • Loading branch information
Tanya-Solyanik and JeremyKuhne authored Jan 24, 2025
1 parent e3f172f commit df75fe5
Show file tree
Hide file tree
Showing 13 changed files with 319 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RootNamespace>System.Windows.Forms.Analyzers</RootNamespace>
<RootNamespace/>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>Preview</LangVersion>
<Nullable>enable</Nullable>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RootNamespace>System.Windows.Forms.Analyzers</RootNamespace>
<RootNamespace/>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>Preview</LangVersion>
<Nullable>enable</Nullable>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RootNamespace>System.Windows.Forms.Analyzers</RootNamespace>
<RootNamespace/>
<TargetFramework>netstandard2.0</TargetFramework>
<Deterministic>true</Deterministic>
<RootNamespace></RootNamespace>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RootNamespace>System.Windows.Forms.Analyzers</RootNamespace>
<RootNamespace/>
<TargetFramework>netstandard2.0</TargetFramework>
<Deterministic>true</Deterministic>
<RootNamespace></RootNamespace>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace />
<LangVersion>Preview</LangVersion>
<Nullable>enable</Nullable>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using ComTypes = System.Runtime.InteropServices.ComTypes;

namespace System.Windows.Forms.TestUtilities;

internal class ManagedAndRuntimeDataObject : ManagedDataObject, ComTypes.IDataObject
{
public int DAdvise(ref ComTypes.FORMATETC pFormatetc, ComTypes.ADVF advf, ComTypes.IAdviseSink adviseSink, out int connection) => throw new NotImplementedException();
public void DUnadvise(int connection) => throw new NotImplementedException();
public int EnumDAdvise(out ComTypes.IEnumSTATDATA enumAdvise) => throw new NotImplementedException();
public ComTypes.IEnumFORMATETC EnumFormatEtc(ComTypes.DATADIR direction) => throw new NotImplementedException();
public int GetCanonicalFormatEtc(ref ComTypes.FORMATETC formatIn, out ComTypes.FORMATETC formatOut) => throw new NotImplementedException();
public void GetData(ref ComTypes.FORMATETC format, out ComTypes.STGMEDIUM medium) => throw new NotImplementedException();
public void GetDataHere(ref ComTypes.FORMATETC format, ref ComTypes.STGMEDIUM medium) => throw new NotImplementedException();
public int QueryGetData(ref ComTypes.FORMATETC format) => throw new NotImplementedException();
public void SetData(ref ComTypes.FORMATETC formatIn, ref ComTypes.STGMEDIUM medium, bool release) => throw new NotImplementedException();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

namespace System.Windows.Forms.TestUtilities;

internal class ManagedDataObject : IDataObject
{
public static string s_format = nameof(SerializableTestData);
protected SerializableTestData? _data;

public object? GetData(string format, bool autoConvert) => format == s_format ? _data : null;
public object? GetData(string format) => format == s_format ? _data : null;
public object? GetData(Type format) => null;
public bool GetDataPresent(string format, bool autoConvert) => format == s_format && _data is not null;
public bool GetDataPresent(string format) => format == s_format && _data is not null;
public bool GetDataPresent(Type format) => false;
public string[] GetFormats(bool autoConvert) => [s_format];
public string[] GetFormats() => [s_format];
public void SetData(string format, bool autoConvert, object? data)
{
if (format == s_format)
{
_data = data as SerializableTestData;
}
}

public void SetData(string format, object? data)
{
if (format == s_format)
{
_data = data as SerializableTestData;
}
}

public void SetData(Type format, object? data) => _data = data as SerializableTestData;
public void SetData(object? data) => _data = data as SerializableTestData;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

namespace System.Windows.Forms.TestUtilities;

[Serializable]
public class SerializableTestData
{
public string Text { get; } = "a";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System.Diagnostics.CodeAnalysis;
using System.Reflection.Metadata;

namespace System.Windows.Forms.TestUtilities;

internal class TypedAndRuntimeDataObject : ManagedAndRuntimeDataObject, ITypedDataObject
{
public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>([MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
throw new NotImplementedException();
public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string format, [MaybeNullWhen(false), NotNullWhen(true)] out T data)
{
data = default;
if (format == s_format && _data is T t)
{
data = t;
return true;
}

return false;
}

public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string format, bool autoConvert, [MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
throw new NotImplementedException();
public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string format, Func<TypeName, Type> resolver, bool autoConvert, [MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
throw new NotImplementedException();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System.Diagnostics.CodeAnalysis;
using System.Reflection.Metadata;

namespace System.Windows.Forms.TestUtilities;

internal class TypedDataObject : ManagedDataObject, ITypedDataObject
{
public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>([MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
throw new NotImplementedException();
public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string format, [MaybeNullWhen(false), NotNullWhen(true)] out T data)
{
data = default;
if (format == s_format && _data is T t)
{
data = t;
return true;
}

return false;
}

public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string format, bool autoConvert, [MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
throw new NotImplementedException();
public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string format, Func<TypeName, Type> resolver, bool autoConvert, [MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
throw new NotImplementedException();
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Windows.Forms.Primitives;
using System.Windows.Forms.TestUtilities;
using Windows.Win32.System.Ole;
using static System.Windows.Forms.Tests.BinaryFormatUtilitiesTests;
using static System.Windows.Forms.TestUtilities.DataObjectTestHelpers;
Expand All @@ -23,7 +24,8 @@ namespace System.Windows.Forms.Tests;
// 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)] // Try up to 3 times before failing.
// Try up to 3 times before failing.
[UISettings(MaxAttempts = 3)]
public class ClipboardTests
{
#pragma warning disable WFDEV005 // Type or member is obsolete
Expand Down Expand Up @@ -1110,14 +1112,19 @@ public void Clipboard_CustomDataObject_AvoidBinaryFormatter(bool copy)
// Pasting in different process has been simulated. Manual Json deserialization will need to occur.
IDataObject received = Clipboard.GetDataObject().Should().BeAssignableTo<IDataObject>().Subject;
received.Should().NotBe(jsonDataObject);
received.Should().BeAssignableTo<ITypedDataObject>();
byte[] jsonBytes = Clipboard.GetData(format).Should().BeOfType<byte[]>().Subject;
JsonSerializer.Deserialize(jsonBytes, typeof(SimpleTestData)).Should().BeEquivalentTo(data);
received.TryGetData(format, out byte[]? jsonBytes1).Should().BeTrue();
jsonBytes1.Should().BeEquivalentTo(jsonBytes);
}
else
{
JsonDataObject received = Clipboard.GetDataObject().Should().BeOfType<JsonDataObject>().Subject;
received.Should().Be(jsonDataObject);
received.Deserialize<SimpleTestData>(format).Should().BeEquivalentTo(data);
Action tryGetData = () => received.TryGetData(format, out byte[]? _);
tryGetData.Should().Throw<NotSupportedException>();
}
}

Expand Down Expand Up @@ -1288,4 +1295,90 @@ public class SomeDataObject : DataObject
public override bool GetDataPresent(string format, bool autoConvert)
=> format == Format || base.GetDataPresent(format, autoConvert);
}

[WinFormsTheory]
[BoolData]
public void Clipboard_RoundTrip_DataObject_SupportsTypedInterface(bool copy) =>
CustomDataObject_RoundTrip_SupportsTypedInterface<DataObject>(copy);

[WinFormsTheory]
[BoolData]
public void Clipboard_RoundTrip_ManagedAndRuntimeDataObject_SupportsTypedInterface(bool copy) =>
CustomDataObject_RoundTrip_SupportsTypedInterface<ManagedAndRuntimeDataObject>(copy);

[WinFormsTheory]
[BoolData]
public void Clipboard_RoundTrip_TypedAndRuntimeDataObject_SupportsTypedInterface(bool copy) =>
CustomDataObject_RoundTrip_SupportsTypedInterface<TypedAndRuntimeDataObject>(copy);

[WinFormsTheory]
[BoolData]
public void Clipboard_RoundTrip_TypedDataObject_SupportsTypedInterface(bool copy) =>
CustomDataObject_RoundTrip_SupportsTypedInterface<TypedDataObject>(copy);

[WinFormsTheory]
[BoolData]
public void Clipboard_RoundTrip_ManagedDataObject_SupportsTypedInterface(bool copy) =>
CustomDataObject_RoundTrip_SupportsTypedInterface<ManagedDataObject>(copy);

[WinFormsTheory]
[BoolData]
public void Clipboard_RoundTrip_Object_SupportsTypedInterface(bool copy)
{
SerializableTestData data = new();
string format = typeof(SerializableTestData).FullName!;

// Opt-in into access to the binary formatted stream.
using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: true);
// We need the BinaryFormatter to flush the data from the managed object to the HGLOBAL
// and to write data to HGLOBAL as a binary formatted stream now if it hadn't been flushed.
using BinaryFormatterScope scope = new(enable: true);

Clipboard.SetDataObject(data, copy);

DataObject received = Clipboard.GetDataObject().Should().BeOfType<DataObject>().Subject;

received.TryGetData(format, out SerializableTestData? result).Should().BeTrue();
result.Should().BeEquivalentTo(data);

Clipboard.TryGetData(format, out result).Should().BeTrue();
result.Should().BeEquivalentTo(data);
}

private static void CustomDataObject_RoundTrip_SupportsTypedInterface<T>(bool copy) where T : IDataObject, new()
{
SerializableTestData data = new();
T testDataObject = new();
string format = ManagedDataObject.s_format;
testDataObject.SetData(format, data);

// Opt-in into access the binary formatted stream.
using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: copy);
// We need the BinaryFormatter to flush the data from the managed object to the HGLOBAL.
using (BinaryFormatterScope scope = new(enable: copy))
{
Clipboard.SetDataObject(testDataObject, copy);
}

// copy == true => data was flushed to HGLOBAL and we read it with a WinForms DataObject.
// Otherwise this is the user-implemented ITypedDataObject or the WinForms wrapper.
if (copy || typeof(T).IsAssignableTo(typeof(ITypedDataObject)))
{
ITypedDataObject received = Clipboard.GetDataObject().Should().BeAssignableTo<ITypedDataObject>().Subject;

received.TryGetData(format, out SerializableTestData? result).Should().BeTrue();
result.Should().BeEquivalentTo(data);

Clipboard.TryGetData(format, out result).Should().BeTrue();
result.Should().BeEquivalentTo(data);
}
else
{
T received = Clipboard.GetDataObject().Should().BeOfType<T>().Subject;
received.Should().Be(testDataObject);
// When we are not flushing the data to the HGLOBAL, we are reading from our DataStore or the managed test data object.
Action tryGetData = () => received.TryGetData(format, out SerializableTestData? result);
tryGetData.Should().Throw<NotSupportedException>();
}
}
}
Loading

0 comments on commit df75fe5

Please sign in to comment.