Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(Selector): firing order of data-bound dp & event #6371

Merged
merged 5 commits into from
Aug 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,43 @@ void AdvanceAutomation(string automationStep)
}
}

[Test]
[AutoRetry]
public void ListView_Selection_Events_Ordering()
{
Run("UITests.Windows_UI_Xaml_Controls.ListView.ListView_Selection_Events");
_app.WaitForElement("EventLogs");

var eventLogs = _app.Marked("EventLogs");
var setSelectIndexTo0Button = _app.Marked("SetSelectIndexTo0Button");
var clearLogsButton = _app.Marked("ClearLogsButton");

// selecting item 1 manually
clearLogsButton.Tap(); // clear events proc from setting initial data-context
_app.Tap("Item_1");
var logs = eventLogs.GetDependencyPropertyValue<string>("Text");
Assert.AreEqual(logs, GenerateItemSelectionLogs(1, "Item_1"));

// selecting item 0 programmatically
clearLogsButton.Tap(); // clear events from the step above
setSelectIndexTo0Button.Tap();
logs = eventLogs.GetDependencyPropertyValue<string>("Text");
Assert.AreEqual(logs, GenerateItemSelectionLogs(0, "Item_0"));

string OnPropertyChanged(string name, object value) => $"VM.PropertyChanged: [{name}]->{value}";
string OnSelectionChanged((string, string, int) lv, (string, string, int) vm) => string.Join("\n",
"LV.SelectionChanged: (Item|Value|Index): ",
$"\t- lv:({lv.Item1}|{lv.Item2}|{lv.Item3}), ",
$"\t- vm:({vm.Item1}|{vm.Item2}|{vm.Item3})"
);
string GenerateItemSelectionLogs(int index, string value) => string.Join("\n",
OnPropertyChanged("SelectedIndex", index),
OnPropertyChanged("SelectedItem", value),
OnPropertyChanged("SelectedValue", value),
OnSelectionChanged(lv: (value, value, index), vm: (value, value, index))
);
}

