using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Reactive.Subjects; using System.Text; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Data.Converters; using Avalonia.Data.Core; using Avalonia.Markup.Data; using Avalonia.Media; using Avalonia.UnitTests; using XamlX; using Xunit; namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions { public class CompiledBindingExtensionTests { [Fact] public void ResolvesClrPropertyBasedOnDataContextType() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); var dataContext = new TestDataContext { StringProperty = "foobar" }; window.DataContext = dataContext; Assert.Equal(dataContext.StringProperty, textBlock.Text); } } [Fact] public void ResolvesClrPropertyBasedOnDataContextType_InterfaceInheritance() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); var dataContext = new TestDataContext { StringProperty = "foobar" }; window.DataContext = dataContext; Assert.Equal(dataContext.StringProperty, textBlock.Text); } } [Fact] public void ResolvesPathPassedByProperty() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); var dataContext = new TestDataContext { StringProperty = "foobar" }; window.DataContext = dataContext; Assert.Equal(dataContext.StringProperty, textBlock.Text); } } [Fact] public void ResolvesStreamTaskBindingCorrectly() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); var dataContext = new TestDataContext { TaskProperty = Task.FromResult("foobar") }; window.DataContext = dataContext; Assert.Equal(dataContext.TaskProperty.Result, textBlock.Text); } } [Fact] public void ResolvesStreamObservableBindingCorrectly() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); DelayedBinding.ApplyBindings(textBlock); var subject = new Subject(); var dataContext = new TestDataContext { ObservableProperty = subject }; window.DataContext = dataContext; subject.OnNext("foobar"); Assert.Equal("foobar", textBlock.Text); } } [Fact] public void ResolvesIndexerBindingCorrectly() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); var dataContext = new TestDataContext { ListProperty = { "A", "B", "C", "D", "E" } }; window.DataContext = dataContext; Assert.Equal(dataContext.ListProperty[3], textBlock.Text); } } [Fact] public void ResolvesArrayIndexerBindingCorrectly() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); var dataContext = new TestDataContext { ArrayProperty = new[] { "A", "B", "C", "D", "E" } }; window.DataContext = dataContext; Assert.Equal(dataContext.ArrayProperty[3], textBlock.Text); } } [Fact] public void ResolvesObservableIndexerBindingCorrectly() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); var dataContext = new TestDataContext { ObservableCollectionProperty = { "A", "B", "C", "D", "E" } }; window.DataContext = dataContext; Assert.Equal(dataContext.ObservableCollectionProperty[3], textBlock.Text); dataContext.ObservableCollectionProperty[3] = "New Value"; Assert.Equal(dataContext.ObservableCollectionProperty[3], textBlock.Text); } } [Fact] public void InfersCompiledBindingDataContextFromDataContextBinding() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); var dataContext = new TestDataContext { StringProperty = "A" }; window.DataContext = dataContext; Assert.Equal(dataContext.StringProperty, textBlock.Text); } } [Fact] public void ResolvesNonIntegerIndexerBindingCorrectly() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); var dataContext = new TestDataContext(); dataContext.NonIntegerIndexerProperty["Test"] = "Initial Value"; window.DataContext = dataContext; Assert.Equal(dataContext.NonIntegerIndexerProperty["Test"], textBlock.Text); dataContext.NonIntegerIndexerProperty["Test"] = "New Value"; Assert.Equal(dataContext.NonIntegerIndexerProperty["Test"], textBlock.Text); } } [Fact] public void ResolvesNonIntegerIndexerBindingFromParentInterfaceCorrectly() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); var dataContext = new TestDataContext(); dataContext.NonIntegerIndexerInterfaceProperty["Test"] = "Initial Value"; window.DataContext = dataContext; Assert.Equal(dataContext.NonIntegerIndexerInterfaceProperty["Test"], textBlock.Text); dataContext.NonIntegerIndexerInterfaceProperty["Test"] = "New Value"; Assert.Equal(dataContext.NonIntegerIndexerInterfaceProperty["Test"], textBlock.Text); } } [Fact] public void InfersDataTemplateTypeFromDataTypeProperty() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var target = window.FindControl("target"); var dataContext = new TestDataContext(); dataContext.StringProperty = "Initial Value"; window.DataContext = dataContext; window.ApplyTemplate(); target.ApplyTemplate(); ((ContentPresenter)target.Presenter).UpdateChild(); Assert.Equal(dataContext.StringProperty, ((TextBlock)target.Presenter.Child).Text); } } [Fact] public void ThrowsOnUninferrableLooseDataTemplateNoDataTypeWithCompiledBindingPath() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; ThrowsXamlTransformException(() => AvaloniaRuntimeXamlLoader.Load(xaml)); } } [Fact] public void ThrowsOnUninferrableDataTypeFromNonCompiledDataContextBindingWithCompiledBindingPath() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; ThrowsXamlTransformException(() => AvaloniaRuntimeXamlLoader.Load(xaml)); } } [Fact] public void InfersDataTemplateTypeFromParentCollectionItemsType() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var target = window.FindControl("target"); var dataContext = new TestDataContext(); dataContext.ListProperty.Add("Test"); window.DataContext = dataContext; window.ApplyTemplate(); target.ApplyTemplate(); target.Presenter.ApplyTemplate(); Assert.Equal(dataContext.ListProperty[0], (string)((ContentPresenter)target.Presenter.Panel.Children[0]).Content); } } [Fact] public void ThrowsOnUninferrableDataTemplateInItemsControlWithoutItemsBinding() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; ThrowsXamlTransformException(() => AvaloniaRuntimeXamlLoader.Load(xaml)); } } [Fact] public void ResolvesElementNameBinding() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("text2"); var dataContext = new TestDataContext { StringProperty = "foobar" }; window.DataContext = dataContext; Assert.Equal(dataContext.StringProperty, textBlock.Text); } } [Fact] public void ResolvesElementNameBindingFromLongForm() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("text2"); var dataContext = new TestDataContext { StringProperty = "foobar" }; window.DataContext = dataContext; Assert.Equal(dataContext.StringProperty, textBlock.Text); } } [Fact] public void ResolvesRelativeSourceBindingLongForm() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var target = window.FindControl("text"); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); target.ApplyTemplate(); Assert.Equal("test", target.Text); } } [Fact] public void Binds_To_Source() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var target = window.FindControl("text"); window.ApplyTemplate(); window.Presenter.ApplyTemplate(); target.ApplyTemplate(); Assert.Equal("Test".Length.ToString(), target.Text); } } [Fact] public void Binds_To_Source_StaticResource() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); Assert.Equal("foobar", textBlock.Text); } } [Fact] public void Binds_To_Source_StaticResource1() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" test "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); Assert.Equal("foobar", textBlock.Text); } } [Fact] public void Binds_To_Source_StaticResource_In_ResourceDictionary() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); Assert.Equal("foobar", textBlock.Text); } } [Fact] public void Binds_To_Source_StaticResource_In_ResourceDictionary1() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" test "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); Assert.Equal("foobar", textBlock.Text); } } [Fact] public void Binds_To_Source_xStatic() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var contentControl = window.FindControl("contentControl"); Assert.Equal(Brushes.Red.Color, contentControl.Content); } } [Fact] public void CompilesBindingWhenRequested() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); var dataContext = new TestDataContext { StringProperty = "foobar" }; window.DataContext = dataContext; Assert.Equal(dataContext.StringProperty, textBlock.Text); } } [Fact] public void ThrowsOnInvalidBindingPathOnCompiledBindingEnabledViaDirective() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; ThrowsXamlParseException(() => AvaloniaRuntimeXamlLoader.Load(xaml)); } } [Fact] public void SupportParentInPath() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var contentControl = window.FindControl("contentControl"); Assert.Equal("foo", contentControl.Content); } } [Fact] public void SupportConverterWithParameter() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var textBlock = window.FindControl("textBlock"); window.DataContext = new TestDataContext() { StringProperty = "Foo" }; Assert.Equal("Foo+Bar", textBlock.Text); } } [Fact] public void ThrowsOnInvalidCompileBindingsDirective() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; ThrowsXamlParseException(() => AvaloniaRuntimeXamlLoader.Load(xaml)); } } [Fact] public void SupportCastToTypeInExpression() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var contentControl = window.FindControl("contentControl"); var dataContext = new TestDataContext(); window.DataContext = dataContext; Assert.Equal(dataContext, contentControl.Content); } } [Fact] public void SupportCastToTypeInExpression_DifferentTypeEvaluatesToNull() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var contentControl = window.FindControl("contentControl"); var dataContext = "foo"; window.DataContext = dataContext; Assert.Equal(null, contentControl.Content); } } [Fact] public void SupportCastToTypeInExpressionWithProperty() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var contentControl = window.FindControl("contentControl"); var dataContext = new TestDataContext { StringProperty = "foobar" }; window.DataContext = dataContext; Assert.Equal(dataContext.StringProperty, contentControl.Content); } } [Fact] public void SupportCastToTypeInExpressionWithProperty1() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var contentControl = window.FindControl("contentControl"); var dataContext = new TestDataContext { StringProperty = "foobar" }; window.DataContext = dataContext; Assert.Equal(dataContext.StringProperty, contentControl.Content); } } [Fact] public void SupportCastToTypeInExpressionWithPropertyIndexer() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var contentControl = window.FindControl("contentControl"); var data = new TestData() { StringProperty = "Foo" }; var dataContext = new TestDataContext { ObjectsArrayProperty = new object[] { data } }; window.DataContext = dataContext; Assert.Equal(data.StringProperty, contentControl.Content); } } [Fact] public void SupportCastToTypeInExpressionWithProperty_DifferentTypeEvaluatesToNull() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var contentControl = window.FindControl("contentControl"); var dataContext = new TestDataContext { StringProperty = "foobar" }; window.DataContext = dataContext; Assert.Equal(dataContext.StringProperty, contentControl.Content); window.DataContext = "foo"; Assert.Equal(null, contentControl.Content); } } void Throws(string type, Action cb) { try { cb(); } catch (Exception e) when (e.GetType().Name == type) { return; } throw new Exception("Expected " + type); } void ThrowsXamlParseException(Action cb) => Throws("XamlParseException", cb); void ThrowsXamlTransformException(Action cb) => Throws("XamlTransformException", cb); } public interface INonIntegerIndexer { string this[string key] { get; set; } } public interface INonIntegerIndexerDerived : INonIntegerIndexer { } public interface IHasProperty { string StringProperty { get; set; } } public interface IHasPropertyDerived : IHasProperty { } public class AppendConverter : IValueConverter { public static IValueConverter Instance { get; } = new AppendConverter(); public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => string.Format("{0}+{1}", value, parameter); public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); } public class TestData { public string StringProperty { get; set; } } public class TestDataContext : IHasPropertyDerived { public string StringProperty { get; set; } public Task TaskProperty { get; set; } public IObservable ObservableProperty { get; set; } public ObservableCollection ObservableCollectionProperty { get; set; } = new ObservableCollection(); public string[] ArrayProperty { get; set; } public object[] ObjectsArrayProperty { get; set; } public List ListProperty { get; set; } = new List(); public NonIntegerIndexer NonIntegerIndexerProperty { get; set; } = new NonIntegerIndexer(); public INonIntegerIndexerDerived NonIntegerIndexerInterfaceProperty => NonIntegerIndexerProperty; public class NonIntegerIndexer : NotifyingBase, INonIntegerIndexerDerived { private readonly Dictionary _storage = new Dictionary(); public string this[string key] { get { return _storage[key]; } set { _storage[key] = value; RaisePropertyChanged(CommonPropertyNames.IndexerName); } } } } }