diff --git a/src/ReactiveUI.Tests/Mocks/MockBindListItemViewModel.cs b/src/ReactiveUI.Tests/Mocks/MockBindListItemViewModel.cs
new file mode 100644
index 0000000000..e44f2378ef
--- /dev/null
+++ b/src/ReactiveUI.Tests/Mocks/MockBindListItemViewModel.cs
@@ -0,0 +1,23 @@
+// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+namespace ReactiveUI.Tests
+{
+ public class MockBindListItemViewModel : ReactiveObject
+ {
+ private string _name = string.Empty;
+
+ public MockBindListItemViewModel(string name) => Name = name;
+
+ ///
+ /// Gets or sets displayed name of the crumb.
+ ///
+ public string Name
+ {
+ get => _name;
+ set => this.RaiseAndSetIfChanged(ref _name, value);
+ }
+ }
+}
diff --git a/src/ReactiveUI.Tests/Mocks/MockBindListView.cs b/src/ReactiveUI.Tests/Mocks/MockBindListView.cs
new file mode 100644
index 0000000000..70cda838ff
--- /dev/null
+++ b/src/ReactiveUI.Tests/Mocks/MockBindListView.cs
@@ -0,0 +1,80 @@
+// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System.IO;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Markup;
+
+namespace ReactiveUI.Tests
+{
+ ///
+ /// MockBindListView.
+ ///
+ ///
+ public class MockBindListView : UserControl, IViewFor
+ {
+ public static readonly DependencyProperty ViewModelProperty =
+ DependencyProperty.Register(nameof(ViewModel), typeof(MockBindListViewModel), typeof(MockBindListView), new PropertyMetadata(null));
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MockBindListView()
+ {
+ ItemList = new();
+
+ var ms = new MemoryStream(Encoding.UTF8.GetBytes(@"
+
+
+
+
+ "));
+ ItemList.ItemTemplate = (DataTemplate)XamlReader.Load(ms);
+ var ms1 = new MemoryStream(Encoding.UTF8.GetBytes(@"
+
+
+ "));
+ ItemList.ItemsPanel = (ItemsPanelTemplate)XamlReader.Load(ms1);
+
+ ViewModel = new();
+ this.WhenActivated(d =>
+ {
+ this.OneWayBind(ViewModel, vm => vm.ListItems, v => v.ItemList.ItemsSource).DisposeWith(d);
+ this.WhenAnyValue(v => v.ItemList.SelectedItem)
+ .Where(i => i != null)
+ .Cast()
+ .Do(_ => ItemList.UnselectAll())
+ .InvokeCommand(this, v => v.ViewModel!.SelectItem).DisposeWith(d);
+ });
+ }
+
+ ///
+ /// Gets or sets the ViewModel corresponding to this specific View. This should be
+ /// a DependencyProperty if you're using XAML.
+ ///
+ public MockBindListViewModel? ViewModel
+ {
+ get => (MockBindListViewModel)GetValue(ViewModelProperty);
+ set => SetValue(ViewModelProperty, value);
+ }
+
+ public ListView ItemList { get; }
+
+ object? IViewFor.ViewModel
+ {
+ get => ViewModel;
+ set => ViewModel = (MockBindListViewModel?)value;
+ }
+ }
+}
diff --git a/src/ReactiveUI.Tests/Mocks/MockBindListViewModel.cs b/src/ReactiveUI.Tests/Mocks/MockBindListViewModel.cs
new file mode 100644
index 0000000000..15e0b36ec4
--- /dev/null
+++ b/src/ReactiveUI.Tests/Mocks/MockBindListViewModel.cs
@@ -0,0 +1,74 @@
+// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved.
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for full license information.
+
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reactive;
+using System.Reactive.Linq;
+using DynamicData;
+
+namespace ReactiveUI.Tests
+{
+ public class MockBindListViewModel : ReactiveObject
+ {
+ private readonly ObservableAsPropertyHelper _activeItem;
+ private readonly ReadOnlyObservableCollection _listItems;
+
+ static MockBindListViewModel()
+ {
+ Splat.Locator.CurrentMutable.Register(() => new MockBindListView(), typeof(IViewFor));
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MockBindListViewModel()
+ {
+ SelectItem = ReactiveCommand.Create((MockBindListItemViewModel item) =>
+ {
+ ActiveListItem.Edit(l =>
+ {
+ var index = l.IndexOf(item);
+ for (var i = l.Count - 1; i > index; i--)
+ {
+ l.RemoveAt(i);
+ }
+ });
+ });
+
+ ActiveListItem.Connect()
+ .Select(_ => ActiveListItem.Count > 0 ? ActiveListItem.Items.ElementAt(ActiveListItem.Count - 1) : null)
+ .ToProperty(this, vm => vm.ActiveItem, out _activeItem);
+
+ ActiveListItem.Connect().ObserveOn(RxApp.MainThreadScheduler).Bind(out _listItems).Subscribe();
+ }
+
+ ///
+ /// Gets the item that is currently loaded in the list.
+ /// Add or remove elements to modify the list.
+ ///
+ public ISourceList ActiveListItem { get; } = new SourceList();
+
+ ///
+ /// Gets the deepest item of the currect list. (Last element of ActiveListItem).
+ ///
+ public MockBindListItemViewModel? ActiveItem => _activeItem.Value;
+
+ ///
+ /// Gets the items to be represented by the selected item which is passed as a parameter.
+ /// Only this item and its ancestors are kept, the rest of the items are removed.
+ ///
+ public ReactiveCommand SelectItem { get; }
+
+ ///
+ /// Gets the list items.
+ ///
+ ///
+ /// The list items.
+ ///
+ public ReadOnlyObservableCollection ListItems => _listItems;
+ }
+}
diff --git a/src/ReactiveUI.Tests/Platforms/windows-xaml/PropertyBindingTest.cs b/src/ReactiveUI.Tests/Platforms/windows-xaml/PropertyBindingTest.cs
index 1543d4a25b..54b34a75e7 100644
--- a/src/ReactiveUI.Tests/Platforms/windows-xaml/PropertyBindingTest.cs
+++ b/src/ReactiveUI.Tests/Platforms/windows-xaml/PropertyBindingTest.cs
@@ -8,10 +8,14 @@
using System.Globalization;
using System.Linq;
using System.Reactive;
+using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
+using System.Windows;
+using DynamicData;
using DynamicData.Binding;
+using ReactiveUI.Tests.Wpf;
using Xunit;
#if NETFX_CORE
@@ -689,5 +693,41 @@ public void BindWithFuncToTriggerUpdateTest()
dis.Dispose();
Assert.True(dis.IsDisposed);
}
+
+ [StaFact]
+ public void BindListFunctionalTest()
+ {
+ var window = new WpfTestWindow();
+ var view = new MockBindListView();
+ window.RootGrid.Children.Add(view);
+
+ var loaded = new RoutedEventArgs
+ {
+ RoutedEvent = FrameworkElement.LoadedEvent
+ };
+
+ window.RaiseEvent(loaded);
+ view.RaiseEvent(loaded);
+ var test1 = new MockBindListItemViewModel("Test1");
+ view.ViewModel?.ActiveListItem.Add(test1);
+ Assert.Equal(1, view.ItemList.Items.Count);
+ Assert.Equal(test1, view.ViewModel!.ActiveItem);
+
+ var test2 = new MockBindListItemViewModel("Test2");
+ view.ViewModel?.ActiveListItem.Add(test2);
+ Assert.Equal(2, view.ItemList.Items.Count);
+ Assert.Equal(test2, view.ViewModel!.ActiveItem);
+
+ var test3 = new MockBindListItemViewModel("Test3");
+ view.ViewModel?.ActiveListItem.Add(test3);
+ Assert.Equal(3, view.ItemList.Items.Count);
+ Assert.Equal(test3, view.ViewModel!.ActiveItem);
+
+ view.ItemList.SelectedItem = view.ItemList.Items.GetItemAt(0);
+ Assert.Equal(1, view.ItemList.Items.Count);
+ Assert.Equal(test1, view.ViewModel!.ActiveItem);
+
+ window.Dispatcher.InvokeShutdown();
+ }
}
}
diff --git a/src/ReactiveUI.Tests/ReactiveUI.Tests.csproj b/src/ReactiveUI.Tests/ReactiveUI.Tests.csproj
index f70c13f4a3..5d5db8634d 100644
--- a/src/ReactiveUI.Tests/ReactiveUI.Tests.csproj
+++ b/src/ReactiveUI.Tests/ReactiveUI.Tests.csproj
@@ -1,4 +1,4 @@
-
+
netcoreapp3.1
@@ -60,19 +60,15 @@
+
+ true
+ true
+
-
-
-
-
-
-
-
-
@@ -81,6 +77,10 @@
+
+
+
+
True
diff --git a/src/ReactiveUI.sln b/src/ReactiveUI.sln
index 0762ed0ad2..4175d99f02 100644
--- a/src/ReactiveUI.sln
+++ b/src/ReactiveUI.sln
@@ -59,6 +59,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.Drawing", "React
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.XamForms.Tests", "ReactiveUI.XamForms.Tests\ReactiveUI.XamForms.Tests.csproj", "{46D5C71E-2E58-4454-BE3A-30B9047A2D1E}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Src", "Src", "{EF7ED1B0-00E4-4CD0-9741-0D1D4463B8EC}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{2AE709FA-BE58-4287-BFD3-E80BEB605125}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -153,6 +157,29 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {464CB812-F99F-401B-BE4C-E8F0515CD19D} = {EF7ED1B0-00E4-4CD0-9741-0D1D4463B8EC}
+ {F5A6E11B-B074-4A1C-B937-267D840E31DF} = {EF7ED1B0-00E4-4CD0-9741-0D1D4463B8EC}
+ {DDF89A7A-5CC9-4243-98E4-462860D5D963} = {EF7ED1B0-00E4-4CD0-9741-0D1D4463B8EC}
+ {C11F6165-6142-476F-83F1-CFEBC330C769} = {EF7ED1B0-00E4-4CD0-9741-0D1D4463B8EC}
+ {84A9E530-93D7-4108-9887-690127F70AF5} = {EF7ED1B0-00E4-4CD0-9741-0D1D4463B8EC}
+ {B311B0EC-CEF3-45E6-BA7A-EC6AB58E7E7D} = {EF7ED1B0-00E4-4CD0-9741-0D1D4463B8EC}
+ {2ADE0A50-5012-4341-8F4F-97597C2D6920} = {2AE709FA-BE58-4287-BFD3-E80BEB605125}
+ {15CF3AA6-9F7C-4F23-BAE7-4A93352E94B6} = {EF7ED1B0-00E4-4CD0-9741-0D1D4463B8EC}
+ {1AC71A71-F5F3-4F96-BDA9-A9DC7F572DB9} = {2AE709FA-BE58-4287-BFD3-E80BEB605125}
+ {7DE43BB9-5AC8-446A-8D8B-88C9201D802E} = {EF7ED1B0-00E4-4CD0-9741-0D1D4463B8EC}
+ {20750BB4-36DD-4F8C-B970-D7809810EC98} = {EF7ED1B0-00E4-4CD0-9741-0D1D4463B8EC}
+ {404B0F3F-7343-4E54-A863-F27B99FE788B} = {2AE709FA-BE58-4287-BFD3-E80BEB605125}
+ {7ED6D69F-138F-40BD-9F37-3E4050E4D19B} = {2AE709FA-BE58-4287-BFD3-E80BEB605125}
+ {CD8B19A9-316E-4FBC-8F0C-87ADC6AAD684} = {2AE709FA-BE58-4287-BFD3-E80BEB605125}
+ {36FC3269-B7D0-4D79-A54A-B26B6190E8A2} = {EF7ED1B0-00E4-4CD0-9741-0D1D4463B8EC}
+ {0C7EDFF0-80BE-4FFC-A1B9-0C48043B71F3} = {EF7ED1B0-00E4-4CD0-9741-0D1D4463B8EC}
+ {86430CEC-BAA3-4ED4-A90D-982437137D19} = {EF7ED1B0-00E4-4CD0-9741-0D1D4463B8EC}
+ {366789D4-4117-46CE-864F-BF1201F35319} = {EF7ED1B0-00E4-4CD0-9741-0D1D4463B8EC}
+ {1FD70B38-E2FB-4A46-BE07-C0F6ACE6FD90} = {2AE709FA-BE58-4287-BFD3-E80BEB605125}
+ {999D555D-C567-457C-95F7-8AD61310C56E} = {EF7ED1B0-00E4-4CD0-9741-0D1D4463B8EC}
+ {46D5C71E-2E58-4454-BE3A-30B9047A2D1E} = {2AE709FA-BE58-4287-BFD3-E80BEB605125}
+ EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9326B58C-0AD3-4527-B3F4-86B54673C62E}
EndGlobalSection