From e9388bf21d97c922b9ef84b692826beb676a3a39 Mon Sep 17 00:00:00 2001 From: Nicolai Henriksen Date: Fri, 4 Nov 2022 16:04:16 +0100 Subject: [PATCH 01/23] Rewrite current UI test and add new UI test to test failing scenario --- .../Samples/PasswordBox/BoundPasswordBox.xaml | 19 ++++ .../PasswordBox/BoundPasswordBox.xaml.cs | 38 ++++++++ .../PasswordBox/BoundPasswordBoxViewModel.cs | 9 ++ .../PasswordBox/BoundPasswordBoxWindow.xaml | 10 +++ .../BoundPasswordBoxWindow.xaml.cs | 6 ++ MaterialDesignThemes.UITests/TestBase.cs | 2 +- .../WPF/PasswordBoxes/PasswordBoxTests.cs | 87 ++++++++++++------- 7 files changed, 140 insertions(+), 31 deletions(-) create mode 100644 MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBox.xaml create mode 100644 MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBox.xaml.cs create mode 100644 MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxViewModel.cs create mode 100644 MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml create mode 100644 MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml.cs diff --git a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBox.xaml b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBox.xaml new file mode 100644 index 0000000000..659abe14d7 --- /dev/null +++ b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBox.xaml @@ -0,0 +1,19 @@ + + + + + diff --git a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBox.xaml.cs b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBox.xaml.cs new file mode 100644 index 0000000000..8cbb35c944 --- /dev/null +++ b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBox.xaml.cs @@ -0,0 +1,38 @@ +namespace MaterialDesignThemes.UITests.Samples.PasswordBox; + +public partial class BoundPasswordBox +{ + + + public string? ViewModelPassword + { + get => ((BoundPasswordBoxViewModel) DataContext).Password; + set => ((BoundPasswordBoxViewModel) DataContext).Password = value; + } + + + private bool _useRevealStyle; + public bool UseRevealStyle + { + get => _useRevealStyle; + set + { + _useRevealStyle = value; + if (_useRevealStyle) + { + PasswordBox.Style = (Style)PasswordBox.FindResource("MaterialDesignFloatingHintRevealPasswordBox"); + } + else + { + PasswordBox.ClearValue(StyleProperty); + } + + } + } + + public BoundPasswordBox() + { + DataContext = new BoundPasswordBoxViewModel(); + InitializeComponent(); + } +} diff --git a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxViewModel.cs b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxViewModel.cs new file mode 100644 index 0000000000..3f7b2d1558 --- /dev/null +++ b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxViewModel.cs @@ -0,0 +1,9 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace MaterialDesignThemes.UITests.Samples.PasswordBox; + +internal partial class BoundPasswordBoxViewModel : ObservableObject +{ + [ObservableProperty] + private string? _password; +} diff --git a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml new file mode 100644 index 0000000000..e6ee451792 --- /dev/null +++ b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml @@ -0,0 +1,10 @@ + + + diff --git a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml.cs b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml.cs new file mode 100644 index 0000000000..f129d6381a --- /dev/null +++ b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml.cs @@ -0,0 +1,6 @@ +namespace MaterialDesignThemes.UITests.Samples.PasswordBox; + +public partial class BoundPasswordBoxWindow +{ + public BoundPasswordBoxWindow() => InitializeComponent(); +} diff --git a/MaterialDesignThemes.UITests/TestBase.cs b/MaterialDesignThemes.UITests/TestBase.cs index 58a327c25f..7237018674 100644 --- a/MaterialDesignThemes.UITests/TestBase.cs +++ b/MaterialDesignThemes.UITests/TestBase.cs @@ -41,7 +41,7 @@ protected async Task LoadUserControl() public async Task InitializeAsync() => App = await XamlTest.App.StartRemote(new AppOptions { - AllowVisualStudioDebuggerAttach = true, + AllowVisualStudioDebuggerAttach = false, LogMessage = message => Output.WriteLine(message) }); public async Task DisposeAsync() => await App.DisposeAsync(); diff --git a/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs b/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs index 583cca816d..bac228fd66 100644 --- a/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs +++ b/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using MaterialDesignThemes.UITests.Samples.PasswordBox; namespace MaterialDesignThemes.UITests.WPF.PasswordBoxes { @@ -83,42 +84,36 @@ await Wait.For(async () => } [Fact] - [Description("PR 2828")] + [Description("PR 2828 and Issue 2930")] public async Task RevealPasswordBox_WithBoundPasswordProperty_RespectsThreeWayBinding() { await using var recorder = new TestRecorder(App); - var grid = await LoadXaml(@" - - - - - -"); - var boundPasswordTextBox = await grid.GetElement("BoundPassword"); // Serves as the "VM" in this test - var passwordBox = await grid.GetElement("PasswordBox"); + await App.InitializeWithMaterialDesign(); + IWindow window = await App.CreateWindow(); + var userControl = await window.GetElement(); + await userControl.SetProperty(nameof(BoundPasswordBox.UseRevealStyle), true); + var passwordBox = await userControl.GetElement("PasswordBox"); var clearTextPasswordTextBox = await passwordBox.GetElement("RevealPasswordTextBox"); var revealPasswordButton = await passwordBox.GetElement("RevealPasswordButton"); - - // Act 1 (Update in VM updates PasswordBox and RevealPasswordTextBox) - await boundPasswordTextBox.SendKeyboardInput($"1"); - string? boundText1 = await boundPasswordTextBox.GetProperty(TextBox.TextProperty); + + // Act 1 (Update in PasswordBox updates VM and RevealPasswordTextBox) + await passwordBox.SendKeyboardInput($"1"); + string? boundText1 = await userControl.GetProperty(nameof(BoundPasswordBox.ViewModelPassword)); string? password1 = await passwordBox.GetProperty(nameof(PasswordBox.Password)); string? clearTextPassword1 = await clearTextPasswordTextBox.GetProperty(TextBox.TextProperty); - // Act 2 (Update in PasswordBox updates VM and RevealPasswordTextBox) - await passwordBox.SendKeyboardInput($"2"); - string? boundText2 = await boundPasswordTextBox.GetProperty(TextBox.TextProperty); - string? password2 = await passwordBox.GetProperty(nameof(PasswordBox.Password)); - string? clearTextPassword2 = await clearTextPasswordTextBox.GetProperty(TextBox.TextProperty); - // Act 2 (Update in RevealPasswordTextBox updates PasswordBox and VM) await revealPasswordButton.LeftClick(); await Task.Delay(50); // Wait for the "clear text TextBox" to become visible - await clearTextPasswordTextBox.SendKeyboardInput($"3"); - string? boundText3 = await boundPasswordTextBox.GetProperty(TextBox.TextProperty); + await clearTextPasswordTextBox.SendKeyboardInput($"2"); + string? boundText2 = await userControl.GetProperty(nameof(BoundPasswordBox.ViewModelPassword)); + string? password2 = await passwordBox.GetProperty(nameof(PasswordBox.Password)); + string? clearTextPassword2 = await clearTextPasswordTextBox.GetProperty(TextBox.TextProperty); + + // Act 3 (Update in VM updates PasswordBox and RevealPasswordTextBox) + await userControl.SetProperty(nameof(BoundPasswordBox.ViewModelPassword), "3"); + string? boundText3 = await userControl.GetProperty(nameof(BoundPasswordBox.ViewModelPassword)); string? password3 = await passwordBox.GetProperty(nameof(PasswordBox.Password)); string? clearTextPassword3 = await clearTextPasswordTextBox.GetProperty(TextBox.TextProperty); @@ -127,13 +122,45 @@ public async Task RevealPasswordBox_WithBoundPasswordProperty_RespectsThreeWayBi Assert.Equal("1", password1); Assert.Equal("1", clearTextPassword1); - Assert.Equal("21", boundText2); - Assert.Equal("21", password2); - Assert.Equal("21", clearTextPassword2); + Assert.Equal("12", boundText2); + Assert.Equal("12", password2); + Assert.Equal("12", clearTextPassword2); + + Assert.Equal("3", boundText3); + Assert.Equal("3", password3); + Assert.Equal("3", clearTextPassword3); + + recorder.Success(); + } + + [Fact] + [Description("Issue 2930")] + public async Task PasswordBox_WithBoundPasswordProperty_RespectsBinding() + { + await using var recorder = new TestRecorder(App); + + await App.InitializeWithMaterialDesign(); + IWindow window = await App.CreateWindow(); + var userControl = await window.GetElement(); + await userControl.SetProperty(nameof(BoundPasswordBox.UseRevealStyle), false); + var passwordBox = await userControl.GetElement("PasswordBox"); + + // Act 1 (Update in PasswordBox updates VM) + await passwordBox.SendKeyboardInput($"1"); + string? boundText1 = await userControl.GetProperty(nameof(BoundPasswordBox.ViewModelPassword)); + string? password1 = await passwordBox.GetProperty(nameof(PasswordBox.Password)); + + // Act 2 (Update in VM updates PasswordBox) + await userControl.SetProperty(nameof(BoundPasswordBox.ViewModelPassword), "2"); + string? boundText2 = await userControl.GetProperty(nameof(BoundPasswordBox.ViewModelPassword)); + string? password2 = await passwordBox.GetProperty(nameof(PasswordBox.Password)); + + // Assert + Assert.Equal("1", boundText1); + Assert.Equal("1", password1); - Assert.Equal("321", boundText3); - Assert.Equal("321", password3); - Assert.Equal("321", clearTextPassword3); + Assert.Equal("2", boundText2); + Assert.Equal("2", password2); recorder.Success(); } From 4d9c23389bd16582276a68b65b4d2629e7107d57 Mon Sep 17 00:00:00 2001 From: Nicolai Henriksen Date: Fri, 4 Nov 2022 16:07:48 +0100 Subject: [PATCH 02/23] Refactor PasswordBoxAssist to use behavior --- Directory.packages.props | 3 +- .../MaterialDesignThemes.Wpf.csproj | 3 + MaterialDesignThemes.Wpf/PasswordBoxAssist.cs | 82 +++++++----- .../StylizedBehaviorCollection.cs | 8 ++ MaterialDesignThemes.Wpf/StylizedBehaviors.cs | 122 ++++++++++++++++++ .../MaterialDesignTheme.PasswordBox.xaml | 22 +++- 6 files changed, 206 insertions(+), 34 deletions(-) create mode 100644 MaterialDesignThemes.Wpf/StylizedBehaviorCollection.cs create mode 100644 MaterialDesignThemes.Wpf/StylizedBehaviors.cs diff --git a/Directory.packages.props b/Directory.packages.props index 7f4967fc05..291e6d227e 100644 --- a/Directory.packages.props +++ b/Directory.packages.props @@ -15,6 +15,7 @@ + @@ -26,4 +27,4 @@ - \ No newline at end of file + diff --git a/MaterialDesignThemes.Wpf/MaterialDesignThemes.Wpf.csproj b/MaterialDesignThemes.Wpf/MaterialDesignThemes.Wpf.csproj index 69926b0470..7237e43629 100644 --- a/MaterialDesignThemes.Wpf/MaterialDesignThemes.Wpf.csproj +++ b/MaterialDesignThemes.Wpf/MaterialDesignThemes.Wpf.csproj @@ -24,6 +24,9 @@ PreserveNewest + + + diff --git a/MaterialDesignThemes.Wpf/PasswordBoxAssist.cs b/MaterialDesignThemes.Wpf/PasswordBoxAssist.cs index b6efd5f9f5..8dd0d09ba3 100644 --- a/MaterialDesignThemes.Wpf/PasswordBoxAssist.cs +++ b/MaterialDesignThemes.Wpf/PasswordBoxAssist.cs @@ -1,8 +1,10 @@ -using System.Windows.Data; +using System.Windows.Data; +using System.Windows.Documents; +using Microsoft.Xaml.Behaviors; namespace MaterialDesignThemes.Wpf; -public static class PasswordBoxAssist +public class PasswordBoxAssist : Behavior { public static readonly DependencyProperty PasswordMaskedIconProperty = DependencyProperty.RegisterAttached( "PasswordMaskedIcon", typeof(PackIconKind), typeof(PasswordBoxAssist), new FrameworkPropertyMetadata(PackIconKind.EyeOff, FrameworkPropertyMetadataOptions.Inherits)); @@ -24,45 +26,67 @@ public static class PasswordBoxAssist public static void SetPassword(DependencyObject element, string value) => element.SetValue(PasswordProperty, value); public static string GetPassword(DependencyObject element) => (string)element.GetValue(PasswordProperty); - // Internal attached DP used to initially wire up the connection between the masked PasswordBox content and the clear text TextBox content - internal static readonly DependencyProperty InitialPasswordProperty = DependencyProperty.RegisterAttached( - "InitialPassword", typeof(string), typeof(PasswordBoxAssist), new PropertyMetadata(default(string))); - internal static void SetInitialPassword(DependencyObject element, string value) => element.SetValue(InitialPasswordProperty, value); - internal static string GetInitialPassword(DependencyObject element) => (string)element.GetValue(InitialPasswordProperty); + private static readonly DependencyProperty IsChangingProperty = DependencyProperty.RegisterAttached( + "IsChanging", typeof(bool), typeof(PasswordBoxAssist), new UIPropertyMetadata(false)); + private static void SetIsChanging(UIElement element, bool value) => element.SetValue(IsChangingProperty, value); + private static bool GetIsChanging(UIElement element) => (bool)element.GetValue(IsChangingProperty); + + private static readonly DependencyProperty SelectionProperty = DependencyProperty.RegisterAttached( + "Selection", typeof(TextSelection), typeof(PasswordBoxAssist), new UIPropertyMetadata(default(TextSelection))); + private static void SetSelection(DependencyObject obj, TextSelection? value) => obj.SetValue(SelectionProperty, value); + private static TextSelection? GetSelection(DependencyObject obj) => (TextSelection?)obj.GetValue(SelectionProperty); - private static readonly DependencyProperty IsPasswordInitializedProperty = DependencyProperty.RegisterAttached( - "IsPasswordInitialized", typeof(bool), typeof(PasswordBoxAssist), new PropertyMetadata(false)); + private static readonly DependencyProperty RevealedPasswordTextBoxProperty = DependencyProperty.RegisterAttached( + "RevealedPasswordTextBox", typeof(TextBox), typeof(PasswordBoxAssist), new UIPropertyMetadata(default(TextBox))); + private static void SetRevealedPasswordTextBox(DependencyObject obj, TextBox? value) => obj.SetValue(RevealedPasswordTextBoxProperty, value); + private static TextBox? GetRevealedPasswordTextBox(DependencyObject obj) => (TextBox?)obj.GetValue(RevealedPasswordTextBoxProperty); - private static readonly DependencyProperty SettingPasswordProperty = DependencyProperty.RegisterAttached( - "SettingPassword", typeof(bool), typeof(PasswordBoxAssist), new PropertyMetadata(false)); - private static void HandlePasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + /// + /// Handles changes to the 'Password' attached property. + /// + private static void HandlePasswordChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { - if (d is not PasswordBox passwordBox) - return; - - if ((bool)passwordBox.GetValue(SettingPasswordProperty)) - return; + if (sender is PasswordBox targetPasswordBox) + { + targetPasswordBox.PasswordChanged -= PasswordBoxPasswordChanged; + if (!GetIsChanging(targetPasswordBox)) + { + targetPasswordBox.Password = (string)e.NewValue; + } + targetPasswordBox.PasswordChanged += PasswordBoxPasswordChanged; + } + } - if (!(bool)passwordBox.GetValue(IsPasswordInitializedProperty)) + /// + /// Handle the 'PasswordChanged'-event on the PasswordBox + /// + private static void PasswordBoxPasswordChanged(object sender, RoutedEventArgs e) + { + if (sender is PasswordBox passwordBox) { - passwordBox.SetValue(IsPasswordInitializedProperty, true); - WeakEventManager.AddHandler(passwordBox, nameof(PasswordBox.PasswordChanged), HandlePasswordChanged); + SetIsChanging(passwordBox, true); + SetPassword(passwordBox, passwordBox.Password); + SetIsChanging(passwordBox, false); } - passwordBox.Password = e.NewValue as string; } - private static void HandlePasswordChanged(object? sender, RoutedEventArgs e) + private void PasswordBoxLoaded(object sender, RoutedEventArgs e) => SetPassword(AssociatedObject, AssociatedObject.Password); + + protected override void OnAttached() { - if (sender is not PasswordBox passwordBox) - return; + base.OnAttached(); + AssociatedObject.PasswordChanged += PasswordBoxPasswordChanged; + AssociatedObject.Loaded += PasswordBoxLoaded; + } - passwordBox.SetValue(SettingPasswordProperty, true); - string currentPassword = GetPassword(passwordBox); - if (currentPassword != passwordBox.Password) + protected override void OnDetaching() + { + if (AssociatedObject != null) { - SetPassword(passwordBox, passwordBox.Password); + AssociatedObject.Loaded -= PasswordBoxLoaded; + AssociatedObject.PasswordChanged -= PasswordBoxPasswordChanged; } - passwordBox.SetValue(SettingPasswordProperty, false); + base.OnDetaching(); } } diff --git a/MaterialDesignThemes.Wpf/StylizedBehaviorCollection.cs b/MaterialDesignThemes.Wpf/StylizedBehaviorCollection.cs new file mode 100644 index 0000000000..fa627fe3e6 --- /dev/null +++ b/MaterialDesignThemes.Wpf/StylizedBehaviorCollection.cs @@ -0,0 +1,8 @@ +using Microsoft.Xaml.Behaviors; + +namespace MaterialDesignThemes.Wpf; + +public class StylizedBehaviorCollection : FreezableCollection +{ + protected override Freezable CreateInstanceCore() => new StylizedBehaviorCollection(); +} diff --git a/MaterialDesignThemes.Wpf/StylizedBehaviors.cs b/MaterialDesignThemes.Wpf/StylizedBehaviors.cs new file mode 100644 index 0000000000..ca72e4590d --- /dev/null +++ b/MaterialDesignThemes.Wpf/StylizedBehaviors.cs @@ -0,0 +1,122 @@ +using Microsoft.Xaml.Behaviors; + +namespace MaterialDesignThemes.Wpf; + +internal class StylizedBehaviors +{ + private static readonly DependencyProperty OriginalBehaviorProperty = DependencyProperty.RegisterAttached( + "OriginalBehavior", typeof(Behavior), typeof(StylizedBehaviors), new UIPropertyMetadata(null)); + private static void SetOriginalBehavior(DependencyObject obj, Behavior? value) => obj.SetValue(OriginalBehaviorProperty, value); + private static Behavior? GetOriginalBehavior(DependencyObject obj) => (Behavior?)obj.GetValue(OriginalBehaviorProperty); + + internal static readonly DependencyProperty BehaviorsProperty = DependencyProperty.RegisterAttached( + "Behaviors", typeof(StylizedBehaviorCollection), typeof(StylizedBehaviors), new FrameworkPropertyMetadata(null, OnPropertyChanged)); + internal static void SetBehaviors(DependencyObject uie, StylizedBehaviorCollection? value) => uie.SetValue(BehaviorsProperty, value); + internal static StylizedBehaviorCollection? GetBehaviors(DependencyObject uie) => (StylizedBehaviorCollection?)uie.GetValue(BehaviorsProperty); + + private static void OnPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e) + { + if (dpo is not FrameworkElement frameworkElement) + { + return; + } + + var newBehaviors = e.NewValue as StylizedBehaviorCollection; + var oldBehaviors = e.OldValue as StylizedBehaviorCollection; + if (newBehaviors == oldBehaviors) + { + return; + } + + var itemBehaviors = Interaction.GetBehaviors(frameworkElement); + frameworkElement.Unloaded -= FrameworkElementUnloaded; + if (oldBehaviors != null) + { + foreach (var behavior in oldBehaviors) + { + int index = GetIndexOf(itemBehaviors, behavior); + if (index >= 0) + { + itemBehaviors.RemoveAt(index); + } + } + } + + if (newBehaviors != null) + { + foreach (var behavior in newBehaviors) + { + int index = GetIndexOf(itemBehaviors, behavior); + if (index < 0) + { + var clone = (Behavior)behavior.Clone(); + SetOriginalBehavior(clone, behavior); + itemBehaviors.Add(clone); + } + } + } + + if (itemBehaviors.Count > 0) + { + frameworkElement.Unloaded += FrameworkElementUnloaded; + } + } + + private static void FrameworkElementUnloaded(object sender, RoutedEventArgs e) + { + // BehaviorCollection doesn't call Detach, so we do this + if (sender is not FrameworkElement frameworkElement) + { + return; + } + + var itemBehaviors = Interaction.GetBehaviors(frameworkElement); + foreach (var behavior in itemBehaviors) + { + behavior.Detach(); + } + + frameworkElement.Loaded += FrameworkElementLoaded; + } + + private static void FrameworkElementLoaded(object sender, RoutedEventArgs e) + { + if (sender is not FrameworkElement frameworkElement) + { + return; + } + + frameworkElement.Loaded -= FrameworkElementLoaded; + var itemBehaviors = Interaction.GetBehaviors(frameworkElement); + foreach (var behavior in itemBehaviors) + { + behavior.Attach(frameworkElement); + } + } + + private static int GetIndexOf(BehaviorCollection itemBehaviors, Behavior behavior) + { + int index = -1; + + var originalBehavior = GetOriginalBehavior(behavior); + + for (int i = 0; i < itemBehaviors.Count; i++) + { + var currentBehavior = itemBehaviors[i]; + if (currentBehavior == behavior || currentBehavior == originalBehavior) + { + index = i; + break; + } + + var currentOriginalBehavior = GetOriginalBehavior(currentBehavior); + if (currentOriginalBehavior == behavior || currentOriginalBehavior == originalBehavior) + { + index = i; + break; + } + } + + return index; + } +} diff --git a/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.PasswordBox.xaml b/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.PasswordBox.xaml index f74378fa66..e062ddebaa 100644 --- a/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.PasswordBox.xaml +++ b/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.PasswordBox.xaml @@ -1,8 +1,10 @@ - + xmlns:behaviors="http://schemas.microsoft.com/xaml/behaviors" + xmlns:wpf="clr-namespace:MaterialDesignThemes.Wpf" + xmlns:internalbehaviors="clr-namespace:MaterialDesignThemes.Wpf.Behaviors"> @@ -443,6 +445,13 @@ + + + + + + + @@ -956,11 +956,11 @@ - + - + - + From 81248834052e390f69f8e837673fafc621f72b3b Mon Sep 17 00:00:00 2001 From: Kevin Bost Date: Mon, 7 Nov 2022 00:50:49 -0800 Subject: [PATCH 18/23] Adding screenshots for debugging --- .../WPF/PasswordBoxes/PasswordBoxTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs b/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs index 990438b564..3e0b9bc641 100644 --- a/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs +++ b/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs @@ -102,10 +102,13 @@ public async Task RevealPasswordBox_WithBoundPasswordProperty_RespectsThreeWayBi string? boundText1 = await userControl.GetProperty(nameof(BoundPasswordBox.ViewModelPassword)); string? password1 = await passwordBox.GetProperty(nameof(PasswordBox.Password)); string? clearTextPassword1 = await clearTextPasswordTextBox.GetProperty(TextBox.TextProperty); + await recorder.SaveScreenshot(); // Act 2 (Update in RevealPasswordTextBox updates PasswordBox and VM) await revealPasswordButton.LeftClick(); + await recorder.SaveScreenshot(); await Task.Delay(50); // Wait for the "clear text TextBox" to become visible + await recorder.SaveScreenshot(); await clearTextPasswordTextBox.SendKeyboardInput($"2"); string? boundText2 = await userControl.GetProperty(nameof(BoundPasswordBox.ViewModelPassword)); string? password2 = await passwordBox.GetProperty(nameof(PasswordBox.Password)); From fac1639237ed30b3d6bdd0ea6e856f28eef2bc87 Mon Sep 17 00:00:00 2001 From: Nicolai Henriksen Date: Mon, 7 Nov 2022 21:00:14 +0100 Subject: [PATCH 19/23] Attempt at fixing failing UI test --- .../Samples/PasswordBox/BoundPasswordBoxWindow.xaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml index e6ee451792..f2c448af7c 100644 --- a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml +++ b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml @@ -4,7 +4,16 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:MaterialDesignThemes.UITests.Samples.PasswordBox" + xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" + Background="{DynamicResource MaterialDesignPaper}" + FontFamily="{materialDesign:MaterialDesignFont}" + TextElement.FontSize="13" + TextElement.FontWeight="Regular" + TextElement.Foreground="{DynamicResource MaterialDesignBody}" + TextOptions.TextFormattingMode="Ideal" + TextOptions.TextRenderingMode="Auto" + WindowStartupLocation="CenterScreen" Title="BoundPasswordBoxWindow" Height="450" Width="800"> From b52a9cec7beb4d64826bb4113545c8ff5b340406 Mon Sep 17 00:00:00 2001 From: Nicolai Henriksen Date: Mon, 7 Nov 2022 21:18:24 +0100 Subject: [PATCH 20/23] Adding more screenshots for debugging --- .../WPF/PasswordBoxes/PasswordBoxTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs b/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs index 3e0b9bc641..a72b8e9c98 100644 --- a/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs +++ b/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs @@ -91,12 +91,14 @@ public async Task RevealPasswordBox_WithBoundPasswordProperty_RespectsThreeWayBi await App.InitializeWithMaterialDesign(); IWindow window = await App.CreateWindow(); + await recorder.SaveScreenshot(); var userControl = await window.GetElement(); await userControl.SetProperty(nameof(BoundPasswordBox.UseRevealStyle), true); var passwordBox = await userControl.GetElement("PasswordBox"); var clearTextPasswordTextBox = await passwordBox.GetElement("RevealPasswordTextBox"); var revealPasswordButton = await passwordBox.GetElement("RevealPasswordButton"); - + await recorder.SaveScreenshot(); + // Act 1 (Update in PasswordBox updates VM and RevealPasswordTextBox) await passwordBox.SendKeyboardInput($"1"); string? boundText1 = await userControl.GetProperty(nameof(BoundPasswordBox.ViewModelPassword)); From 100bad6af418e0de236d2e00d0a139dd6094bdc3 Mon Sep 17 00:00:00 2001 From: Nicolai Henriksen Date: Mon, 7 Nov 2022 21:32:28 +0100 Subject: [PATCH 21/23] Activate test window on loaded event --- .../Samples/PasswordBox/BoundPasswordBoxWindow.xaml | 1 + .../Samples/PasswordBox/BoundPasswordBoxWindow.xaml.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml index f2c448af7c..a63cc41486 100644 --- a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml +++ b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml @@ -14,6 +14,7 @@ TextOptions.TextFormattingMode="Ideal" TextOptions.TextRenderingMode="Auto" WindowStartupLocation="CenterScreen" + Loaded="BoundPasswordBoxWindow_OnLoaded" Title="BoundPasswordBoxWindow" Height="450" Width="800"> diff --git a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml.cs b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml.cs index f129d6381a..c97320eac8 100644 --- a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml.cs +++ b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml.cs @@ -3,4 +3,6 @@ public partial class BoundPasswordBoxWindow { public BoundPasswordBoxWindow() => InitializeComponent(); + + private void BoundPasswordBoxWindow_OnLoaded(object sender, RoutedEventArgs e) => Activate(); } From bfbd3d54a69d6eae03c7d67fd37e7d5b2916885f Mon Sep 17 00:00:00 2001 From: Nicolai Henriksen Date: Mon, 7 Nov 2022 21:47:31 +0100 Subject: [PATCH 22/23] Yet another attempt at getting test window shown --- .../Samples/PasswordBox/BoundPasswordBoxWindow.xaml | 6 ++++-- .../Samples/PasswordBox/BoundPasswordBoxWindow.xaml.cs | 8 +++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml index a63cc41486..da1370246a 100644 --- a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml +++ b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml @@ -6,6 +6,9 @@ xmlns:local="clr-namespace:MaterialDesignThemes.UITests.Samples.PasswordBox" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" + Width="1100" + Height="800" + Title="BoundPasswordBoxWindow" Background="{DynamicResource MaterialDesignPaper}" FontFamily="{materialDesign:MaterialDesignFont}" TextElement.FontSize="13" @@ -14,7 +17,6 @@ TextOptions.TextFormattingMode="Ideal" TextOptions.TextRenderingMode="Auto" WindowStartupLocation="CenterScreen" - Loaded="BoundPasswordBoxWindow_OnLoaded" - Title="BoundPasswordBoxWindow" Height="450" Width="800"> + Loaded="BoundPasswordBoxWindow_OnLoaded"> diff --git a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml.cs b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml.cs index c97320eac8..b4bae3721a 100644 --- a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml.cs +++ b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml.cs @@ -4,5 +4,11 @@ public partial class BoundPasswordBoxWindow { public BoundPasswordBoxWindow() => InitializeComponent(); - private void BoundPasswordBoxWindow_OnLoaded(object sender, RoutedEventArgs e) => Activate(); + private void BoundPasswordBoxWindow_OnLoaded(object sender, RoutedEventArgs e) + { + Activate(); + Topmost = true; + Topmost = false; + Focus(); + } } From afbc7300cf7828db4afa8d3326f880665febded1 Mon Sep 17 00:00:00 2001 From: Nicolai Henriksen Date: Mon, 7 Nov 2022 21:59:24 +0100 Subject: [PATCH 23/23] Removing debugging screenshots --- .../Samples/PasswordBox/BoundPasswordBoxWindow.xaml | 4 ++-- .../WPF/PasswordBoxes/PasswordBoxTests.cs | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml index da1370246a..251c50acc1 100644 --- a/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml +++ b/MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml @@ -6,8 +6,8 @@ xmlns:local="clr-namespace:MaterialDesignThemes.UITests.Samples.PasswordBox" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" - Width="1100" - Height="800" + Height="450" + Width="800" Title="BoundPasswordBoxWindow" Background="{DynamicResource MaterialDesignPaper}" FontFamily="{materialDesign:MaterialDesignFont}" diff --git a/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs b/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs index a72b8e9c98..a8b72dca81 100644 --- a/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs +++ b/MaterialDesignThemes.UITests/WPF/PasswordBoxes/PasswordBoxTests.cs @@ -91,26 +91,21 @@ public async Task RevealPasswordBox_WithBoundPasswordProperty_RespectsThreeWayBi await App.InitializeWithMaterialDesign(); IWindow window = await App.CreateWindow(); - await recorder.SaveScreenshot(); var userControl = await window.GetElement(); await userControl.SetProperty(nameof(BoundPasswordBox.UseRevealStyle), true); var passwordBox = await userControl.GetElement("PasswordBox"); var clearTextPasswordTextBox = await passwordBox.GetElement("RevealPasswordTextBox"); var revealPasswordButton = await passwordBox.GetElement("RevealPasswordButton"); - await recorder.SaveScreenshot(); // Act 1 (Update in PasswordBox updates VM and RevealPasswordTextBox) await passwordBox.SendKeyboardInput($"1"); string? boundText1 = await userControl.GetProperty(nameof(BoundPasswordBox.ViewModelPassword)); string? password1 = await passwordBox.GetProperty(nameof(PasswordBox.Password)); string? clearTextPassword1 = await clearTextPasswordTextBox.GetProperty(TextBox.TextProperty); - await recorder.SaveScreenshot(); // Act 2 (Update in RevealPasswordTextBox updates PasswordBox and VM) await revealPasswordButton.LeftClick(); - await recorder.SaveScreenshot(); await Task.Delay(50); // Wait for the "clear text TextBox" to become visible - await recorder.SaveScreenshot(); await clearTextPasswordTextBox.SendKeyboardInput($"2"); string? boundText2 = await userControl.GetProperty(nameof(BoundPasswordBox.ViewModelPassword)); string? password2 = await passwordBox.GetProperty(nameof(PasswordBox.Password));