diff --git a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net472.approved.txt b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net472.approved.txt index d3074429d1..9f4af14a9e 100644 --- a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net472.approved.txt +++ b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net472.approved.txt @@ -72,11 +72,11 @@ namespace ReactiveUI where TView : class, ReactiveUI.IViewFor where TViewModel : class where TProp : System.Windows.Input.ICommand { } - public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.IObservable withParameter, string? toEvent = null) + public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.IObservable withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class where TProp : System.Windows.Input.ICommand { } - public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) + public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class where TProp : System.Windows.Input.ICommand { } @@ -84,11 +84,11 @@ namespace ReactiveUI public class CommandBinderImplementation : Splat.IEnableLogger { public CommandBinderImplementation() { } - public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.Func withParameter, string? toEvent = null) + public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.IObservable withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class where TProp : System.Windows.Input.ICommand { } - public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.IObservable withParameter, string? toEvent = null) + public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class where TProp : System.Windows.Input.ICommand { } @@ -431,6 +431,10 @@ namespace ReactiveUI public void Dispose() { } public static ReactiveUI.ObservableAsPropertyHelper Default(T? initialValue = default, System.Reactive.Concurrency.IScheduler? scheduler = null) { } } + public static class ObservableFuncMixins + { + public static System.IObservable ToObservable(this TSource? source, System.Linq.Expressions.Expression> expression, bool beforeChange = false, bool skipInitial = false) { } + } public static class ObservableLoggingMixin { public static System.IObservable Log(this System.IObservable @this, TObj logObject, string? message = null, System.Func? stringifier = null) diff --git a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net5.0.approved.txt b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net5.0.approved.txt index 6ad256a9a5..f0f88b0dab 100644 --- a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net5.0.approved.txt +++ b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.net5.0.approved.txt @@ -74,11 +74,11 @@ namespace ReactiveUI where TView : class, ReactiveUI.IViewFor where TViewModel : class where TProp : System.Windows.Input.ICommand { } - public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.IObservable withParameter, string? toEvent = null) + public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.IObservable withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class where TProp : System.Windows.Input.ICommand { } - public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) + public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class where TProp : System.Windows.Input.ICommand { } @@ -86,11 +86,11 @@ namespace ReactiveUI public class CommandBinderImplementation : Splat.IEnableLogger { public CommandBinderImplementation() { } - public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.Func withParameter, string? toEvent = null) + public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.IObservable withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class where TProp : System.Windows.Input.ICommand { } - public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.IObservable withParameter, string? toEvent = null) + public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class where TProp : System.Windows.Input.ICommand { } @@ -426,6 +426,10 @@ namespace ReactiveUI public void Dispose() { } public static ReactiveUI.ObservableAsPropertyHelper Default(T? initialValue = default, System.Reactive.Concurrency.IScheduler? scheduler = null) { } } + public static class ObservableFuncMixins + { + public static System.IObservable ToObservable(this TSource? source, System.Linq.Expressions.Expression> expression, bool beforeChange = false, bool skipInitial = false) { } + } public static class ObservableLoggingMixin { public static System.IObservable Log(this System.IObservable @this, TObj logObject, string? message = null, System.Func? stringifier = null) diff --git a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.netcoreapp3.1.approved.txt b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.netcoreapp3.1.approved.txt index aa48602277..7a858719b5 100644 --- a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.netcoreapp3.1.approved.txt +++ b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.netcoreapp3.1.approved.txt @@ -72,11 +72,11 @@ namespace ReactiveUI where TView : class, ReactiveUI.IViewFor where TViewModel : class where TProp : System.Windows.Input.ICommand { } - public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.IObservable withParameter, string? toEvent = null) + public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.IObservable withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class where TProp : System.Windows.Input.ICommand { } - public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) + public static ReactiveUI.IReactiveBinding BindCommand(this TView view, TViewModel? viewModel, System.Linq.Expressions.Expression> propertyName, System.Linq.Expressions.Expression> controlName, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class where TProp : System.Windows.Input.ICommand { } @@ -84,11 +84,11 @@ namespace ReactiveUI public class CommandBinderImplementation : Splat.IEnableLogger { public CommandBinderImplementation() { } - public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.Func withParameter, string? toEvent = null) + public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.IObservable withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class where TProp : System.Windows.Input.ICommand { } - public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.IObservable withParameter, string? toEvent = null) + public ReactiveUI.IReactiveBinding BindCommand(TViewModel? viewModel, TView view, System.Linq.Expressions.Expression> vmProperty, System.Linq.Expressions.Expression> controlProperty, System.Linq.Expressions.Expression> withParameter, string? toEvent = null) where TView : class, ReactiveUI.IViewFor where TViewModel : class where TProp : System.Windows.Input.ICommand { } @@ -424,6 +424,10 @@ namespace ReactiveUI public void Dispose() { } public static ReactiveUI.ObservableAsPropertyHelper Default(T? initialValue = default, System.Reactive.Concurrency.IScheduler? scheduler = null) { } } + public static class ObservableFuncMixins + { + public static System.IObservable ToObservable(this TSource? source, System.Linq.Expressions.Expression> expression, bool beforeChange = false, bool skipInitial = false) { } + } public static class ObservableLoggingMixin { public static System.IObservable Log(this System.IObservable @this, TObj logObject, string? message = null, System.Func? stringifier = null) diff --git a/src/ReactiveUI.Tests/Locator/DefaultViewLocatorTests.cs b/src/ReactiveUI.Tests/Locator/DefaultViewLocatorTests.cs index 7018c16568..0427b4a639 100644 --- a/src/ReactiveUI.Tests/Locator/DefaultViewLocatorTests.cs +++ b/src/ReactiveUI.Tests/Locator/DefaultViewLocatorTests.cs @@ -65,7 +65,6 @@ public void TheRuntimeTypeOfTheViewModelIsUsedToResolveTheView() /// Tests that the view model to view naming convention can be customized. /// [Fact] - [SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "Not in all platforms.")] public void ViewModelToViewNamingConventionCanBeCustomized() { var resolver = new ModernDependencyResolver(); @@ -382,7 +381,6 @@ public void CanResolveViewFromViewModelWithIRoutableViewModelType() /// Tests that make sure this instance [can override name resolution function]. /// [Fact] - [SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "Not in all frameworks.")] public void CanOverrideNameResolutionFunc() { var resolver = new ModernDependencyResolver(); diff --git a/src/ReactiveUI.Tests/Locator/Mocks/IBarViewModel.cs b/src/ReactiveUI.Tests/Locator/Mocks/IBarViewModel.cs index 7e24dd6b88..2496255819 100644 --- a/src/ReactiveUI.Tests/Locator/Mocks/IBarViewModel.cs +++ b/src/ReactiveUI.Tests/Locator/Mocks/IBarViewModel.cs @@ -10,7 +10,6 @@ namespace ReactiveUI.Tests /// /// A mock interface view model. /// - [SuppressMessage("Design", "CA1040: Avoid empty interfaces", Justification = "Deliberate empty interface.")] public interface IBarViewModel { } diff --git a/src/ReactiveUI.Tests/Locator/Mocks/IFooViewModel.cs b/src/ReactiveUI.Tests/Locator/Mocks/IFooViewModel.cs index 5372a0d00d..b248f76104 100644 --- a/src/ReactiveUI.Tests/Locator/Mocks/IFooViewModel.cs +++ b/src/ReactiveUI.Tests/Locator/Mocks/IFooViewModel.cs @@ -10,7 +10,6 @@ namespace ReactiveUI.Tests /// /// A interface view model. /// - [SuppressMessage("Design", "CA1040: Avoid empty interfaces", Justification = "Deliberate empty interface.")] public interface IFooViewModel { } diff --git a/src/ReactiveUI.Tests/Locator/Mocks/IStrangeInterfaceNotFollowingConvention.cs b/src/ReactiveUI.Tests/Locator/Mocks/IStrangeInterfaceNotFollowingConvention.cs index 9d4403273d..5a88cb0034 100644 --- a/src/ReactiveUI.Tests/Locator/Mocks/IStrangeInterfaceNotFollowingConvention.cs +++ b/src/ReactiveUI.Tests/Locator/Mocks/IStrangeInterfaceNotFollowingConvention.cs @@ -10,7 +10,6 @@ namespace ReactiveUI.Tests /// /// A strange interface. /// - [SuppressMessage("Design", "CA1040: Avoid empty interfaces", Justification = "Deliberate empty interface.")] public interface IStrangeInterfaceNotFollowingConvention { } diff --git a/src/ReactiveUI.Tests/ObservableAsPropertyHelper/Mocks/OAPHIndexerTestFixture.cs b/src/ReactiveUI.Tests/ObservableAsPropertyHelper/Mocks/OAPHIndexerTestFixture.cs index ce07f0e73d..7b4b126b74 100644 --- a/src/ReactiveUI.Tests/ObservableAsPropertyHelper/Mocks/OAPHIndexerTestFixture.cs +++ b/src/ReactiveUI.Tests/ObservableAsPropertyHelper/Mocks/OAPHIndexerTestFixture.cs @@ -4,6 +4,7 @@ // See the LICENSE file in the project root for full license information. using System.Diagnostics.CodeAnalysis; +using System.Reactive.Linq; namespace ReactiveUI.Tests { @@ -17,11 +18,28 @@ internal class OAPHIndexerTestFixture : ReactiveObject /// /// Initializes a new instance of the class. /// - public OAPHIndexerTestFixture() + public OAPHIndexerTestFixture(int test) { - var temp = this.WhenAnyValue(f => f.Text) - .ToProperty(this, f => f["Whatever"]) - .Value; + switch (test) + { + case 0: + var temp = this.WhenAnyValue(f => f.Text) + .ToProperty(this, f => f["Whatever"]) + .Value; + break; + + case 1: + var temp1 = this.WhenAnyValue(f => f.Text) + .ToProperty(new ReactiveObject(), f => f.ToString()) + .Value; + break; + + case 2: + var temp2 = Observable.Return("happy") + .ToProperty(this, string.Empty) + .Value; + break; + } } /// @@ -38,7 +56,8 @@ public string? Text /// /// Name of the property. /// The string. - [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Used by test.")] +#pragma warning disable RCS1163 // Unused parameter. public string? this[string propertyName] => string.Empty; +#pragma warning restore RCS1163 // Unused parameter. } } diff --git a/src/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs b/src/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs index b000fbbc15..784988f9bf 100644 --- a/src/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs +++ b/src/ReactiveUI.Tests/ObservableAsPropertyHelper/ObservableAsPropertyHelperTest.cs @@ -489,7 +489,7 @@ public void ToProperty_NameOf_ValidValuesProduced(string[] testWords, string[] f public void ToProperty_GivenIndexer_NotifiesOnExpectedPropertyName() => new TestScheduler().With(scheduler => { - var fixture = new OAPHIndexerTestFixture(); + var fixture = new OAPHIndexerTestFixture(0); var propertiesChanged = new List(); fixture.PropertyChanged += (_, args) => @@ -504,5 +504,42 @@ public void ToProperty_GivenIndexer_NotifiesOnExpectedPropertyName() => Assert.Equal(new[] { "Text", "Item[]" }, propertiesChanged); }); + + /// + /// Tests to make sure that the ToProperty with a indexer notifies the expected property name. + /// + [Fact] + public void ToProperty_GivenIndexer_NotifiesOnExpectedPropertyName1() => + new TestScheduler().With(scheduler => + { + Assert.Throws(() => + { + var fixture = new OAPHIndexerTestFixture(1); + var propertiesChanged = new List(); + + fixture.PropertyChanged += (_, args) => + { + if (args.PropertyName != null) + { + propertiesChanged.Add(args.PropertyName); + } + }; + + fixture.Text = "awesome"; + }); + }); + + /// + /// Tests to make sure that the ToProperty with a indexer notifies the expected property name. + /// + [Fact] + public void ToProperty_GivenIndexer_NotifiesOnExpectedPropertyName2() => + new TestScheduler().With(scheduler => + { + Assert.Throws(() => + { + var fixture = new OAPHIndexerTestFixture(2); + }); + }); } } diff --git a/src/ReactiveUI.Tests/Platforms/windows-xaml/Utilities/DispatcherUtilities.cs b/src/ReactiveUI.Tests/Platforms/windows-xaml/Utilities/DispatcherUtilities.cs index b24f439dab..2d42d0011e 100644 --- a/src/ReactiveUI.Tests/Platforms/windows-xaml/Utilities/DispatcherUtilities.cs +++ b/src/ReactiveUI.Tests/Platforms/windows-xaml/Utilities/DispatcherUtilities.cs @@ -38,7 +38,6 @@ public static void DoEvents() /// /// Unused frame object.. /// Unused return value. - [SuppressMessage("Design", "CA1801: Parameter never used", Justification = "Used on some platforms.")] public static object? ExitFrame(object f) { #if !NETFX_CORE diff --git a/src/ReactiveUI.Tests/Platforms/winforms/DefaultPropertyBindingTests.cs b/src/ReactiveUI.Tests/Platforms/winforms/DefaultPropertyBindingTests.cs index b84c4a3339..8aed8d1095 100644 --- a/src/ReactiveUI.Tests/Platforms/winforms/DefaultPropertyBindingTests.cs +++ b/src/ReactiveUI.Tests/Platforms/winforms/DefaultPropertyBindingTests.cs @@ -13,8 +13,6 @@ using ReactiveUI.Winforms; using Xunit; -#pragma warning disable SA1403 // FileMayOnlyContainASingleNamespace - namespace ReactiveUI.Tests.Winforms { /// diff --git a/src/ReactiveUI.Tests/Platforms/wpf/WpfCommandBindingImplementationTests.cs b/src/ReactiveUI.Tests/Platforms/wpf/WpfCommandBindingImplementationTests.cs index 62afd381b3..a97c654730 100644 --- a/src/ReactiveUI.Tests/Platforms/wpf/WpfCommandBindingImplementationTests.cs +++ b/src/ReactiveUI.Tests/Platforms/wpf/WpfCommandBindingImplementationTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved. +// 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. @@ -189,77 +189,69 @@ public void CommandBindViewModelToViewWithObservable() var view = new CommandBindingView { ViewModel = vm }; // Create a paramenter feed - var invokeCount = 0; - vm.Command2.Subscribe(_ => invokeCount++); + vm.Command2.Subscribe(_ => vm.Value++); view.BindCommand(vm, x => x.Command2, x => x.Command2, "MouseUp"); // Bind the command and the IObservable parameter. - var fixture = new CommandBinderImplementation().BindCommand(vm, view, vm => vm.Command1, v => v.Command3, vm.Command2.Select(_ => invokeCount), "MouseUp"); - Assert.Equal(0, invokeCount); + var fixture = new CommandBinderImplementation().BindCommand(vm, view, vm => vm.Command1, v => v.Command3, vm.WhenAnyValue(vm => vm.Value), "MouseUp"); + Assert.Equal(0, vm.Value); // Confirm that the values update as expected. var parameter = 0; vm.Command1.Subscribe(i => parameter = i); view.Command2.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) { RoutedEvent = UIElement.MouseUpEvent }); - Assert.Equal(1, invokeCount); + Assert.Equal(1, vm.Value); Assert.Equal(0, parameter); view.Command3.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) { RoutedEvent = UIElement.MouseUpEvent }); Assert.Equal(1, parameter); view.Command2.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) { RoutedEvent = UIElement.MouseUpEvent }); - Assert.Equal(2, invokeCount); + Assert.Equal(2, vm.Value); Assert.Equal(1, parameter); view.Command3.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) { RoutedEvent = UIElement.MouseUpEvent }); Assert.Equal(2, parameter); - Assert.Equal(2, invokeCount); + Assert.Equal(2, vm.Value); } /// /// Commands the bind view model to view with function. /// - [Fact(Skip = "Need to resolve Func to IObservable bug exists if using this overload. Implimentation uses a ReactiveCommand for Parameter but does not reflect the required types")] + [Fact] public void CommandBindViewModelToViewWithFunc() { - //////////////////////////////////////////// - //// TEST FAILING NEED TO FIND RESOLVE ///// - //////////////////////////////////////////// - var vm = new CommandBindingViewModel(); var view = new CommandBindingView { ViewModel = vm }; // Create a paramenter feed - var invokeCount = 0; - var func = new Func(() => invokeCount); vm.Command2.Subscribe(_ => { - invokeCount++; - func(); + vm.Value++; }); view.BindCommand(vm, x => x.Command2, x => x.Command2, "MouseUp"); // Bind the command and the Func parameter. - var fixture = new CommandBinderImplementation().BindCommand(vm, view, vm => vm.Command1, v => v.Command3, func, "MouseUp"); - Assert.Equal(0, invokeCount); + var fixture = new CommandBinderImplementation().BindCommand(vm, view, vm => vm.Command1, v => v.Command3, vm => vm.Value, "MouseUp"); + Assert.Equal(0, vm.Value); // Confirm that the values update as expected. var parameter = 0; vm.Command1.Subscribe(i => parameter = i); view.Command2.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) { RoutedEvent = UIElement.MouseUpEvent }); - Assert.Equal(1, invokeCount); + Assert.Equal(1, vm.Value); Assert.Equal(0, parameter); view.Command3.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) { RoutedEvent = UIElement.MouseUpEvent }); Assert.Equal(1, parameter); view.Command2.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) { RoutedEvent = UIElement.MouseUpEvent }); - Assert.Equal(2, invokeCount); + Assert.Equal(2, vm.Value); Assert.Equal(1, parameter); view.Command3.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) { RoutedEvent = UIElement.MouseUpEvent }); Assert.Equal(2, parameter); - Assert.Equal(2, invokeCount); + Assert.Equal(2, vm.Value); } [Fact] diff --git a/src/ReactiveUI.Tests/Resolvers/DependencyResolverTests.cs b/src/ReactiveUI.Tests/Resolvers/DependencyResolverTests.cs index f73771caa1..3c5bae8953 100644 --- a/src/ReactiveUI.Tests/Resolvers/DependencyResolverTests.cs +++ b/src/ReactiveUI.Tests/Resolvers/DependencyResolverTests.cs @@ -97,7 +97,6 @@ public void AllDefaultServicesShouldBeRegisteredPerRegistrationNamespace(IEnumer [Theory] [MemberData(nameof(NamespacesToRegister))] - [SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "Not in NET472")] public void RegisteredNamespacesShouldBeRegistered(IEnumerable namespacesToRegister) { var resolver = GenerateResolver(); diff --git a/src/ReactiveUI.Tests/Resolvers/PocoObservableForPropertyTests.cs b/src/ReactiveUI.Tests/Resolvers/PocoObservableForPropertyTests.cs index 312f6cc095..50bde9adfb 100644 --- a/src/ReactiveUI.Tests/Resolvers/PocoObservableForPropertyTests.cs +++ b/src/ReactiveUI.Tests/Resolvers/PocoObservableForPropertyTests.cs @@ -78,7 +78,6 @@ public void NotificationPocoErrorOnBind() } [Fact] - [SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "Not in NET472")] public void NotificationPocoSuppressErrorOnBind() { using (var testLoggerRegistration = new TestLoggerRegistration()) diff --git a/src/ReactiveUI.Tests/Routing/Mocks/TestScreen.cs b/src/ReactiveUI.Tests/Routing/Mocks/TestScreen.cs index 197fbb7755..8038ab5cbb 100644 --- a/src/ReactiveUI.Tests/Routing/Mocks/TestScreen.cs +++ b/src/ReactiveUI.Tests/Routing/Mocks/TestScreen.cs @@ -19,9 +19,7 @@ public class TestScreen : ReactiveObject, IScreen public RoutingState? Router { #pragma warning disable CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). -#pragma warning disable CS8613 // Nullability of reference types in return type doesn't match implicitly implemented member. get => _router; -#pragma warning restore CS8613 // Nullability of reference types in return type doesn't match implicitly implemented member. #pragma warning restore CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). set => this.RaiseAndSetIfChanged(ref _router, value); } diff --git a/src/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs b/src/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs index 05940e9109..56eefe9a39 100644 --- a/src/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs +++ b/src/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs @@ -42,10 +42,7 @@ public void CallSchedulerFactoryOnCreation() public void FactoryThrowsArgumentNullException_FallsBackToCurrentThread() { IScheduler? schedulerExecutedOn = null; -#pragma warning disable CA2208 // Instantiate argument exceptions correctly var schedulerFactory = new Func(() => throw new ArgumentNullException()); -#pragma warning restore CA2208 // Instantiate argument exceptions correctly - var sut = new WaitForDispatcherScheduler(schedulerFactory); sut.Schedule( null!, diff --git a/src/ReactiveUI.Tests/WhenAny/TestWhenAnyObsViewModel.cs b/src/ReactiveUI.Tests/WhenAny/TestWhenAnyObsViewModel.cs index 2e98760471..ad94c360c0 100644 --- a/src/ReactiveUI.Tests/WhenAny/TestWhenAnyObsViewModel.cs +++ b/src/ReactiveUI.Tests/WhenAny/TestWhenAnyObsViewModel.cs @@ -36,7 +36,6 @@ public IObservable>? Changes public ReactiveCommand Command3 { get; set; } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Test usage")] public ObservableCollectionExtended? MyListOfInts { get => _myListOfInts; diff --git a/src/ReactiveUI/Bindings/Command/CommandBinder.cs b/src/ReactiveUI/Bindings/Command/CommandBinder.cs index e7e3e9420b..cc8a00cce9 100644 --- a/src/ReactiveUI/Bindings/Command/CommandBinder.cs +++ b/src/ReactiveUI/Bindings/Command/CommandBinder.cs @@ -51,7 +51,7 @@ public static IReactiveBinding BindCommand> propertyName, Expression> controlName, - IObservable withParameter, + IObservable withParameter, string? toEvent = null) where TView : class, IViewFor where TViewModel : class @@ -113,7 +113,7 @@ public static IReactiveBinding BindCommand> propertyName, Expression> controlName, - Expression> withParameter, + Expression> withParameter, string? toEvent = null) where TView : class, IViewFor where TViewModel : class diff --git a/src/ReactiveUI/Bindings/Command/CommandBinderImplementation.cs b/src/ReactiveUI/Bindings/Command/CommandBinderImplementation.cs index 1f77a0455f..e1fa62a2bd 100644 --- a/src/ReactiveUI/Bindings/Command/CommandBinderImplementation.cs +++ b/src/ReactiveUI/Bindings/Command/CommandBinderImplementation.cs @@ -38,14 +38,19 @@ public class CommandBinderImplementation : ICommandBinderImplementation /// instead of the default. /// NOTE: If this parameter is used inside WhenActivated, it's /// important to dispose the binding when the view is deactivated. - /// A class representing the binding. Dispose it to disconnect - /// the binding. + /// + /// A class representing the binding. Dispose it to disconnect + /// the binding. + /// + /// nameof(vmProperty) + /// or + /// nameof(vmProperty). public IReactiveBinding BindCommand( TViewModel? viewModel, TView view, Expression> vmProperty, Expression> controlProperty, - Func withParameter, + Expression> withParameter, string? toEvent = null) where TView : class, IViewFor where TViewModel : class @@ -65,10 +70,7 @@ public IReactiveBinding BindCommand(); - var bindingDisposable = BindCommandInternal(source, view, controlExpression, Observable.Defer(() => Observable.Return(withParameter())), toEvent, cmd => - cmd is IReactiveCommand rc - ? ReactiveCommand.Create(() => ((ICommand)rc).Execute(null), rc.CanExecute) - : new RelayCommand(cmd.CanExecute, _ => cmd.Execute(withParameter()))); + var bindingDisposable = BindCommandInternal(source, view, controlExpression, viewModel.ToObservable(withParameter), toEvent); return new ReactiveBinding( view, @@ -105,7 +107,7 @@ public IReactiveBinding BindCommand> vmProperty, Expression> controlProperty, - IObservable withParameter, + IObservable withParameter, string? toEvent = null) where TView : class, IViewFor where TViewModel : class @@ -141,8 +143,7 @@ private static IDisposable BindCommandInternal( TView view, Expression controlExpression, IObservable withParameter, - string? toEvent, - Func? commandFixuper = null) + string? toEvent) where TView : class, IViewFor where TProp : ICommand { @@ -167,11 +168,9 @@ private static IDisposable BindCommandInternal( return; } - var cmd = commandFixuper is not null ? commandFixuper(x.val) : x.val; - disposable = !string.IsNullOrEmpty(toEvent) ? - CreatesCommandBinding.BindCommandToObject(cmd, x.host, withParameter.Select(y => (object)y!), toEvent) : - CreatesCommandBinding.BindCommandToObject(cmd, x.host, withParameter.Select(y => (object)y!)); + CreatesCommandBinding.BindCommandToObject(x.val, x.host, withParameter.Select(y => (object)y!), toEvent) : + CreatesCommandBinding.BindCommandToObject(x.val, x.host, withParameter.Select(y => (object)y!)); }); return Disposable.Create(() => diff --git a/src/ReactiveUI/Bindings/Command/ICommandBinderImplementation.cs b/src/ReactiveUI/Bindings/Command/ICommandBinderImplementation.cs index e7f4e8e5be..dcad2f84a7 100644 --- a/src/ReactiveUI/Bindings/Command/ICommandBinderImplementation.cs +++ b/src/ReactiveUI/Bindings/Command/ICommandBinderImplementation.cs @@ -35,7 +35,7 @@ IReactiveBinding BindCommand> vmProperty, Expression> controlProperty, - Func withParameter, + Expression> withParameter, string? toEvent = null) where TView : class, IViewFor where TViewModel : class @@ -61,7 +61,7 @@ IReactiveBinding BindCommand> vmProperty, Expression> controlProperty, - IObservable withParameter, + IObservable withParameter, string? toEvent = null) where TView : class, IViewFor where TViewModel : class diff --git a/src/ReactiveUI/ObservableFuncMixins.cs b/src/ReactiveUI/ObservableFuncMixins.cs new file mode 100644 index 0000000000..c8420bb9d7 --- /dev/null +++ b/src/ReactiveUI/ObservableFuncMixins.cs @@ -0,0 +1,41 @@ +// 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.Linq.Expressions; +using System.Reactive.Linq; + +namespace ReactiveUI +{ + /// + /// Observable Func Mixins. + /// + public static class ObservableFuncMixins + { + /// + /// Converts to observable. + /// + /// The type of the view model. + /// The type of the result. + /// The view model. + /// The expression. + /// if set to true [before change]. + /// if set to true [skip initial]. + /// + /// An observable Result. + /// + public static IObservable ToObservable( + this TSource? source, + Expression> expression, + bool beforeChange = false, + bool skipInitial = false) + { + var sParam = Reflection.Rewrite(expression.Body); + return source.SubscribeToExpressionChain(sParam, beforeChange, skipInitial, RxApp.SuppressViewCommandBindingMessage) + .Select(x => x.GetValue()) + .Retry(); + } + } +}