New binding mechanism: Expression tracking
This commit is contained in:
259
src/Library/TerminalUI.Tests/BindingTests.cs
Normal file
259
src/Library/TerminalUI.Tests/BindingTests.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
using TerminalUI.Controls;
|
||||
using TerminalUI.Extensions;
|
||||
using TerminalUI.Tests.Models;
|
||||
|
||||
namespace TerminalUI.Tests;
|
||||
|
||||
public class BindingTests
|
||||
{
|
||||
private static string TestMapperMethodStatic(string s) => s.ToUpper();
|
||||
private string TestMapperMethod(string s) => s.ToUpper();
|
||||
|
||||
[Fact]
|
||||
public void StaticCaptureMethodBinding_ByDefault_ShouldSetValue()
|
||||
{
|
||||
var testViewModel = new TestViewModel
|
||||
{
|
||||
Text = "test"
|
||||
};
|
||||
var grid = new Grid<TestViewModel>
|
||||
{
|
||||
DataContext = testViewModel
|
||||
};
|
||||
var txtb1 = grid.CreateChild<TextBlock<TestViewModel>>();
|
||||
|
||||
txtb1.Bind(
|
||||
txtb1,
|
||||
vm => TestMapperMethodStatic(vm.Text),
|
||||
t => t.Text,
|
||||
v => v);
|
||||
|
||||
Assert.Equal("TEST", txtb1.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaticCaptureMethodBinding_AfterUpdate_ShouldSetValue()
|
||||
{
|
||||
var testViewModel = new TestViewModel
|
||||
{
|
||||
Text = "test"
|
||||
};
|
||||
var grid = new Grid<TestViewModel>
|
||||
{
|
||||
DataContext = testViewModel
|
||||
};
|
||||
var txtb1 = grid.CreateChild<TextBlock<TestViewModel>>();
|
||||
|
||||
txtb1.Bind(
|
||||
txtb1,
|
||||
vm => TestMapperMethodStatic(vm.Text),
|
||||
t => t.Text,
|
||||
v => v);
|
||||
|
||||
testViewModel.Text = "Updated";
|
||||
|
||||
Assert.Equal("UPDATED", txtb1.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CaptureMethodBinding_ByDefault_ShouldSetValue()
|
||||
{
|
||||
var testViewModel = new TestViewModel
|
||||
{
|
||||
Text = "test"
|
||||
};
|
||||
var grid = new Grid<TestViewModel>
|
||||
{
|
||||
DataContext = testViewModel
|
||||
};
|
||||
var txtb1 = grid.CreateChild<TextBlock<TestViewModel>>();
|
||||
|
||||
txtb1.Bind(
|
||||
txtb1,
|
||||
vm => TestMapperMethod(vm.Text),
|
||||
t => t.Text,
|
||||
v => v);
|
||||
|
||||
Assert.Equal("TEST", txtb1.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CaptureMethodBinding_AfterUpdate_ShouldSetValue()
|
||||
{
|
||||
var testViewModel = new TestViewModel
|
||||
{
|
||||
Text = "test"
|
||||
};
|
||||
var grid = new Grid<TestViewModel>
|
||||
{
|
||||
DataContext = testViewModel
|
||||
};
|
||||
var txtb1 = grid.CreateChild<TextBlock<TestViewModel>>();
|
||||
|
||||
txtb1.Bind(
|
||||
txtb1,
|
||||
vm => TestMapperMethod(vm.Text),
|
||||
t => t.Text,
|
||||
v => v);
|
||||
|
||||
testViewModel.Text = "Updated";
|
||||
|
||||
Assert.Equal("UPDATED", txtb1.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FallbackValue_BindingFails_ShouldBeApplied()
|
||||
{
|
||||
var grid = new Grid<TestViewModel>();
|
||||
var txtb1 = grid.CreateChild<TextBlock<TestViewModel>>();
|
||||
|
||||
txtb1.Bind(
|
||||
txtb1,
|
||||
vm => vm.Items,
|
||||
t => t.Text,
|
||||
v => v.ToString(),
|
||||
fallbackValue: "Fallback");
|
||||
|
||||
Assert.Equal("Fallback", txtb1.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConditionalExpression_WhenTrue_ShouldResultInTrueValue()
|
||||
{
|
||||
var grid = new Grid<TestViewModel>();
|
||||
var txtb1 = grid.CreateChild<TextBlock<TestViewModel>>();
|
||||
|
||||
txtb1.Bind(
|
||||
txtb1,
|
||||
vm => vm == null ? "null" : "not null",
|
||||
t => t.Text,
|
||||
v => v);
|
||||
|
||||
Assert.Equal("null", txtb1.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConditionalExpression_WhenFalse_ShouldResultInFalseValue()
|
||||
{
|
||||
var testViewModel = new TestViewModel();
|
||||
var grid = new Grid<TestViewModel>
|
||||
{
|
||||
DataContext = testViewModel
|
||||
};
|
||||
var txtb1 = grid.CreateChild<TextBlock<TestViewModel>>();
|
||||
|
||||
txtb1.Bind(
|
||||
txtb1,
|
||||
vm => vm == null ? "null" : "not null",
|
||||
t => t.Text,
|
||||
v => v);
|
||||
|
||||
Assert.Equal("not null", txtb1.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConditionalExpressionWithNestedValues_WhenTrue_ShouldResultInTrueNestedValue()
|
||||
{
|
||||
var testViewModel = new TestViewModel();
|
||||
var grid = new Grid<TestViewModel>
|
||||
{
|
||||
DataContext = testViewModel
|
||||
};
|
||||
var txtb1 = grid.CreateChild<TextBlock<TestViewModel>>();
|
||||
|
||||
txtb1.Bind(
|
||||
txtb1,
|
||||
vm => vm != null ? vm.Items.Count : -1,
|
||||
t => t.Text,
|
||||
v => v.ToString());
|
||||
|
||||
Assert.Equal("1", txtb1.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConditionalExpressionWithNestedValues_WhenFalse_ShouldResultInFalseNestedValue()
|
||||
{
|
||||
var testViewModel = new TestViewModel();
|
||||
var grid = new Grid<TestViewModel>
|
||||
{
|
||||
DataContext = testViewModel
|
||||
};
|
||||
var txtb1 = grid.CreateChild<TextBlock<TestViewModel>>();
|
||||
|
||||
txtb1.Bind(
|
||||
txtb1,
|
||||
vm => vm == null ? -1 : vm.Items.Count,
|
||||
t => t.Text,
|
||||
v => v.ToString());
|
||||
|
||||
Assert.Equal("1", txtb1.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MixedBindingWithMethodCall_WhenRootChanged_ShouldUpdate()
|
||||
{
|
||||
var testViewModel = new TestViewModel();
|
||||
var grid = new Grid<TestViewModel>
|
||||
{
|
||||
DataContext = testViewModel
|
||||
};
|
||||
var txtb1 = grid.CreateChild<TextBlock<TestViewModel>>();
|
||||
|
||||
txtb1.Bind(
|
||||
txtb1,
|
||||
vm => vm.Items[0].GetNestedItem(0).OtherItems.Count,
|
||||
t => t.Text,
|
||||
v => v.ToString());
|
||||
|
||||
testViewModel.Items = new()
|
||||
{
|
||||
TestNestedCollectionItem.Create(4,
|
||||
TestNestedCollectionItem.Create(
|
||||
6,
|
||||
TestNestedCollectionItem.Create(4)
|
||||
),
|
||||
TestNestedCollectionItem.Create(1),
|
||||
TestNestedCollectionItem.Create(2),
|
||||
TestNestedCollectionItem.Create(3)
|
||||
),
|
||||
TestNestedCollectionItem.Create(1),
|
||||
TestNestedCollectionItem.Create(2),
|
||||
};
|
||||
|
||||
Assert.Equal("6", txtb1.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NestedPropertyBinding_WhenRootChanged_ShouldUpdateText()
|
||||
{
|
||||
var testViewModel = new TestViewModel();
|
||||
var grid = new Grid<TestViewModel>
|
||||
{
|
||||
DataContext = testViewModel
|
||||
};
|
||||
var txtb1 = grid.CreateChild<TextBlock<TestViewModel>>();
|
||||
|
||||
txtb1.Bind(
|
||||
txtb1,
|
||||
vm => vm.Items.Count,
|
||||
t => t.Text,
|
||||
v => v.ToString());
|
||||
|
||||
testViewModel.Items = new List<TestNestedCollectionItem>
|
||||
{
|
||||
TestNestedCollectionItem.Create(4,
|
||||
TestNestedCollectionItem.Create(
|
||||
6,
|
||||
TestNestedCollectionItem.Create(4)
|
||||
),
|
||||
TestNestedCollectionItem.Create(1),
|
||||
TestNestedCollectionItem.Create(2),
|
||||
TestNestedCollectionItem.Create(3)
|
||||
),
|
||||
TestNestedCollectionItem.Create(1),
|
||||
TestNestedCollectionItem.Create(2),
|
||||
};
|
||||
|
||||
Assert.Equal("3", txtb1.Text);
|
||||
}
|
||||
}
|
||||
1
src/Library/TerminalUI.Tests/GlobalUsings.cs
Normal file
1
src/Library/TerminalUI.Tests/GlobalUsings.cs
Normal file
@@ -0,0 +1 @@
|
||||
global using Xunit;
|
||||
10
src/Library/TerminalUI.Tests/Models/TestCollectionItem.cs
Normal file
10
src/Library/TerminalUI.Tests/Models/TestCollectionItem.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace TerminalUI.Tests.Models;
|
||||
|
||||
public class TestCollectionItem
|
||||
{
|
||||
public List<TestItem>? Items1 { get; set; } = new()
|
||||
{
|
||||
new TestItem() {Name = "Name1"},
|
||||
new TestItem() {Name = "Name2"},
|
||||
};
|
||||
}
|
||||
6
src/Library/TerminalUI.Tests/Models/TestItem.cs
Normal file
6
src/Library/TerminalUI.Tests/Models/TestItem.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace TerminalUI.Tests.Models;
|
||||
|
||||
public class TestItem
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace TerminalUI.Tests.Models;
|
||||
|
||||
public class TestNestedCollectionItem
|
||||
{
|
||||
public List<TestNestedCollectionItem> Items { get; set; } = new();
|
||||
public List<TestCollectionItem> OtherItems { get; set; } = new();
|
||||
|
||||
public TestNestedCollectionItem GetNestedItem(int index) => Items[index];
|
||||
public TestCollectionItem GetSimpleItem(int index) => OtherItems[index];
|
||||
|
||||
public static TestNestedCollectionItem Create(int otherItemCount, params TestNestedCollectionItem[] items)
|
||||
{
|
||||
var collection = new TestNestedCollectionItem()
|
||||
{
|
||||
Items = items.ToList()
|
||||
};
|
||||
for (var i = 0; i < otherItemCount; i++)
|
||||
collection.OtherItems.Add(new TestCollectionItem());
|
||||
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
32
src/Library/TerminalUI.Tests/Models/TestViewModel.cs
Normal file
32
src/Library/TerminalUI.Tests/Models/TestViewModel.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
|
||||
namespace TerminalUI.Tests.Models;
|
||||
|
||||
public sealed partial class TestViewModel : INotifyPropertyChanged
|
||||
{
|
||||
[Notify] private List<TestNestedCollectionItem> _items;
|
||||
[Notify] private string _text;
|
||||
|
||||
public TestViewModel()
|
||||
{
|
||||
_text = "Initial text";
|
||||
_items = new List<TestNestedCollectionItem>
|
||||
{
|
||||
TestNestedCollectionItem.Create(
|
||||
3,
|
||||
TestNestedCollectionItem.Create(
|
||||
1,
|
||||
TestNestedCollectionItem.Create(2)
|
||||
),
|
||||
new()
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
33
src/Library/TerminalUI.Tests/TerminalUI.Tests.csproj
Normal file
33
src/Library/TerminalUI.Tests/TerminalUI.Tests.csproj
Normal file
@@ -0,0 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
||||
<PackageReference Include="PropertyChanged.SourceGenerator" Version="1.0.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.2.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TerminalUI\TerminalUI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user