Skip to content

Commit 7dfd065

Browse files
authored
Merge pull request #2963 from unoplatform/copilot/fix-bindable-attribute-issue
Add BindableAttribute to ViewModelGenerator_1 and ViewModelGenerator_2
2 parents fbc04d6 + c2dc787 commit 7dfd065

4 files changed

Lines changed: 79 additions & 1 deletion

File tree

src/Uno.Extensions.Reactive.Generator/Bindables/ViewModelGenerator_1.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ string GetPropertyInfo(ITypeSymbol type, (ITypeSymbol type, Func<string, string>
159159
namespace {record.ContainingNamespace}
160160
{{
161161
{this.GetCodeGenAttribute()}
162+
[{NS.Bindings}.Bindable(typeof({record.ToFullString()}))]
162163
{record.GetAccessibilityAsCSharpCodeString()} sealed class Bindable{record.GetPascalCaseName()} : {NS.Bindings}.Bindable<{record}>
163164
{{
164165
{properties.Select(prop => $"private readonly {prop.bindable} _{prop.symbol.GetCamelCaseName()};").Align(2)}

src/Uno.Extensions.Reactive.Generator/Bindables/ViewModelGenerator_2.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ string GetPropertyInfo(ITypeSymbol type, (ITypeSymbol type, Func<string, string>
162162
namespace {record.ContainingNamespace}
163163
{{
164164
{this.GetCodeGenAttribute()}
165+
[{NS.Bindings}.Bindable(typeof({record.ToFullString()}))]
165166
{record.GetAccessibilityAsCSharpCodeString()} sealed class {vm} : {NS.Bindings}.Bindable<{record}>
166167
{{
167168
{properties.Select(prop => $"private readonly {prop.bindable} _{prop.symbol.GetCamelCaseName()};").Align(2)}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System;
2+
using System.Collections.Immutable;
3+
using System.Linq;
4+
using System.Reflection;
5+
using Microsoft.VisualStudio.TestTools.UnitTesting;
6+
using Uno.Extensions.Reactive.Bindings;
7+
using Uno.Extensions.Reactive.Testing;
8+
9+
namespace Uno.Extensions.Reactive.Tests.Generator;
10+
11+
/// <summary>
12+
/// Tests to verify that generated bindable types are flagged with the BindableAttribute.
13+
/// This ensures better performance and trimming support by avoiding reflection at runtime.
14+
/// </summary>
15+
[TestClass]
16+
public partial class Given_BindableAttribute_Then_Flagged : FeedUITests
17+
{
18+
[TestMethod]
19+
public void ViewModelGenerator_GeneratesBindableAttributeForRecords()
20+
{
21+
// When ViewModelGenerator_2 generates a bindable wrapper for a record type,
22+
// it should include the BindableAttribute to avoid reflection at runtime.
23+
// ViewModelGenerator_2 generates types named Bindable{RecordName}ViewModel
24+
var bindableType = typeof(BindableTestRecordViewModel);
25+
26+
Assert.IsNotNull(bindableType, "The generated bindable type should exist");
27+
28+
var bindableAttribute = bindableType.GetCustomAttribute<BindableAttribute>();
29+
Assert.IsNotNull(bindableAttribute, "The generated bindable type should have BindableAttribute");
30+
Assert.AreEqual(typeof(TestRecord), bindableAttribute.Model, "The BindableAttribute should reference the correct model type");
31+
}
32+
33+
[TestMethod]
34+
public void ViewModelGenTool_3_GeneratesBindableAttribute()
35+
{
36+
// ViewModelGenTool_3 generates types named {ModelName}ViewModel (for ViewModels with partial class)
37+
var bindableType = typeof(TestViewModel);
38+
39+
Assert.IsNotNull(bindableType, "The generated bindable type should exist");
40+
41+
var bindableAttribute = bindableType.GetCustomAttribute<BindableAttribute>();
42+
Assert.IsNotNull(bindableAttribute, "The generated bindable type should have BindableAttribute");
43+
Assert.AreEqual(typeof(TestModel), bindableAttribute.Model, "The BindableAttribute should reference the correct model type");
44+
}
45+
46+
[TestMethod]
47+
public void ViewModelGenerator_GeneratesBindableAttributeForNestedRecords()
48+
{
49+
// Test that records containing other records also generate the BindableAttribute
50+
var bindableType = typeof(BindableTestRecordWithListViewModel);
51+
52+
Assert.IsNotNull(bindableType, "The generated bindable type should exist");
53+
54+
var bindableAttribute = bindableType.GetCustomAttribute<BindableAttribute>();
55+
Assert.IsNotNull(bindableAttribute, "The generated bindable type should have BindableAttribute");
56+
Assert.AreEqual(typeof(TestRecordWithList), bindableAttribute.Model, "The BindableAttribute should reference the correct model type");
57+
}
58+
}
59+
60+
// Test model for ViewModelGenTool_3 (generates view models from partial classes with feeds)
61+
// This will trigger generation of bindable wrappers for the records it references
62+
public partial class TestModel
63+
{
64+
public IFeed<TestRecord> Record => Feed.Async(async ct => new TestRecord("Test", 42));
65+
public IFeed<TestRecordWithList> RecordWithList => Feed.Async(async ct => new TestRecordWithList(ImmutableList<TestRecord>.Empty));
66+
}
67+
68+
// Test record for ViewModelGenerator_2 (generates bindable wrappers for records)
69+
public partial record TestRecord(string Name, int Value);
70+
71+
// Test record with nested list to verify complex scenarios
72+
public partial record TestRecordWithList(ImmutableList<TestRecord> Items);

src/Uno.Extensions.Reactive/Presentation/Bindings/BindableAttribute.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ namespace Uno.Extensions.Reactive.Bindings;
77
/// <summary>
88
/// Flags a class as a _bindable_ (a.k.a. _view model_).
99
/// </summary>
10-
/// <remarks>This attribute is added by the feeds generator on the _view model_ type, you should not have to use it.</remarks>
10+
/// <remarks>
11+
/// This attribute is added by the feeds generator on the _view model_ type, you should not have to use it.
12+
/// This attribute is used at compile-time by Uno Platform and WinAppSDK to generate bindings.
13+
/// Note: The binding generators validate only the attribute name, not the namespace.
14+
/// </remarks>
1115
[EditorBrowsable(EditorBrowsableState.Advanced)]
1216
[AttributeUsage(AttributeTargets.Class)]
1317
public class BindableAttribute : Attribute

0 commit comments

Comments
 (0)