aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKarsten Heimrich <karsten.heimrich@qt.io>2025-10-28 07:28:58 +0100
committerMiguel Costa <miguel.costa@qt.io>2025-11-13 12:38:54 +0000
commit376e975d35f2b07533dc70c306f05125159c2adc (patch)
tree9e65741b54bc7185867f13f2b077c1aa596420ef
parent0cc35d555959ea8d9a1bc233d95ebf28f782dd7c (diff)
Complement "Allow type casting in QML" with auto-tests
Change-Id: Ie5d39ada2e923bc1fe20ff01dde3cb1773f1d2a1 Reviewed-by: Karsten Heimrich <karsten.heimrich@qt.io>
-rw-r--r--tests/Test_Qt.DotNet.Generator/Test_TypeCastImplemention.cs143
1 files changed, 143 insertions, 0 deletions
diff --git a/tests/Test_Qt.DotNet.Generator/Test_TypeCastImplemention.cs b/tests/Test_Qt.DotNet.Generator/Test_TypeCastImplemention.cs
new file mode 100644
index 0000000..78d1f7a
--- /dev/null
+++ b/tests/Test_Qt.DotNet.Generator/Test_TypeCastImplemention.cs
@@ -0,0 +1,143 @@
+/***************************************************************************************************
+ Copyright (C) 2025 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+***************************************************************************************************/
+
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace Test_Qt.DotNet.Generator
+{
+ using Support;
+
+ [TestClass]
+ public class Test_TypeCastImplemention
+ {
+ private const string TypeCastHeaderPath = "source/hpp/qt/dotnet/typecast.h";
+
+ public TestContext TestContext { get; set; }
+
+ [TestMethod]
+ public async Task TypeCast_IsGenerated_When_No_ReferenceTypes()
+ {
+ const string source = """
+ namespace N1
+ {
+ public struct S { public int X; }
+ }
+ namespace N2
+ {
+ public enum E { A, B }
+ }
+ """;
+
+ var result = await TestCodeGenerator.GenerateAsync([source],
+ ct: TestContext.CancellationTokenSource.Token);
+
+ Assert.IsTrue(result.Sink.Files.TryGetValue(TypeCastHeaderPath, out _),
+ "typecast.h missing.");
+ }
+
+ [TestMethod]
+ public async Task TypeCast_DoesNotGenerate_As_For_Value_Enum_Interface_Abstract()
+ {
+ const string source = """
+ namespace T
+ {
+ public struct Point { public int X; }
+ public enum Color { Red, Green }
+ public interface IFace {}
+ public abstract class AbstractBase {}
+ }
+ """;
+
+ var result = await TestCodeGenerator.GenerateAsync([source],
+ ct: TestContext.CancellationTokenSource.Token);
+
+ Assert.IsTrue(result.Sink.Files.TryGetValue(TypeCastHeaderPath, out var typeCastHeader),
+ "typecast.h missing.");
+
+ Assert.MatchesRegex(new Regex(@"Q_INVOKABLE\s+[A-Za-z0-9_:]+\s*\*\s*as[A-Za-z0-9_]"
+ + @"+\s*\(\s*QObject\s*\*\s*obj\s*\)\s*;", RegexOptions.Multiline),
+ typeCastHeader, "No as* invokable expected for value/enum/interface/abstract types.");
+ }
+
+ [TestMethod]
+ public async Task TypeCast_As_Method_Signature_Uses_QObject_Param()
+ {
+ const string source = """
+ namespace Foo.A {
+ public class Widget {}
+ }
+ """;
+
+ var result = await TestCodeGenerator.GenerateAsync([source],
+ ct: TestContext.CancellationTokenSource.Token);
+
+ Assert.IsTrue(result.Sink.Files.TryGetValue(TypeCastHeaderPath, out var typeCastHeader),
+ "typecast.h missing.");
+
+ Assert.MatchesRegex(new Regex(@"Q_INVOKABLE\s+Foo::A::Widget\s*\*\s*as[A-Za-z0-9_]"
+ + @"+\s*\(\s*QObject\s*\*\s*obj\s*\)\s*;", RegexOptions.Multiline),
+ typeCastHeader, "Expected at least one as* method with QObject* obj");
+ }
+
+ [TestMethod]
+ public async Task TypeCast_Has_Qml_Singleton_Macros()
+ {
+ var result = await TestCodeGenerator.GenerateAsync(["namespace T { public class X {} }"],
+ ct: TestContext.CancellationTokenSource.Token);
+
+ Assert.IsTrue(result.Sink.Files.TryGetValue(TypeCastHeaderPath, out var typeCastHeader),
+ "typecast.h missing.");
+
+ Assert.Contains("QML_ELEMENT", typeCastHeader);
+ Assert.Contains("QML_SINGLETON", typeCastHeader);
+ }
+
+ [TestMethod]
+ public async Task TypeCast_As_Method_Names_Are_Unique_For_Same_SimpleName()
+ {
+ const string source = """
+ namespace Foo.A
+ {
+ public class Widget { public Widget() {} }
+ }
+ namespace Bar.B
+ {
+ public class Widget { public Widget() {} }
+ }
+ """;
+
+ var result = await TestCodeGenerator.GenerateAsync([source],
+ ct: TestContext.CancellationTokenSource.Token);
+
+ Assert.IsTrue(result.Sink.Files.TryGetValue(TypeCastHeaderPath, out var typeCastHeader),
+ "typecast.h missing.");
+
+ var rx = new Regex(@"Q_INVOKABLE\s+(?<ret>[A-Za-z_][A-Za-z0-9_:]*)\s*\*\s*(?<name>as"
+ + @"[A-Za-z0-9_]+)\s*\(\s*QObject\s*\*\s*obj\s*\)\s*;", RegexOptions.Multiline);
+
+ var matches = rx.Matches(typeCastHeader).ToArray();
+ var widgets = matches
+ .Where(m => m.Groups["ret"].Value is "Foo::A::Widget" or "Bar::B::Widget")
+ .ToArray();
+
+ Assert.AreEqual(2, widgets.Length, "Expected 2 as*-methods for both widget types.");
+
+ var names = widgets.Select(m => m.Groups["name"].Value).ToList();
+ var duplicates = names.GroupBy(n => n).Where(g => g.Count() > 1).ToList();
+
+ if (duplicates.Any()) {
+ var detail = string.Join("\n", duplicates
+ .Select(d => $" {d.Key} -> [{string.Join(", ", widgets
+ .Where(m => m.Groups["name"].Value == d.Key)
+ .Select(m => m.Groups["ret"].Value)
+ .Distinct())}]"));
+ Assert.Fail($"Collision found (overload by return type is illegal in C++):\n{detail}");
+ }
+ }
+ }
+}