diff options
| author | Karsten Heimrich <karsten.heimrich@qt.io> | 2025-10-28 07:28:58 +0100 |
|---|---|---|
| committer | Miguel Costa <miguel.costa@qt.io> | 2025-11-13 12:38:54 +0000 |
| commit | 376e975d35f2b07533dc70c306f05125159c2adc (patch) | |
| tree | 9e65741b54bc7185867f13f2b077c1aa596420ef | |
| parent | 0cc35d555959ea8d9a1bc233d95ebf28f782dd7c (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.cs | 143 |
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}"); + } + } + } +} |