private void ClickCheckBoxAt(int i)
{
_app.Marked("CheckBox").AtIndex(i).Tap();
Expand Down
7 changes: 7 additions & 0 deletions src/SamplesApp/UITests.Shared/UITests.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -1409,6 +1409,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ListView\ListView_Selection_Events.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ListView\ListView_WithScrollViewer.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down Expand Up @@ -5135,6 +5139,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ImageTests\Image_UseTargetSize.xaml.cs">
<DependentUpon>Image_UseTargetSize.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ListView\ListView_Selection_Events.xaml.cs">
<DependentUpon>ListView_Selection_Events.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Controls\ListView\ListView_WithScrollViewer.xaml.cs">
<DependentUpon>ListView_WithScrollViewer.xaml</DependentUpon>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<Page x:Class="UITests.Windows_UI_Xaml_Controls.ListView.ListView_Selection_Events"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UITests.Windows_UI_Xaml_Controls.ListView"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="1*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="1*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>

<TextBlock Grid.Row="0"
Text="Item for selection:" />
<ListView Grid.Row="1"
x:Name="SampleListView"
ItemsSource="{x:Bind ViewModel.Source}"
SelectedIndex="{x:Bind ViewModel.SelectedIndex, Mode=TwoWay}"
SelectedItem="{x:Bind ViewModel.SelectedItem, Mode=TwoWay}"
SelectedValue="{x:Bind ViewModel.SelectedValue, Mode=TwoWay}"
SelectionMode="Single"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock AutomationProperties.AutomationId="{Binding}"
Text="{Binding}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

<TextBlock Grid.Row="2"
Text="Event Logs:" />
<!--<ListView Grid.Row="3"
x:Name="LogListView">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
TextWrapping="Wrap" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>-->
<ScrollViewer Grid.Row="3"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<TextBox x:Name="EventLogs"
IsReadOnly="True"
AcceptsReturn="True"
TextWrapping="Wrap" />
</ScrollViewer>


<StackPanel Grid.Row="4">
<Button x:Name="SetSelectIndexTo0Button"
Content="Set SelectionIndex to 0"
Click="SetSelectIndexTo0" />
<Button x:Name="ClearLogsButton"
Content="Clear Logs"
Click="ClearLogs" />
</StackPanel>
</Grid>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Uno.UI.Samples.Controls;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace UITests.Windows_UI_Xaml_Controls.ListView
{
[SampleControlInfo("ListView", nameof(ListView_Selection_Events), description: "The sequence of events when selecting an item should happen in the same order when compared to uwp.")]
public sealed partial class ListView_Selection_Events : Page
{
private CustomViewModel ViewModel { get; } = new CustomViewModel();

public ListView_Selection_Events()
{
this.InitializeComponent();
SampleListView.SelectionChanged += (s, e) => AddLog(
"LV.SelectionChanged: (Item|Value|Index): " +
$"\n\t- lv:({Format(SampleListView.SelectedItem)}|{Format(SampleListView.SelectedValue)}|{Format(SampleListView.SelectedIndex)}), " +
$"\n\t- vm:({Format(ViewModel.SelectedItem)}|{Format(ViewModel.SelectedValue)}|{Format(ViewModel.SelectedIndex)})"
);
ViewModel.PropertyChanged += (s, e) =>
{
AddLog($"VM.PropertyChanged: [{e.PropertyName}]->{Format(GetValueFrom(e.PropertyName))}");

object GetValueFrom(string propertyName) => propertyName switch
{
nameof(CustomViewModel.SelectedItem) => ViewModel.SelectedItem,
nameof(CustomViewModel.SelectedValue) => ViewModel.SelectedValue,
nameof(CustomViewModel.SelectedIndex) => ViewModel.SelectedIndex,

_ => throw new ArgumentOutOfRangeException(propertyName),
};
};

string Format(object value) => value?.ToString() ?? "<null>";
void AddLog(string log)
{
//LogListView.Items.Add(log);
EventLogs.Text += (string.IsNullOrEmpty(EventLogs.Text) ? null : "\n") + log;
}
}
private void SetSelectIndexTo0(object sender, RoutedEventArgs e)
{
SampleListView.SelectedIndex = 0;
}
private void ClearLogs(object sender, RoutedEventArgs e)
{
//LogListView.Items.Clear();
EventLogs.Text = string.Empty;
}

// not using ViewModelBase because it would fire the same event twice, once as $"{propertyName}" and once as $"Item[{propertyName}]"...
private class CustomViewModel : global::System.ComponentModel.INotifyPropertyChanged
{
public event global::System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

public string[] Source { get; } = Enumerable.Range(0, 5).Select(x => $"Item_{x}").ToArray();

#region SelectedItem
private object _selectedItem;
public object SelectedItem
{
get => _selectedItem;
set => RaiseAndSetIfChanged(ref _selectedItem, value);
}
#endregion
#region SelectedValue
private object _selectedValue;
public object SelectedValue
{
get => _selectedValue;
set => RaiseAndSetIfChanged(ref _selectedValue, value);
}
#endregion
#region SelectedIndex
private int _selectedIndex;
public int SelectedIndex
{
get => _selectedIndex;
set => RaiseAndSetIfChanged(ref _selectedIndex, value);
}
#endregion

protected void RaiseAndSetIfChanged<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(backingField, value))
{
backingField = value;
PropertyChanged?.Invoke(this, new global::System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
using Uno.Extensions;
using Uno.UI.RuntimeTests.Helpers;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Windows.UI.Xaml.Data;

namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls
{
Expand Down Expand Up @@ -1312,11 +1314,47 @@ await WindowHelper.WaitForEqual(0, () =>
Assert.AreEqual(null, list.SelectedItem);
}

[TestMethod]
public async Task When_Selection_Events()
{
var list = new ListView
{
ItemContainerStyle = NoSpaceContainerStyle,
ItemTemplate = FixedSizeItemTemplate
};

var items = Enumerable.Range(0, 4).Select(x => "Item_" + x).ToArray();
list.ItemsSource = items;
list.SelectedIndex = 0;

var model = new When_Selection_Events_DataContext();
list.DataContext = model;
list.SetBinding(Selector.SelectedIndexProperty, new Binding { Path = new PropertyPath(nameof(model.SelectedIndex)), Mode = BindingMode.TwoWay });
list.SetBinding(Selector.SelectedItemProperty, new Binding { Path = new PropertyPath(nameof(model.SelectedItem)), Mode = BindingMode.TwoWay });
list.SetBinding(Selector.SelectedValueProperty, new Binding { Path = new PropertyPath(nameof(model.SelectedValue)), Mode = BindingMode.TwoWay });

WindowHelper.WindowContent = list;
await WindowHelper.WaitForLoaded(list);
await WindowHelper.WaitFor(() => GetPanelChildren(list).Length == 4);

list.SelectionChanged += (s, e) =>
{
Assert.AreEqual(list.SelectedItem, "Item_1");
Assert.AreEqual(list.SelectedValue, "Item_1");
Assert.AreEqual(model.SelectedIndex, 1);
Assert.AreEqual(model.SelectedItem, "Item_1");
Assert.AreEqual(model.SelectedValue, "Item_1");
};

// update selection
list.SelectedIndex = 1;
}

private bool ApproxEquals(double value1, double value2) => Math.Abs(value1 - value2) <= 2;

private class When_Removed_From_Tree_And_Selection_TwoWay_Bound_DataContext : INotifyPropertyChanged
private class When_Removed_From_Tree_And_Selection_TwoWay_Bound_DataContext : global::System.ComponentModel.INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public event global::System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

public string[] MyItems { get; } = new[] { "Red beans", "Rice" };

Expand All @@ -1330,11 +1368,51 @@ public string MySelection
_mySelection = value;
if (changing)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MySelection)));
PropertyChanged?.Invoke(this, new global::System.ComponentModel.PropertyChangedEventArgs(nameof(MySelection)));
}
}
}
}

private class When_Selection_Events_DataContext : global::System.ComponentModel.INotifyPropertyChanged
{
public event global::System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

#region SelectedItem
private object _selectedItem;
public object SelectedItem
{
get => _selectedItem;
set => RaiseAndSetIfChanged(ref _selectedItem, value);
}
#endregion
#region SelectedValue
private object _selectedValue;
public object SelectedValue
{
get => _selectedValue;
set => RaiseAndSetIfChanged(ref _selectedValue, value);
}
#endregion
#region SelectedIndex
private int _selectedIndex;

public int SelectedIndex
{
get => _selectedIndex;
set => RaiseAndSetIfChanged(ref _selectedIndex, value);
}
#endregion

protected void RaiseAndSetIfChanged<T>(ref T backingField, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(backingField, value))
{
backingField = value;
PropertyChanged?.Invoke(this, new global::System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
}

public partial class OnItemsChangedListView : ListView
Expand Down
Loading