Skip to content

Commit df75fe5

Browse files
Adding custom DataObject Tests (#12807)
Co-authored-by: Jeremy Kuhne <[email protected]>
1 parent e3f172f commit df75fe5

File tree

13 files changed

+319
-10
lines changed

13 files changed

+319
-10
lines changed

src/System.Windows.Forms.Analyzers.CSharp/src/System.Windows.Forms.Analyzers.CSharp.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<RootNamespace>System.Windows.Forms.Analyzers</RootNamespace>
4+
<RootNamespace/>
55
<TargetFramework>netstandard2.0</TargetFramework>
66
<LangVersion>Preview</LangVersion>
77
<Nullable>enable</Nullable>

src/System.Windows.Forms.Analyzers.CodeFixes.CSharp/System.Windows.Forms.Analyzers.CodeFixes.CSharp.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<RootNamespace>System.Windows.Forms.Analyzers</RootNamespace>
4+
<RootNamespace/>
55
<TargetFramework>netstandard2.0</TargetFramework>
66
<LangVersion>Preview</LangVersion>
77
<Nullable>enable</Nullable>

src/System.Windows.Forms.Analyzers.CodeFixes.VisualBasic/System.Windows.Forms.Analyzers.CodeFixes.VisualBasic.vbproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<RootNamespace>System.Windows.Forms.Analyzers</RootNamespace>
4+
<RootNamespace/>
55
<TargetFramework>netstandard2.0</TargetFramework>
66
<Deterministic>true</Deterministic>
77
<RootNamespace></RootNamespace>

src/System.Windows.Forms.Analyzers.VisualBasic/src/System.Windows.Forms.Analyzers.VisualBasic.vbproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<RootNamespace>System.Windows.Forms.Analyzers</RootNamespace>
4+
<RootNamespace/>
55
<TargetFramework>netstandard2.0</TargetFramework>
66
<Deterministic>true</Deterministic>
77
<RootNamespace></RootNamespace>

src/System.Windows.Forms.Analyzers/src/System.Windows.Forms.Analyzers.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
5+
<RootNamespace />
56
<LangVersion>Preview</LangVersion>
67
<Nullable>enable</Nullable>
78
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#nullable enable
5+
6+
using ComTypes = System.Runtime.InteropServices.ComTypes;
7+
8+
namespace System.Windows.Forms.TestUtilities;
9+
10+
internal class ManagedAndRuntimeDataObject : ManagedDataObject, ComTypes.IDataObject
11+
{
12+
public int DAdvise(ref ComTypes.FORMATETC pFormatetc, ComTypes.ADVF advf, ComTypes.IAdviseSink adviseSink, out int connection) => throw new NotImplementedException();
13+
public void DUnadvise(int connection) => throw new NotImplementedException();
14+
public int EnumDAdvise(out ComTypes.IEnumSTATDATA enumAdvise) => throw new NotImplementedException();
15+
public ComTypes.IEnumFORMATETC EnumFormatEtc(ComTypes.DATADIR direction) => throw new NotImplementedException();
16+
public int GetCanonicalFormatEtc(ref ComTypes.FORMATETC formatIn, out ComTypes.FORMATETC formatOut) => throw new NotImplementedException();
17+
public void GetData(ref ComTypes.FORMATETC format, out ComTypes.STGMEDIUM medium) => throw new NotImplementedException();
18+
public void GetDataHere(ref ComTypes.FORMATETC format, ref ComTypes.STGMEDIUM medium) => throw new NotImplementedException();
19+
public int QueryGetData(ref ComTypes.FORMATETC format) => throw new NotImplementedException();
20+
public void SetData(ref ComTypes.FORMATETC formatIn, ref ComTypes.STGMEDIUM medium, bool release) => throw new NotImplementedException();
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#nullable enable
5+
6+
namespace System.Windows.Forms.TestUtilities;
7+
8+
internal class ManagedDataObject : IDataObject
9+
{
10+
public static string s_format = nameof(SerializableTestData);
11+
protected SerializableTestData? _data;
12+
13+
public object? GetData(string format, bool autoConvert) => format == s_format ? _data : null;
14+
public object? GetData(string format) => format == s_format ? _data : null;
15+
public object? GetData(Type format) => null;
16+
public bool GetDataPresent(string format, bool autoConvert) => format == s_format && _data is not null;
17+
public bool GetDataPresent(string format) => format == s_format && _data is not null;
18+
public bool GetDataPresent(Type format) => false;
19+
public string[] GetFormats(bool autoConvert) => [s_format];
20+
public string[] GetFormats() => [s_format];
21+
public void SetData(string format, bool autoConvert, object? data)
22+
{
23+
if (format == s_format)
24+
{
25+
_data = data as SerializableTestData;
26+
}
27+
}
28+
29+
public void SetData(string format, object? data)
30+
{
31+
if (format == s_format)
32+
{
33+
_data = data as SerializableTestData;
34+
}
35+
}
36+
37+
public void SetData(Type format, object? data) => _data = data as SerializableTestData;
38+
public void SetData(object? data) => _data = data as SerializableTestData;
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#nullable enable
5+
6+
namespace System.Windows.Forms.TestUtilities;
7+
8+
[Serializable]
9+
public class SerializableTestData
10+
{
11+
public string Text { get; } = "a";
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#nullable enable
5+
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Reflection.Metadata;
8+
9+
namespace System.Windows.Forms.TestUtilities;
10+
11+
internal class TypedAndRuntimeDataObject : ManagedAndRuntimeDataObject, ITypedDataObject
12+
{
13+
public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>([MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
14+
throw new NotImplementedException();
15+
public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string format, [MaybeNullWhen(false), NotNullWhen(true)] out T data)
16+
{
17+
data = default;
18+
if (format == s_format && _data is T t)
19+
{
20+
data = t;
21+
return true;
22+
}
23+
24+
return false;
25+
}
26+
27+
public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string format, bool autoConvert, [MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
28+
throw new NotImplementedException();
29+
public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string format, Func<TypeName, Type> resolver, bool autoConvert, [MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
30+
throw new NotImplementedException();
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#nullable enable
5+
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Reflection.Metadata;
8+
9+
namespace System.Windows.Forms.TestUtilities;
10+
11+
internal class TypedDataObject : ManagedDataObject, ITypedDataObject
12+
{
13+
public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>([MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
14+
throw new NotImplementedException();
15+
public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string format, [MaybeNullWhen(false), NotNullWhen(true)] out T data)
16+
{
17+
data = default;
18+
if (format == s_format && _data is T t)
19+
{
20+
data = t;
21+
return true;
22+
}
23+
24+
return false;
25+
}
26+
27+
public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string format, bool autoConvert, [MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
28+
throw new NotImplementedException();
29+
public bool TryGetData<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(string format, Func<TypeName, Type> resolver, bool autoConvert, [MaybeNullWhen(false), NotNullWhen(true)] out T data) =>
30+
throw new NotImplementedException();
31+
}

src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ClipboardTests.cs

+94-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Runtime.InteropServices;
1313
using System.Text.Json;
1414
using System.Windows.Forms.Primitives;
15+
using System.Windows.Forms.TestUtilities;
1516
using Windows.Win32.System.Ole;
1617
using static System.Windows.Forms.Tests.BinaryFormatUtilitiesTests;
1718
using static System.Windows.Forms.TestUtilities.DataObjectTestHelpers;
@@ -23,7 +24,8 @@ namespace System.Windows.Forms.Tests;
2324
// Note: each registered Clipboard format is an OS singleton
2425
// and we should not run this test at the same time as other tests using the same format.
2526
[Collection("Sequential")]
26-
[UISettings(MaxAttempts = 3)] // Try up to 3 times before failing.
27+
// Try up to 3 times before failing.
28+
[UISettings(MaxAttempts = 3)]
2729
public class ClipboardTests
2830
{
2931
#pragma warning disable WFDEV005 // Type or member is obsolete
@@ -1110,14 +1112,19 @@ public void Clipboard_CustomDataObject_AvoidBinaryFormatter(bool copy)
11101112
// Pasting in different process has been simulated. Manual Json deserialization will need to occur.
11111113
IDataObject received = Clipboard.GetDataObject().Should().BeAssignableTo<IDataObject>().Subject;
11121114
received.Should().NotBe(jsonDataObject);
1115+
received.Should().BeAssignableTo<ITypedDataObject>();
11131116
byte[] jsonBytes = Clipboard.GetData(format).Should().BeOfType<byte[]>().Subject;
11141117
JsonSerializer.Deserialize(jsonBytes, typeof(SimpleTestData)).Should().BeEquivalentTo(data);
1118+
received.TryGetData(format, out byte[]? jsonBytes1).Should().BeTrue();
1119+
jsonBytes1.Should().BeEquivalentTo(jsonBytes);
11151120
}
11161121
else
11171122
{
11181123
JsonDataObject received = Clipboard.GetDataObject().Should().BeOfType<JsonDataObject>().Subject;
11191124
received.Should().Be(jsonDataObject);
11201125
received.Deserialize<SimpleTestData>(format).Should().BeEquivalentTo(data);
1126+
Action tryGetData = () => received.TryGetData(format, out byte[]? _);
1127+
tryGetData.Should().Throw<NotSupportedException>();
11211128
}
11221129
}
11231130

@@ -1288,4 +1295,90 @@ public class SomeDataObject : DataObject
12881295
public override bool GetDataPresent(string format, bool autoConvert)
12891296
=> format == Format || base.GetDataPresent(format, autoConvert);
12901297
}
1298+
1299+
[WinFormsTheory]
1300+
[BoolData]
1301+
public void Clipboard_RoundTrip_DataObject_SupportsTypedInterface(bool copy) =>
1302+
CustomDataObject_RoundTrip_SupportsTypedInterface<DataObject>(copy);
1303+
1304+
[WinFormsTheory]
1305+
[BoolData]
1306+
public void Clipboard_RoundTrip_ManagedAndRuntimeDataObject_SupportsTypedInterface(bool copy) =>
1307+
CustomDataObject_RoundTrip_SupportsTypedInterface<ManagedAndRuntimeDataObject>(copy);
1308+
1309+
[WinFormsTheory]
1310+
[BoolData]
1311+
public void Clipboard_RoundTrip_TypedAndRuntimeDataObject_SupportsTypedInterface(bool copy) =>
1312+
CustomDataObject_RoundTrip_SupportsTypedInterface<TypedAndRuntimeDataObject>(copy);
1313+
1314+
[WinFormsTheory]
1315+
[BoolData]
1316+
public void Clipboard_RoundTrip_TypedDataObject_SupportsTypedInterface(bool copy) =>
1317+
CustomDataObject_RoundTrip_SupportsTypedInterface<TypedDataObject>(copy);
1318+
1319+
[WinFormsTheory]
1320+
[BoolData]
1321+
public void Clipboard_RoundTrip_ManagedDataObject_SupportsTypedInterface(bool copy) =>
1322+
CustomDataObject_RoundTrip_SupportsTypedInterface<ManagedDataObject>(copy);
1323+
1324+
[WinFormsTheory]
1325+
[BoolData]
1326+
public void Clipboard_RoundTrip_Object_SupportsTypedInterface(bool copy)
1327+
{
1328+
SerializableTestData data = new();
1329+
string format = typeof(SerializableTestData).FullName!;
1330+
1331+
// Opt-in into access to the binary formatted stream.
1332+
using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: true);
1333+
// We need the BinaryFormatter to flush the data from the managed object to the HGLOBAL
1334+
// and to write data to HGLOBAL as a binary formatted stream now if it hadn't been flushed.
1335+
using BinaryFormatterScope scope = new(enable: true);
1336+
1337+
Clipboard.SetDataObject(data, copy);
1338+
1339+
DataObject received = Clipboard.GetDataObject().Should().BeOfType<DataObject>().Subject;
1340+
1341+
received.TryGetData(format, out SerializableTestData? result).Should().BeTrue();
1342+
result.Should().BeEquivalentTo(data);
1343+
1344+
Clipboard.TryGetData(format, out result).Should().BeTrue();
1345+
result.Should().BeEquivalentTo(data);
1346+
}
1347+
1348+
private static void CustomDataObject_RoundTrip_SupportsTypedInterface<T>(bool copy) where T : IDataObject, new()
1349+
{
1350+
SerializableTestData data = new();
1351+
T testDataObject = new();
1352+
string format = ManagedDataObject.s_format;
1353+
testDataObject.SetData(format, data);
1354+
1355+
// Opt-in into access the binary formatted stream.
1356+
using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: copy);
1357+
// We need the BinaryFormatter to flush the data from the managed object to the HGLOBAL.
1358+
using (BinaryFormatterScope scope = new(enable: copy))
1359+
{
1360+
Clipboard.SetDataObject(testDataObject, copy);
1361+
}
1362+
1363+
// copy == true => data was flushed to HGLOBAL and we read it with a WinForms DataObject.
1364+
// Otherwise this is the user-implemented ITypedDataObject or the WinForms wrapper.
1365+
if (copy || typeof(T).IsAssignableTo(typeof(ITypedDataObject)))
1366+
{
1367+
ITypedDataObject received = Clipboard.GetDataObject().Should().BeAssignableTo<ITypedDataObject>().Subject;
1368+
1369+
received.TryGetData(format, out SerializableTestData? result).Should().BeTrue();
1370+
result.Should().BeEquivalentTo(data);
1371+
1372+
Clipboard.TryGetData(format, out result).Should().BeTrue();
1373+
result.Should().BeEquivalentTo(data);
1374+
}
1375+
else
1376+
{
1377+
T received = Clipboard.GetDataObject().Should().BeOfType<T>().Subject;
1378+
received.Should().Be(testDataObject);
1379+
// When we are not flushing the data to the HGLOBAL, we are reading from our DataStore or the managed test data object.
1380+
Action tryGetData = () => received.TryGetData(format, out SerializableTestData? result);
1381+
tryGetData.Should().Throw<NotSupportedException>();
1382+
}
1383+
}
12911384
}

0 commit comments

Comments
 (0)