AsyncCommand (#279)
* WeakEventManager added * AsyncCommand added * cosmetics * Update XamarinCommunityToolkit/ObjectModel/IAsyncCommand.shared.cs * Update XamarinCommunityToolkit/ObjectModel/AsyncCommand.shared.cs * Update XamarinCommunityToolkit/Helpers/WeakEventManager.shared.cs * cosmetics * ArgumentNullException constructor test added * Lazy isNullableParameterType added * cosmetics * Update XamarinCommunityToolkit/ObjectModel/AsyncCommand.shared.cs * Update XamarinCommunityToolkit/ObjectModel/AsyncCommand.shared.cs * Update XamarinCommunityToolkit/ObjectModel/AsyncCommand.shared.cs * CanExecute tests converted to Theory * build fix * unit test fix * async void test fixed with semaphore * Add AsyncValueCommand & UnitTests * Update Unit Tests for xUnit * Delete AsyncCommand_Tests.cs * Added IAsyncCommand.IsExecuting, IAsyncCommand.AllowsMultipleExecutions * Add missing NuGet Packages * Added NuGet Packages * Fixed Failing Unit Tests Defer `async void`. This ensures InvalidCommandParameterException is thrown on the calling thread/context before reaching an async void method * Add BaseCommand, Update Unit Tests * Finish Unit Tests * Implement AsyncCommand * Implement WeakEventManager * Updated XML Documentation * Update XML Documentation * Add & Implement IAsyncCommand<TExecute, TCanExecute> + IAsyncValueCommand<TExecute, TCanExecute> * Add IAsyncCommand<TExecute, TCanExecute> Tests, Add IAsyncValueCommand<TExecute, TCanExecute> Tests * Fix Failing Tests * Removed Default Parameters * Update XamarinCommunityToolkit/Helpers/WeakEventManagerService.shared.cs Co-authored-by: Pedro Jesus <pedrojesus.cefet@gmail.com> * Update XamarinCommunityToolkit/ObjectModel/AsyncCommand.shared.cs Co-authored-by: Pedro Jesus <pedrojesus.cefet@gmail.com> * Update XamarinCommunityToolkit/ObjectModel/AsyncValueCommand.shared.cs Co-authored-by: Pedro Jesus <pedrojesus.cefet@gmail.com> * Update XamarinCommunityToolkit/Helpers/WeakEventManager.shared.cs Co-authored-by: Pedro Jesus <pedrojesus.cefet@gmail.com> * Update XamarinCommunityToolkit/Helpers/WeakEventManager.shared.cs Co-authored-by: Pedro Jesus <pedrojesus.cefet@gmail.com> * Update XamarinCommunityToolkit/Helpers/WeakEventManagerService.shared.cs Co-authored-by: Pedro Jesus <pedrojesus.cefet@gmail.com> * Update XamarinCommunityToolkit/Helpers/WeakEventManagerService.shared.cs Co-authored-by: Pedro Jesus <pedrojesus.cefet@gmail.com> * Update XamarinCommunityToolkit/Helpers/WeakEventManager.shared.cs Co-authored-by: Pedro Jesus <pedrojesus.cefet@gmail.com> * Implement ValueTuple for EventManagerService, Remove WeakEventManager from CameraView * merge build fix * Re-add .NET Standard 2.1 Support * null check cleanup * Increase NETCORE_TEST_VERSION to 3.1.x * Add null check Co-authored-by: Andrei <andrei.misiukevich@gmail.com> Co-authored-by: Brandon Minnick <13558917+brminnick@users.noreply.github.com> Co-authored-by: Pedro Jesus <pedrojesus.cefet@gmail.com>
This commit is contained in:
Родитель
0e93d82cc8
Коммит
23baecafbd
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using Xamarin.CommunityToolkit.Helpers;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.UnitTests.Helpers.WeakEventManagerTests
|
||||
{
|
||||
public class BaseWeakEventManagerTests
|
||||
{
|
||||
protected event EventHandler TestEvent
|
||||
{
|
||||
add => TestWeakEventManager.AddEventHandler(value);
|
||||
remove => TestWeakEventManager.RemoveEventHandler(value);
|
||||
}
|
||||
|
||||
protected event EventHandler<string> TestStringEvent
|
||||
{
|
||||
add => TestStringWeakEventManager.AddEventHandler(value);
|
||||
remove => TestStringWeakEventManager.RemoveEventHandler(value);
|
||||
}
|
||||
|
||||
protected WeakEventManager TestWeakEventManager { get; } = new WeakEventManager();
|
||||
|
||||
protected WeakEventManager<string> TestStringWeakEventManager { get; } = new WeakEventManager<string>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
using System;
|
||||
using Xamarin.CommunityToolkit.Exceptions;
|
||||
using Xamarin.CommunityToolkit.Helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.UnitTests.Helpers.WeakEventManagerTests
|
||||
{
|
||||
public class WeakEventManager_ActionT_Tests : BaseWeakEventManagerTests
|
||||
{
|
||||
readonly WeakEventManager<string> actionEventManager = new WeakEventManager<string>();
|
||||
|
||||
public event Action<string> ActionEvent
|
||||
{
|
||||
add => actionEventManager.AddEventHandler(value);
|
||||
remove => actionEventManager.RemoveEventHandler(value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerActionT_HandleEvent_ValidImplementation()
|
||||
{
|
||||
// Arrange
|
||||
ActionEvent += HandleDelegateTest;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleDelegateTest(string message)
|
||||
{
|
||||
Assert.NotNull(message);
|
||||
Assert.NotEmpty(message);
|
||||
|
||||
didEventFire = true;
|
||||
ActionEvent -= HandleDelegateTest;
|
||||
}
|
||||
|
||||
// Act
|
||||
actionEventManager.RaiseEvent("Test", nameof(ActionEvent));
|
||||
|
||||
// Assert
|
||||
Assert.True(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerActionT_HandleEvent_InvalidHandleEventEventName()
|
||||
{
|
||||
// Arrange
|
||||
ActionEvent += HandleDelegateTest;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleDelegateTest(string message)
|
||||
{
|
||||
Assert.NotNull(message);
|
||||
Assert.NotEmpty(message);
|
||||
|
||||
didEventFire = true;
|
||||
}
|
||||
|
||||
// Act
|
||||
actionEventManager.RaiseEvent("Test", nameof(TestEvent));
|
||||
|
||||
// Assert
|
||||
Assert.False(didEventFire);
|
||||
ActionEvent -= HandleDelegateTest;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerActionT_UnassignedEvent()
|
||||
{
|
||||
// Arrange
|
||||
var didEventFire = false;
|
||||
|
||||
ActionEvent += HandleDelegateTest;
|
||||
ActionEvent -= HandleDelegateTest;
|
||||
void HandleDelegateTest(string message)
|
||||
{
|
||||
Assert.NotNull(message);
|
||||
Assert.NotEmpty(message);
|
||||
|
||||
didEventFire = true;
|
||||
}
|
||||
|
||||
// Act
|
||||
actionEventManager.RaiseEvent("Test", nameof(ActionEvent));
|
||||
|
||||
// Assert
|
||||
Assert.False(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerActionT_UnassignedEventManager()
|
||||
{
|
||||
// Arrange
|
||||
var unassignedEventManager = new WeakEventManager();
|
||||
var didEventFire = false;
|
||||
|
||||
ActionEvent += HandleDelegateTest;
|
||||
void HandleDelegateTest(string message)
|
||||
{
|
||||
Assert.NotNull(message);
|
||||
Assert.NotEmpty(message);
|
||||
|
||||
didEventFire = true;
|
||||
}
|
||||
|
||||
// Act
|
||||
unassignedEventManager.RaiseEvent(nameof(ActionEvent));
|
||||
|
||||
// Assert
|
||||
Assert.False(didEventFire);
|
||||
ActionEvent -= HandleDelegateTest;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerActionT_HandleEvent_InvalidHandleEvent()
|
||||
{
|
||||
// Arrange
|
||||
ActionEvent += HandleDelegateTest;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleDelegateTest(string message)
|
||||
{
|
||||
Assert.NotNull(message);
|
||||
Assert.NotEmpty(message);
|
||||
|
||||
didEventFire = true;
|
||||
}
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<InvalidHandleEventException>(() => actionEventManager.RaiseEvent(this, "Test", nameof(ActionEvent)));
|
||||
Assert.False(didEventFire);
|
||||
ActionEvent -= HandleDelegateTest;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerActionT_AddEventHandler_NullHandler()
|
||||
{
|
||||
// Arrange
|
||||
Action<string> nullAction = null;
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8604 //Possible null reference argument for parameter
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.AddEventHandler(nullAction, nameof(ActionEvent)));
|
||||
#pragma warning restore CS8604 //Possible null reference argument for parameter
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerActionT_AddEventHandler_NullEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.AddEventHandler(s => { var temp = s; }, null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerActionT_AddEventHandler_EmptyEventName()
|
||||
{
|
||||
// Arrange
|
||||
Action<string> nullAction = null;
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8604 //Possible null reference argument for parameter
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.AddEventHandler(nullAction, string.Empty));
|
||||
#pragma warning restore CS8604 //Possible null reference argument for parameter
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerActionT_AddEventHandler_WhitespaceEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.AddEventHandler(s => { var temp = s; }, " "));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerActionT_RemoveEventHandler_NullHandler()
|
||||
{
|
||||
// Arrange
|
||||
Action<string> nullAction = null;
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8604 //Possible null reference argument for parameter
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.RemoveEventHandler(nullAction));
|
||||
#pragma warning restore CS8604
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerActionT_RemoveEventHandler_NullEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.RemoveEventHandler(s => { var temp = s; }, null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerActionT_RemoveEventHandler_EmptyEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.RemoveEventHandler(s => { var temp = s; }, string.Empty));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerActionT_RemoveEventHandler_WhiteSpaceEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.RemoveEventHandler(s => { var temp = s; }, " "));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
using System;
|
||||
using Xamarin.CommunityToolkit.Exceptions;
|
||||
using Xamarin.CommunityToolkit.Helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.UnitTests.Helpers.WeakEventManagerTests
|
||||
{
|
||||
public class WeakEventManager_Action_Tests : BaseWeakEventManagerTests
|
||||
{
|
||||
readonly WeakEventManager actionEventManager = new WeakEventManager();
|
||||
|
||||
public event Action ActionEvent
|
||||
{
|
||||
add => actionEventManager.AddEventHandler(value);
|
||||
remove => actionEventManager.RemoveEventHandler(value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerAction_HandleEvent_ValidImplementation()
|
||||
{
|
||||
// Arrange
|
||||
ActionEvent += HandleDelegateTest;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleDelegateTest()
|
||||
{
|
||||
didEventFire = true;
|
||||
ActionEvent -= HandleDelegateTest;
|
||||
}
|
||||
|
||||
// Act
|
||||
actionEventManager.RaiseEvent(nameof(ActionEvent));
|
||||
|
||||
// Assert
|
||||
Assert.True(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerAction_HandleEvent_InvalidHandleEventEventName()
|
||||
{
|
||||
// Arrange
|
||||
ActionEvent += HandleDelegateTest;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleDelegateTest() => didEventFire = true;
|
||||
|
||||
// Act
|
||||
actionEventManager.RaiseEvent(nameof(TestStringEvent));
|
||||
|
||||
// Assert
|
||||
Assert.False(didEventFire);
|
||||
ActionEvent -= HandleDelegateTest;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerAction_UnassignedEvent()
|
||||
{
|
||||
// Arrange
|
||||
var didEventFire = false;
|
||||
|
||||
ActionEvent += HandleDelegateTest;
|
||||
ActionEvent -= HandleDelegateTest;
|
||||
void HandleDelegateTest() => didEventFire = true;
|
||||
|
||||
// Act
|
||||
actionEventManager.RaiseEvent(nameof(ActionEvent));
|
||||
|
||||
// Assert
|
||||
Assert.False(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerAction_UnassignedEventManager()
|
||||
{
|
||||
// Arrange
|
||||
var unassignedEventManager = new WeakEventManager();
|
||||
var didEventFire = false;
|
||||
|
||||
ActionEvent += HandleDelegateTest;
|
||||
void HandleDelegateTest() => didEventFire = true;
|
||||
|
||||
// Act
|
||||
unassignedEventManager.RaiseEvent(nameof(ActionEvent));
|
||||
|
||||
// Assert
|
||||
Assert.False(didEventFire);
|
||||
ActionEvent -= HandleDelegateTest;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerAction_HandleEvent_InvalidHandleEvent()
|
||||
{
|
||||
// Arrange
|
||||
ActionEvent += HandleDelegateTest;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleDelegateTest() => didEventFire = true;
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<InvalidHandleEventException>(() => actionEventManager.RaiseEvent(this, EventArgs.Empty, nameof(ActionEvent)));
|
||||
Assert.False(didEventFire);
|
||||
ActionEvent -= HandleDelegateTest;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerAction_AddEventHandler_NullHandler()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.AddEventHandler(null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerAction_AddEventHandler_NullEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.AddEventHandler(null, null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerAction_AddEventHandler_EmptyEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.AddEventHandler(null, string.Empty));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerAction_AddEventHandler_WhitespaceEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.AddEventHandler(null, " "));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerAction_RemoveEventHandler_NullHandler()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.RemoveEventHandler(null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerAction_RemoveEventHandler_NullEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.RemoveEventHandler(null, null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerAction_RemoveEventHandler_EmptyEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.RemoveEventHandler(null, string.Empty));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerAction_RemoveEventHandler_WhiteSpaceEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => actionEventManager.RemoveEventHandler(null, " "));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using Xamarin.CommunityToolkit.Exceptions;
|
||||
using Xamarin.CommunityToolkit.Helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.UnitTests.Helpers.WeakEventManagerTests
|
||||
{
|
||||
public class WeakEventManager_Delegate_Tests : BaseWeakEventManagerTests, INotifyPropertyChanged
|
||||
{
|
||||
readonly WeakEventManager propertyChangedWeakEventManager = new WeakEventManager();
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged
|
||||
{
|
||||
add => propertyChangedWeakEventManager.AddEventHandler(value);
|
||||
remove => propertyChangedWeakEventManager.RemoveEventHandler(value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_HandleEvent_ValidImplementation()
|
||||
{
|
||||
// Arrange
|
||||
PropertyChanged += HandleDelegateTest;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleDelegateTest(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
Assert.NotNull(sender);
|
||||
Assert.Equal(this.GetType(), sender.GetType());
|
||||
|
||||
Assert.NotNull(e);
|
||||
|
||||
didEventFire = true;
|
||||
PropertyChanged -= HandleDelegateTest;
|
||||
}
|
||||
|
||||
// Act
|
||||
propertyChangedWeakEventManager.RaiseEvent(this, new PropertyChangedEventArgs("Test"), nameof(PropertyChanged));
|
||||
|
||||
// Assert
|
||||
Assert.True(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_HandleEvent_NullSender()
|
||||
{
|
||||
// Arrange
|
||||
PropertyChanged += HandleDelegateTest;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleDelegateTest(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
Assert.Null(sender);
|
||||
Assert.NotNull(e);
|
||||
|
||||
didEventFire = true;
|
||||
PropertyChanged -= HandleDelegateTest;
|
||||
}
|
||||
|
||||
// Act
|
||||
propertyChangedWeakEventManager.RaiseEvent(null, new PropertyChangedEventArgs("Test"), nameof(PropertyChanged));
|
||||
|
||||
// Assert
|
||||
Assert.True(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_HandleEvent_InvalidEventArgs()
|
||||
{
|
||||
// Arrange
|
||||
PropertyChanged += HandleDelegateTest;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleDelegateTest(object sender, PropertyChangedEventArgs e) => didEventFire = true;
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentException>(() => propertyChangedWeakEventManager.RaiseEvent(this, EventArgs.Empty, nameof(PropertyChanged)));
|
||||
Assert.False(didEventFire);
|
||||
PropertyChanged -= HandleDelegateTest;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_HandleEvent_NullEventArgs()
|
||||
{
|
||||
// Arrange
|
||||
PropertyChanged += HandleDelegateTest;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleDelegateTest(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
Assert.NotNull(sender);
|
||||
Assert.Equal(this.GetType(), sender.GetType());
|
||||
|
||||
Assert.Null(e);
|
||||
|
||||
didEventFire = true;
|
||||
PropertyChanged -= HandleDelegateTest;
|
||||
}
|
||||
|
||||
// Act
|
||||
#pragma warning disable CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
propertyChangedWeakEventManager.RaiseEvent(this, null, nameof(PropertyChanged));
|
||||
#pragma warning restore CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
|
||||
// Assert
|
||||
Assert.True(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_HandleEvent_InvalidHandleEventEventName()
|
||||
{
|
||||
// Arrange
|
||||
PropertyChanged += HandleDelegateTest;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleDelegateTest(object sender, PropertyChangedEventArgs e) => didEventFire = true;
|
||||
|
||||
// Act
|
||||
propertyChangedWeakEventManager.RaiseEvent(this, new PropertyChangedEventArgs("Test"), nameof(TestStringEvent));
|
||||
|
||||
// Assert
|
||||
Assert.False(didEventFire);
|
||||
PropertyChanged -= HandleDelegateTest;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_HandleEvent_DynamicMethod_ValidImplementation()
|
||||
{
|
||||
// Arrange
|
||||
var dynamicMethod = new System.Reflection.Emit.DynamicMethod(string.Empty, typeof(void), new[] { typeof(object), typeof(PropertyChangedEventArgs) });
|
||||
var ilGenerator = dynamicMethod.GetILGenerator();
|
||||
ilGenerator.Emit(System.Reflection.Emit.OpCodes.Ret);
|
||||
|
||||
var handler = (PropertyChangedEventHandler)dynamicMethod.CreateDelegate(typeof(PropertyChangedEventHandler));
|
||||
PropertyChanged += handler;
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
propertyChangedWeakEventManager.RaiseEvent(this, new PropertyChangedEventArgs("Test"), nameof(PropertyChanged));
|
||||
PropertyChanged -= handler;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_UnassignedEvent()
|
||||
{
|
||||
// Arrange
|
||||
var didEventFire = false;
|
||||
|
||||
PropertyChanged += HandleDelegateTest;
|
||||
PropertyChanged -= HandleDelegateTest;
|
||||
void HandleDelegateTest(object sender, PropertyChangedEventArgs e) => didEventFire = true;
|
||||
|
||||
// Act
|
||||
#pragma warning disable CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
propertyChangedWeakEventManager.RaiseEvent(null, null, nameof(PropertyChanged));
|
||||
#pragma warning restore CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
|
||||
// Assert
|
||||
Assert.False(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_UnassignedEventManager()
|
||||
{
|
||||
// Arrange
|
||||
var unassignedEventManager = new WeakEventManager();
|
||||
var didEventFire = false;
|
||||
|
||||
PropertyChanged += HandleDelegateTest;
|
||||
void HandleDelegateTest(object sender, PropertyChangedEventArgs e) => didEventFire = true;
|
||||
|
||||
// Act
|
||||
unassignedEventManager.RaiseEvent(null, null, nameof(PropertyChanged));
|
||||
|
||||
// Assert
|
||||
Assert.False(didEventFire);
|
||||
PropertyChanged -= HandleDelegateTest;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_HandleEvent_InvalidHandleEvent()
|
||||
{
|
||||
// Arrange
|
||||
PropertyChanged += HandleDelegateTest;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleDelegateTest(object sender, PropertyChangedEventArgs e) => didEventFire = true;
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<InvalidHandleEventException>(() => propertyChangedWeakEventManager.RaiseEvent(nameof(PropertyChanged)));
|
||||
Assert.False(didEventFire);
|
||||
PropertyChanged -= HandleDelegateTest;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_AddEventHandler_NullHandler()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => propertyChangedWeakEventManager.AddEventHandler(null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_AddEventHandler_NullEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => propertyChangedWeakEventManager.AddEventHandler(null, null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_AddEventHandler_EmptyEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => propertyChangedWeakEventManager.AddEventHandler(null, string.Empty));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_AddEventHandler_WhitespaceEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => propertyChangedWeakEventManager.AddEventHandler(null, " "));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_RemoveEventHandler_NullHandler()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => propertyChangedWeakEventManager.RemoveEventHandler(null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_RemoveEventHandler_NullEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => propertyChangedWeakEventManager.RemoveEventHandler(null, null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_RemoveEventHandler_EmptyEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => propertyChangedWeakEventManager.RemoveEventHandler(null, string.Empty));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerDelegate_RemoveEventHandler_WhiteSpaceEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => propertyChangedWeakEventManager.RemoveEventHandler(null, " "));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,281 @@
|
|||
using System;
|
||||
using Xamarin.CommunityToolkit.Exceptions;
|
||||
using Xamarin.CommunityToolkit.Helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.UnitTests.Helpers.WeakEventManagerTests
|
||||
{
|
||||
public class WeakEventManager_EventHandlerT_Tests : BaseWeakEventManagerTests
|
||||
{
|
||||
[Fact]
|
||||
public void WeakEventManagerTEventArgs_HandleEvent_ValidImplementation()
|
||||
{
|
||||
// Arrange
|
||||
TestStringEvent += HandleTestEvent;
|
||||
|
||||
const string stringEventArg = "Test";
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleTestEvent(object sender, string? e)
|
||||
{
|
||||
if (sender == null || e == null)
|
||||
throw new ArgumentNullException(nameof(sender));
|
||||
|
||||
Assert.NotNull(sender);
|
||||
Assert.Equal(GetType(), sender.GetType());
|
||||
|
||||
Assert.NotNull(e);
|
||||
Assert.Equal(stringEventArg, e);
|
||||
|
||||
didEventFire = true;
|
||||
TestStringEvent -= HandleTestEvent;
|
||||
}
|
||||
|
||||
// Act
|
||||
TestStringWeakEventManager.RaiseEvent(this, stringEventArg, nameof(TestStringEvent));
|
||||
|
||||
// Assert
|
||||
Assert.True(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManageTEventArgs_HandleEvent_NullSender()
|
||||
{
|
||||
// Arrange
|
||||
TestStringEvent += HandleTestEvent;
|
||||
|
||||
const string stringEventArg = "Test";
|
||||
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleTestEvent(object sender, string e)
|
||||
{
|
||||
Assert.Null(sender);
|
||||
|
||||
Assert.NotNull(e);
|
||||
Assert.Equal(stringEventArg, e);
|
||||
|
||||
didEventFire = true;
|
||||
TestStringEvent -= HandleTestEvent;
|
||||
}
|
||||
|
||||
// Act
|
||||
TestStringWeakEventManager.RaiseEvent(null, stringEventArg, nameof(TestStringEvent));
|
||||
|
||||
// Assert
|
||||
Assert.True(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerTEventArgs_HandleEvent_NullEventArgs()
|
||||
{
|
||||
// Arrange
|
||||
TestStringEvent += HandleTestEvent;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleTestEvent(object sender, string e)
|
||||
{
|
||||
if (sender == null)
|
||||
throw new ArgumentNullException(nameof(sender));
|
||||
|
||||
Assert.NotNull(sender);
|
||||
Assert.Equal(GetType(), sender.GetType());
|
||||
|
||||
Assert.Null(e);
|
||||
|
||||
didEventFire = true;
|
||||
TestStringEvent -= HandleTestEvent;
|
||||
}
|
||||
|
||||
// Act
|
||||
#pragma warning disable CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
TestStringWeakEventManager.RaiseEvent(this, null, nameof(TestStringEvent));
|
||||
#pragma warning restore CS8625
|
||||
|
||||
// Assert
|
||||
Assert.True(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerTEventArgs_HandleEvent_InvalidHandleEvent()
|
||||
{
|
||||
// Arrange
|
||||
TestStringEvent += HandleTestEvent;
|
||||
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleTestEvent(object sender, string e) => didEventFire = true;
|
||||
|
||||
// Act
|
||||
TestStringWeakEventManager.RaiseEvent(this, "Test", nameof(TestEvent));
|
||||
|
||||
// Assert
|
||||
Assert.False(didEventFire);
|
||||
TestStringEvent -= HandleTestEvent;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_NullEventManager()
|
||||
{
|
||||
// Arrange
|
||||
WeakEventManager unassignedEventManager = null;
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8602 //Dereference of a possible null reference
|
||||
Assert.Throws<NullReferenceException>(() => unassignedEventManager.RaiseEvent(null, null, nameof(TestEvent)));
|
||||
#pragma warning restore CS8602
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerTEventArgs_UnassignedEventManager()
|
||||
{
|
||||
// Arrange
|
||||
var unassignedEventManager = new WeakEventManager<string>();
|
||||
var didEventFire = false;
|
||||
|
||||
TestStringEvent += HandleTestEvent;
|
||||
void HandleTestEvent(object sender, string e) => didEventFire = true;
|
||||
|
||||
// Act
|
||||
#pragma warning disable CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
unassignedEventManager.RaiseEvent(null, null, nameof(TestStringEvent));
|
||||
#pragma warning restore CS8625
|
||||
|
||||
// Assert
|
||||
Assert.False(didEventFire);
|
||||
TestStringEvent -= HandleTestEvent;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerTEventArgs_UnassignedEvent()
|
||||
{
|
||||
// Arrange
|
||||
var didEventFire = false;
|
||||
|
||||
TestStringEvent += HandleTestEvent;
|
||||
TestStringEvent -= HandleTestEvent;
|
||||
void HandleTestEvent(object sender, string e) => didEventFire = true;
|
||||
|
||||
// Act
|
||||
TestStringWeakEventManager.RaiseEvent(this, "Test", nameof(TestStringEvent));
|
||||
|
||||
// Assert
|
||||
Assert.False(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerT_AddEventHandler_NullHandler()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
Assert.Throws<ArgumentNullException>(() => TestStringWeakEventManager.AddEventHandler((EventHandler<string>)null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerT_AddEventHandler_NullEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => TestStringWeakEventManager.AddEventHandler(s => { var temp = s; }, null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerT_AddEventHandler_EmptyEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentNullException>(() => TestStringWeakEventManager.AddEventHandler(s => { var temp = s; }, string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerT_AddEventHandler_WhiteSpaceEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentNullException>(() => TestStringWeakEventManager.AddEventHandler(s => { var temp = s; }, " "));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerT_RemoveEventHandler_NullHandler()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => TestStringWeakEventManager.RemoveEventHandler((EventHandler<string>)null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerT_RemoveEventHandler_NullEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => TestStringWeakEventManager.AddEventHandler(s => { var temp = s; }, null));
|
||||
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerT_RemoveEventHandler_EmptyEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentNullException>(() => TestStringWeakEventManager.AddEventHandler(s => { var temp = s; }, string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerT_RemoveEventHandler_WhiteSpaceEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<ArgumentNullException>(() => TestStringWeakEventManager.AddEventHandler(s => { var temp = s; }, string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManagerT_HandleEvent_InvalidHandleEvent()
|
||||
{
|
||||
// Arrange
|
||||
TestStringEvent += HandleTestStringEvent;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleTestStringEvent(object sender, string e) => didEventFire = true;
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<InvalidHandleEventException>(() => TestStringWeakEventManager.RaiseEvent("", nameof(TestStringEvent)));
|
||||
Assert.False(didEventFire);
|
||||
TestStringEvent -= HandleTestStringEvent;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
using System;
|
||||
using Xamarin.CommunityToolkit.Exceptions;
|
||||
using Xamarin.CommunityToolkit.Helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.UnitTests.Helpers.WeakEventManagerTests
|
||||
{
|
||||
public class WeakEventManager_EventHandler_Tests : BaseWeakEventManagerTests
|
||||
{
|
||||
[Fact]
|
||||
public void WeakEventManager_HandleEvent_ValidImplementation()
|
||||
{
|
||||
// Arrange
|
||||
TestEvent += HandleTestEvent;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleTestEvent(object? sender, EventArgs e)
|
||||
{
|
||||
if (sender == null)
|
||||
throw new ArgumentNullException(nameof(sender));
|
||||
|
||||
Assert.NotNull(sender);
|
||||
Assert.Equal(GetType(), sender.GetType());
|
||||
|
||||
Assert.NotNull(e);
|
||||
|
||||
didEventFire = true;
|
||||
TestEvent -= HandleTestEvent;
|
||||
}
|
||||
|
||||
// Act
|
||||
TestWeakEventManager.RaiseEvent(this, new EventArgs(), nameof(TestEvent));
|
||||
|
||||
// Assert
|
||||
Assert.True(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_HandleEvent_NullSender()
|
||||
{
|
||||
// Arrange
|
||||
TestEvent += HandleTestEvent;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleTestEvent(object? sender, EventArgs e)
|
||||
{
|
||||
Assert.Null(sender);
|
||||
Assert.NotNull(e);
|
||||
|
||||
didEventFire = true;
|
||||
TestEvent -= HandleTestEvent;
|
||||
}
|
||||
|
||||
// Act
|
||||
TestWeakEventManager.RaiseEvent(null, new EventArgs(), nameof(TestEvent));
|
||||
|
||||
// Assert
|
||||
Assert.True(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_HandleEvent_EmptyEventArgs()
|
||||
{
|
||||
// Arrange
|
||||
TestEvent += HandleTestEvent;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleTestEvent(object? sender, EventArgs e)
|
||||
{
|
||||
if (sender == null)
|
||||
throw new ArgumentNullException(nameof(sender));
|
||||
|
||||
Assert.NotNull(sender);
|
||||
Assert.Equal(GetType(), sender.GetType());
|
||||
|
||||
Assert.NotNull(e);
|
||||
Assert.Equal(EventArgs.Empty, e);
|
||||
|
||||
didEventFire = true;
|
||||
TestEvent -= HandleTestEvent;
|
||||
}
|
||||
|
||||
// Act
|
||||
TestWeakEventManager.RaiseEvent(this, EventArgs.Empty, nameof(TestEvent));
|
||||
|
||||
// Assert
|
||||
Assert.True(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_HandleEvent_NullEventArgs()
|
||||
{
|
||||
// Arrange
|
||||
TestEvent += HandleTestEvent;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleTestEvent(object? sender, EventArgs e)
|
||||
{
|
||||
if (sender == null)
|
||||
throw new ArgumentNullException(nameof(sender));
|
||||
|
||||
Assert.NotNull(sender);
|
||||
Assert.Equal(GetType(), sender.GetType());
|
||||
|
||||
Assert.Null(e);
|
||||
|
||||
didEventFire = true;
|
||||
TestEvent -= HandleTestEvent;
|
||||
}
|
||||
|
||||
// Act
|
||||
#pragma warning disable CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
TestWeakEventManager.RaiseEvent(this, null, nameof(TestEvent));
|
||||
#pragma warning restore CS8625
|
||||
|
||||
// Assert
|
||||
Assert.True(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_HandleEvent_InvalidHandleEventName()
|
||||
{
|
||||
// Arrange
|
||||
TestEvent += HandleTestEvent;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleTestEvent(object? sender, EventArgs e) => didEventFire = true;
|
||||
|
||||
// Act
|
||||
TestWeakEventManager.RaiseEvent(this, new EventArgs(), nameof(TestStringEvent));
|
||||
|
||||
// Assert
|
||||
Assert.False(didEventFire);
|
||||
TestEvent -= HandleTestEvent;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_UnassignedEvent()
|
||||
{
|
||||
// Arrange
|
||||
var didEventFire = false;
|
||||
|
||||
TestEvent += HandleTestEvent;
|
||||
TestEvent -= HandleTestEvent;
|
||||
void HandleTestEvent(object? sender, EventArgs e) => didEventFire = true;
|
||||
|
||||
// Act
|
||||
TestWeakEventManager.RaiseEvent(null, null, nameof(TestEvent));
|
||||
|
||||
// Assert
|
||||
Assert.False(didEventFire);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_UnassignedEventManager()
|
||||
{
|
||||
// Arrange
|
||||
var unassignedEventManager = new WeakEventManager();
|
||||
var didEventFire = false;
|
||||
|
||||
TestEvent += HandleTestEvent;
|
||||
void HandleTestEvent(object? sender, EventArgs e) => didEventFire = true;
|
||||
|
||||
// Act
|
||||
unassignedEventManager.RaiseEvent(null, null, nameof(TestEvent));
|
||||
|
||||
// Assert
|
||||
Assert.False(didEventFire);
|
||||
TestEvent -= HandleTestEvent;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_AddEventHandler_NullHandler()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => TestWeakEventManager.AddEventHandler(null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_AddEventHandler_NullEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => TestWeakEventManager.AddEventHandler(null, null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_AddEventHandler_EmptyEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => TestWeakEventManager.AddEventHandler(null, string.Empty));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_AddEventHandler_WhitespaceEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => TestWeakEventManager.AddEventHandler(null, " "));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_RemoveEventHandler_NullHandler()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => TestWeakEventManager.RemoveEventHandler(null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_RemoveEventHandler_NullEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => TestWeakEventManager.RemoveEventHandler(null, null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_RemoveEventHandler_EmptyEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => TestWeakEventManager.RemoveEventHandler(null, string.Empty));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_RemoveEventHandler_WhiteSpaceEventName()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference
|
||||
Assert.Throws<ArgumentNullException>(() => TestWeakEventManager.RemoveEventHandler(null, " "));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WeakEventManager_HandleEvent_InvalidHandleEvent()
|
||||
{
|
||||
// Arrange
|
||||
TestEvent += HandleTestEvent;
|
||||
var didEventFire = false;
|
||||
|
||||
void HandleTestEvent(object? sender, EventArgs e) => didEventFire = true;
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<InvalidHandleEventException>(() => TestWeakEventManager.RaiseEvent(nameof(TestEvent)));
|
||||
Assert.False(didEventFire);
|
||||
TestEvent -= HandleTestEvent;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xunit;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.UnitTests.ObjectModel.ICommandTests.AsyncCommandTests
|
||||
{
|
||||
public class AsyncCommandTests : BaseAsyncCommandTests
|
||||
{
|
||||
[Fact]
|
||||
public void AsyncCommand_NullExecuteParameter()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
Assert.Throws<ArgumentNullException>(() => new AsyncCommand(null));
|
||||
Assert.Throws<ArgumentNullException>(() => new AsyncCommand<string>(null));
|
||||
Assert.Throws<ArgumentNullException>(() => new AsyncCommand<string, string>(null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(500)]
|
||||
[InlineData(0)]
|
||||
public async Task AsyncCommand_ExecuteAsync_IntParameter_Test(int parameter)
|
||||
{
|
||||
// Arrange
|
||||
var command = new AsyncCommand<int>(IntParameterTask);
|
||||
|
||||
// Act
|
||||
await command.ExecuteAsync(parameter);
|
||||
|
||||
// Assert
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Hello")]
|
||||
[InlineData(default)]
|
||||
public async Task AsyncCommand_ExecuteAsync_StringParameter_Test(string parameter)
|
||||
{
|
||||
// Arrange
|
||||
var command = new AsyncCommand<string>(StringParameterTask);
|
||||
|
||||
// Act
|
||||
await command.ExecuteAsync(parameter);
|
||||
|
||||
// Assert
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncCommand_Parameter_CanExecuteTrue_Test()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AsyncCommand<int>(IntParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
|
||||
Assert.True(command.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncCommand_Parameter_CanExecuteFalse_Test()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AsyncCommand<int>(IntParameterTask, CanExecuteFalse);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncCommand_NoParameter_CanExecuteTrue_Test()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AsyncCommand(NoParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncCommand_NoParameter_CanExecuteFalse_Test()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AsyncCommand(NoParameterTask, CanExecuteFalse);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncCommand_CanExecuteChanged_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canCommandExecute = false;
|
||||
var didCanExecuteChangeFire = false;
|
||||
|
||||
var command = new AsyncCommand(NoParameterTask, commandCanExecute);
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
bool commandCanExecute(object parameter) => canCommandExecute;
|
||||
|
||||
Assert.False(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
canCommandExecute = true;
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.False(didCanExecuteChangeFire);
|
||||
|
||||
// Act
|
||||
command.RaiseCanExecuteChanged();
|
||||
|
||||
// Assert
|
||||
Assert.True(didCanExecuteChangeFire);
|
||||
Assert.True(command.CanExecute(null));
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => didCanExecuteChangeFire = true;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncCommand_CanExecuteChanged_AllowsMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
var command = new AsyncCommand<int>(IntParameterTask);
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
Assert.True(command.AllowsMultipleExecutions);
|
||||
|
||||
// Act
|
||||
var asyncCommandTask = command.ExecuteAsync(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.IsExecuting);
|
||||
Assert.True(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await asyncCommandTask;
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(0, canExecuteChangedCount);
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncCommand_CanExecuteChanged_DoesNotAllowMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
var command = new AsyncCommand<int>(IntParameterTask, allowsMultipleExecutions: false);
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
Assert.False(command.AllowsMultipleExecutions);
|
||||
|
||||
// Act
|
||||
var asyncCommandTask = command.ExecuteAsync(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.IsExecuting);
|
||||
Assert.False(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await asyncCommandTask;
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(2, canExecuteChangedCount);
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace Xamarin.CommunityToolkit.UnitTests.ObjectModel.ICommandTests.AsyncCommandTests
|
||||
{
|
||||
public abstract class BaseAsyncCommandTests : BaseCommandTests
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.CommunityToolkit.Exceptions;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xunit;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.UnitTests.ObjectModel.ICommandTests.AsyncCommandTests
|
||||
{
|
||||
public class IAsyncCommandTests : BaseAsyncCommandTests
|
||||
{
|
||||
[Fact]
|
||||
public void IAsyncCommand_CanExecute_InvalidReferenceParameter()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncCommand<int, bool> command = new AsyncCommand<int, bool>(IntParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<InvalidCommandParameterException>(() => command.CanExecute("Hello World"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IAsyncCommand_Execute_InvalidValueTypeParameter()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncCommand<string, bool> command = new AsyncCommand<string, bool>(StringParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<InvalidCommandParameterException>(() => command.Execute(true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IAsyncCommand_Execute_InvalidReferenceParameter()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncCommand<int, bool> command = new AsyncCommand<int, bool>(IntParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<InvalidCommandParameterException>(() => command.Execute("Hello World"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IAsyncCommand_CanExecute_InvalidValueTypeParameter()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncCommand<int, string> command = new AsyncCommand<int, string>(IntParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<InvalidCommandParameterException>(() => command.CanExecute(true));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Hello")]
|
||||
[InlineData(default)]
|
||||
public async Task AsyncCommand_ExecuteAsync_StringParameter_Test(string parameter)
|
||||
{
|
||||
// Arrange
|
||||
IAsyncCommand<string> command = new AsyncCommand<string>(StringParameterTask);
|
||||
IAsyncCommand<string, int> command2 = new AsyncCommand<string, int>(StringParameterTask);
|
||||
|
||||
// Act
|
||||
await command.ExecuteAsync(parameter);
|
||||
await command2.ExecuteAsync(parameter);
|
||||
|
||||
// Assert
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IAsyncCommand_Parameter_CanExecuteTrue_Test()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncCommand<int> command = new AsyncCommand<int>(IntParameterTask, CanExecuteTrue);
|
||||
IAsyncCommand<int, bool> command2 = new AsyncCommand<int, bool>(IntParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.True(command2.CanExecute(true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IAsyncCommand_Parameter_CanExecuteFalse_Test()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncCommand<int> command = new AsyncCommand<int>(IntParameterTask, CanExecuteFalse);
|
||||
IAsyncCommand<int, string> command2 = new AsyncCommand<int, string>(IntParameterTask, CanExecuteFalse);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
Assert.False(command2.CanExecute("Hello World"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IAsyncCommand_NoParameter_CanExecuteTrue_Test()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncCommand command = new AsyncCommand(NoParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IAsyncCommand_NoParameter_CanExecuteFalse_Test()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncCommand command = new AsyncCommand(NoParameterTask, CanExecuteFalse);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IAsyncCommand_CanExecuteChanged_AllowsMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
IAsyncCommand<int> command = new AsyncCommand<int>(IntParameterTask);
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
Assert.True(command.AllowsMultipleExecutions);
|
||||
|
||||
// Act
|
||||
var asyncCommandTask = command.ExecuteAsync(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.IsExecuting);
|
||||
Assert.True(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await asyncCommandTask;
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(0, canExecuteChangedCount);
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IAsyncCommand_CanExecuteChanged_DoesNotAllowMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
IAsyncCommand<int> command = new AsyncCommand<int>(IntParameterTask, allowsMultipleExecutions: false);
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
Assert.False(command.AllowsMultipleExecutions);
|
||||
|
||||
// Act
|
||||
var asyncCommandTask = command.ExecuteAsync(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.IsExecuting);
|
||||
Assert.False(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await asyncCommandTask;
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(2, canExecuteChangedCount);
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.CommunityToolkit.Exceptions;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xunit;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.UnitTests.ObjectModel.ICommandTests.AsyncCommandTests
|
||||
{
|
||||
public class ICommand_AsyncCommandTests : BaseAsyncCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(500)]
|
||||
[InlineData(0)]
|
||||
public async Task ICommand_Execute_IntParameter_Test(int parameter)
|
||||
{
|
||||
// Arrange
|
||||
ICommand command = new AsyncCommand<int>(IntParameterTask);
|
||||
|
||||
// Act
|
||||
command.Execute(parameter);
|
||||
await NoParameterTask();
|
||||
|
||||
// Assert
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Hello")]
|
||||
[InlineData(default)]
|
||||
public async Task ICommand_Execute_StringParameter_Test(string parameter)
|
||||
{
|
||||
// Arrange
|
||||
ICommand command = new AsyncCommand<string>(StringParameterTask);
|
||||
|
||||
// Act
|
||||
command.Execute(parameter);
|
||||
await NoParameterTask();
|
||||
|
||||
// Assert
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ICommand_ExecuteAsync_InvalidValueTypeParameter_Test()
|
||||
{
|
||||
// Arrange
|
||||
InvalidCommandParameterException actualInvalidCommandParameterException = null;
|
||||
var expectedInvalidCommandParameterException = new InvalidCommandParameterException(typeof(string), typeof(int));
|
||||
|
||||
ICommand command = new AsyncCommand<string>(StringParameterTask);
|
||||
|
||||
// Act
|
||||
|
||||
actualInvalidCommandParameterException = Assert.Throws<InvalidCommandParameterException>(() => command.Execute(Delay));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(actualInvalidCommandParameterException);
|
||||
Assert.Equal(expectedInvalidCommandParameterException.Message, actualInvalidCommandParameterException?.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ICommand_ExecuteAsync_InvalidReferenceTypeParameter_Test()
|
||||
{
|
||||
// Arrange
|
||||
InvalidCommandParameterException actualInvalidCommandParameterException = null;
|
||||
var expectedInvalidCommandParameterException = new InvalidCommandParameterException(typeof(int), typeof(string));
|
||||
|
||||
ICommand command = new AsyncCommand<int>(IntParameterTask);
|
||||
|
||||
// Act
|
||||
actualInvalidCommandParameterException = Assert.Throws<InvalidCommandParameterException>(() => command.Execute("Hello World"));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(actualInvalidCommandParameterException);
|
||||
Assert.Equal(expectedInvalidCommandParameterException.Message, actualInvalidCommandParameterException?.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ICommand_ExecuteAsync_ValueTypeParameter_Test()
|
||||
{
|
||||
// Arrange
|
||||
InvalidCommandParameterException actualInvalidCommandParameterException = null;
|
||||
var expectedInvalidCommandParameterException = new InvalidCommandParameterException(typeof(int));
|
||||
|
||||
ICommand command = new AsyncCommand<int>(IntParameterTask);
|
||||
|
||||
// Act
|
||||
actualInvalidCommandParameterException = Assert.Throws<InvalidCommandParameterException>(() => command.Execute(null));
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(actualInvalidCommandParameterException);
|
||||
Assert.Equal(expectedInvalidCommandParameterException.Message, actualInvalidCommandParameterException?.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ICommand_Parameter_CanExecuteTrue_Test()
|
||||
{
|
||||
// Arrange
|
||||
ICommand command = new AsyncCommand<int>(IntParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ICommand_Parameter_CanExecuteFalse_Test()
|
||||
{
|
||||
// Arrange
|
||||
ICommand command = new AsyncCommand<int>(IntParameterTask, CanExecuteFalse);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ICommand_NoParameter_CanExecuteFalse_Test()
|
||||
{
|
||||
// Arrange
|
||||
ICommand command = new AsyncCommand(NoParameterTask, CanExecuteFalse);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ICommand_Parameter_CanExecuteDynamic_Test()
|
||||
{
|
||||
// Arrange
|
||||
ICommand command = new AsyncCommand<int>(IntParameterTask, CanExecuteDynamic);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(true));
|
||||
Assert.False(command.CanExecute(false));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ICommand_Parameter_CanExecuteChanged_Test()
|
||||
{
|
||||
// Arrange
|
||||
ICommand command = new AsyncCommand<int>(IntParameterTask, CanExecuteDynamic);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(true));
|
||||
Assert.False(command.CanExecute(false));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ICommand_Parameter_CanExecuteChanged_AllowsMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
ICommand command = new AsyncCommand<int>(IntParameterTask);
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
|
||||
// Act
|
||||
command.Execute(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await IntParameterTask(Delay);
|
||||
await IntParameterTask(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(0, canExecuteChangedCount);
|
||||
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ICommand_Parameter_CanExecuteChanged_DoesNotAllowMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
ICommand command = new AsyncCommand<int>(IntParameterTask, allowsMultipleExecutions: false);
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
// Act
|
||||
command.Execute(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await IntParameterTask(Delay);
|
||||
await IntParameterTask(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(2, canExecuteChangedCount);
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ICommand_NoParameter_CanExecuteChanged_AllowsMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
ICommand command = new AsyncCommand(() => IntParameterTask(Delay));
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
|
||||
// Act
|
||||
command.Execute(null);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await IntParameterTask(Delay);
|
||||
await IntParameterTask(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(0, canExecuteChangedCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ICommand_NoParameter_CanExecuteChanged_DoesNotAllowMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
ICommand command = new AsyncCommand(() => IntParameterTask(Delay), allowsMultipleExecutions: false);
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
|
||||
// Act
|
||||
command.Execute(null);
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await IntParameterTask(Delay);
|
||||
await IntParameterTask(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(2, canExecuteChangedCount);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xunit;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.UnitTests.ObjectModel.ICommandTests.AsyncValueCommandTests
|
||||
{
|
||||
public class AsyncValueCommandTests : BaseAsyncValueCommandTests
|
||||
{
|
||||
[Fact]
|
||||
public void AsyncValueCommandNullExecuteParameter()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
Assert.Throws<ArgumentNullException>(() => new AsyncValueCommand(null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncValueCommandT_NullExecuteParameter()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
#pragma warning disable CS8625 //Cannot convert null literal to non-nullable reference type
|
||||
Assert.Throws<ArgumentNullException>(() => new AsyncValueCommand<object>(null));
|
||||
#pragma warning restore CS8625
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(500)]
|
||||
[InlineData(0)]
|
||||
public async Task AsyncValueCommandExecuteAsync_IntParameter_Test(int parameter)
|
||||
{
|
||||
// Arrange
|
||||
var command = new AsyncValueCommand<int>(IntParameterTask);
|
||||
var command2 = new AsyncValueCommand<int, string>(IntParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
await command.ExecuteAsync(parameter);
|
||||
await command2.ExecuteAsync(parameter);
|
||||
|
||||
// Assert
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Hello")]
|
||||
[InlineData(default)]
|
||||
public async Task AsyncValueCommandExecuteAsync_StringParameter_Test(string parameter)
|
||||
{
|
||||
// Arrange
|
||||
var command = new AsyncValueCommand<string>(StringParameterTask);
|
||||
var command2 = new AsyncValueCommand<string, bool>(StringParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
await command.ExecuteAsync(parameter);
|
||||
await command2.ExecuteAsync(parameter);
|
||||
|
||||
// Assert
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncValueCommandParameter_CanExecuteTrue_Test()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AsyncValueCommand<int>(IntParameterTask, CanExecuteTrue);
|
||||
var command2 = new AsyncValueCommand<int, bool>(IntParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.True(command2.CanExecute(true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncValueCommandParameter_CanExecuteFalse_Test()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AsyncValueCommand<int>(IntParameterTask, CanExecuteFalse);
|
||||
var command2 = new AsyncValueCommand<int, string>(IntParameterTask, CanExecuteFalse);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
Assert.False(command2.CanExecute("Hello World"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncValueCommandNoParameter_CanExecuteTrue_Test()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AsyncValueCommand(NoParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncValueCommandNoParameter_CanExecuteFalse_Test()
|
||||
{
|
||||
// Arrange
|
||||
var command = new AsyncValueCommand(NoParameterTask, CanExecuteFalse);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncValueCommandCanExecuteChanged_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canCommandExecute = false;
|
||||
var didCanExecuteChangeFire = false;
|
||||
|
||||
var command = new AsyncValueCommand(NoParameterTask, commandCanExecute);
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
bool commandCanExecute(object parameter) => canCommandExecute;
|
||||
|
||||
Assert.False(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
canCommandExecute = true;
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.False(didCanExecuteChangeFire);
|
||||
|
||||
// Act
|
||||
command.RaiseCanExecuteChanged();
|
||||
|
||||
// Assert
|
||||
Assert.True(didCanExecuteChangeFire);
|
||||
Assert.True(command.CanExecute(null));
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => didCanExecuteChangeFire = true;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncValueCommand_Parameter_CanExecuteChanged_AllowsMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
var command = new AsyncValueCommand<int>(IntParameterTask);
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
|
||||
Assert.True(command.AllowsMultipleExecutions);
|
||||
|
||||
// Act
|
||||
var asyncCommandTask = command.ExecuteAsync(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.IsExecuting);
|
||||
Assert.True(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await asyncCommandTask;
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(0, canExecuteChangedCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncValueCommand_Parameter_CanExecuteChanged_DoesNotAllowMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
var command = new AsyncValueCommand<int>(IntParameterTask, allowsMultipleExecutions: false);
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
|
||||
Assert.False(command.AllowsMultipleExecutions);
|
||||
|
||||
// Act
|
||||
var asyncCommandTask = command.ExecuteAsync(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.IsExecuting);
|
||||
Assert.False(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await asyncCommandTask;
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(2, canExecuteChangedCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncValueCommand_NoParameter_CanExecuteChanged_AllowsMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
var command = new AsyncValueCommand(() => IntParameterTask(Delay));
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
|
||||
Assert.True(command.AllowsMultipleExecutions);
|
||||
|
||||
// Act
|
||||
var asyncCommandTask = command.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
Assert.True(command.IsExecuting);
|
||||
Assert.True(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await asyncCommandTask;
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(0, canExecuteChangedCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AsyncValueCommand_NoParameter_CanExecuteChanged_DoesNotAllowMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
var command = new AsyncValueCommand(() => IntParameterTask(Delay), allowsMultipleExecutions: false);
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
|
||||
Assert.False(command.AllowsMultipleExecutions);
|
||||
|
||||
// Act
|
||||
var asyncCommandTask = command.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
Assert.True(command.IsExecuting);
|
||||
Assert.False(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await asyncCommandTask;
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(2, canExecuteChangedCount);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.UnitTests.ObjectModel.ICommandTests.AsyncValueCommandTests
|
||||
{
|
||||
public abstract class BaseAsyncValueCommandTests : BaseCommandTests
|
||||
{
|
||||
protected new ValueTask NoParameterTask() => ValueTaskDelay(Delay);
|
||||
|
||||
protected new ValueTask IntParameterTask(int delay) => ValueTaskDelay(delay);
|
||||
|
||||
protected new ValueTask StringParameterTask(string text) => ValueTaskDelay(Delay);
|
||||
|
||||
protected new ValueTask NoParameterImmediateNullReferenceExceptionTask() => throw new NullReferenceException();
|
||||
|
||||
protected new ValueTask ParameterImmediateNullReferenceExceptionTask(int delay) => throw new NullReferenceException();
|
||||
|
||||
protected async ValueTask ValueTaskDelay(int delay) => await Task.Delay(delay);
|
||||
|
||||
protected new async ValueTask NoParameterDelayedNullReferenceExceptionTask()
|
||||
{
|
||||
await Task.Delay(Delay);
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
protected new async ValueTask IntParameterDelayedNullReferenceExceptionTask(int delay)
|
||||
{
|
||||
await Task.Delay(delay);
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
using System.Threading.Tasks;
|
||||
using Xamarin.CommunityToolkit.Exceptions;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xunit;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.UnitTests.ObjectModel.ICommandTests.AsyncValueCommandTests
|
||||
{
|
||||
public class IAsyncValueCommandTests : BaseAsyncValueCommandTests
|
||||
{
|
||||
[Fact]
|
||||
public void IAsyncCommand_CanExecute_InvalidReferenceParameter()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncValueCommand<int, bool> command = new AsyncValueCommand<int, bool>(IntParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<InvalidCommandParameterException>(() => command.CanExecute("Hello World"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IAsyncCommand_Execute_InvalidValueTypeParameter()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncValueCommand<string, bool> command = new AsyncValueCommand<string, bool>(StringParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<InvalidCommandParameterException>(() => command.Execute(true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IAsyncCommand_Execute_InvalidReferenceParameter()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncValueCommand<int, bool> command = new AsyncValueCommand<int, bool>(IntParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<InvalidCommandParameterException>(() => command.Execute("Hello World"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IAsyncCommand_CanExecute_InvalidValueTypeParameter()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncValueCommand<int, string> command = new AsyncValueCommand<int, string>(IntParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.Throws<InvalidCommandParameterException>(() => command.CanExecute(true));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(500)]
|
||||
[InlineData(0)]
|
||||
public async Task AsyncValueCommand_ExecuteAsync_IntParameter_Test(int parameter)
|
||||
{
|
||||
// Arrange
|
||||
IAsyncValueCommand<int> command = new AsyncValueCommand<int>(IntParameterTask);
|
||||
IAsyncValueCommand<int> command2 = new AsyncValueCommand<int, string>(IntParameterTask);
|
||||
|
||||
// Act
|
||||
await command.ExecuteAsync(parameter);
|
||||
await command2.ExecuteAsync(parameter);
|
||||
|
||||
// Assert
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Hello")]
|
||||
[InlineData(default)]
|
||||
public async Task AsyncValueCommand_ExecuteAsync_StringParameter_Test(string parameter)
|
||||
{
|
||||
// Arrange
|
||||
IAsyncValueCommand<string> command = new AsyncValueCommand<string>(StringParameterTask);
|
||||
IAsyncValueCommand<string, int> command2 = new AsyncValueCommand<string, int>(StringParameterTask);
|
||||
|
||||
// Act
|
||||
await command.ExecuteAsync(parameter);
|
||||
await command2.ExecuteAsync(parameter);
|
||||
|
||||
// Assert
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IAsyncValueCommand_Parameter_CanExecuteTrue_Test()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncValueCommand<int> command = new AsyncValueCommand<int>(IntParameterTask, CanExecuteTrue);
|
||||
IAsyncValueCommand<int, string> command2 = new AsyncValueCommand<int, string>(IntParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.True(command.CanExecute("Hello World"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IAsyncValueCommand_Parameter_CanExecuteFalse_Test()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncValueCommand<int> command = new AsyncValueCommand<int>(IntParameterTask, CanExecuteFalse);
|
||||
IAsyncValueCommand<int, bool> command2 = new AsyncValueCommand<int, bool>(IntParameterTask, CanExecuteFalse);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
Assert.False(command2.CanExecute(true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IAsyncValueCommand_NoParameter_CanExecuteTrue_Test()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncValueCommand command = new AsyncValueCommand(NoParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IAsyncValueCommand_NoParameter_CanExecuteFalse_Test()
|
||||
{
|
||||
// Arrange
|
||||
IAsyncValueCommand command = new AsyncValueCommand(NoParameterTask, CanExecuteFalse);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.CommunityToolkit.Exceptions;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xunit;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.UnitTests.ObjectModel.ICommandTests.AsyncValueCommandTests
|
||||
{
|
||||
public class ICommand_AsyncValueCommandTests : BaseAsyncValueCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(500)]
|
||||
[InlineData(0)]
|
||||
public async Task ICommand_Execute_IntParameter_Test(int parameter)
|
||||
{
|
||||
// Arrange
|
||||
ICommand command = new AsyncValueCommand<int>(IntParameterTask);
|
||||
|
||||
// Act
|
||||
command.Execute(parameter);
|
||||
await NoParameterTask();
|
||||
|
||||
// Assert
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Hello")]
|
||||
[InlineData(default)]
|
||||
public async Task ICommand_Execute_StringParameter_Test(string parameter)
|
||||
{
|
||||
// Arrange
|
||||
ICommand command = new AsyncValueCommand<string>(StringParameterTask);
|
||||
|
||||
// Act
|
||||
command.Execute(parameter);
|
||||
await NoParameterTask();
|
||||
|
||||
// Assert
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ICommand_Execute_InvalidValueTypeParameter_Test()
|
||||
{
|
||||
// Arrange
|
||||
InvalidCommandParameterException actualInvalidCommandParameterException = null;
|
||||
var expectedInvalidCommandParameterException = new InvalidCommandParameterException(typeof(string), typeof(int));
|
||||
|
||||
ICommand command = new AsyncValueCommand<string>(StringParameterTask);
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
command.Execute(Delay);
|
||||
await NoParameterTask();
|
||||
await NoParameterTask();
|
||||
}
|
||||
catch (InvalidCommandParameterException e)
|
||||
{
|
||||
actualInvalidCommandParameterException = e;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(actualInvalidCommandParameterException);
|
||||
Assert.Equal(expectedInvalidCommandParameterException.Message, actualInvalidCommandParameterException?.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ICommand_Execute_InvalidReferenceTypeParameter_Test()
|
||||
{
|
||||
// Arrange
|
||||
InvalidCommandParameterException actualInvalidCommandParameterException = null;
|
||||
var expectedInvalidCommandParameterException = new InvalidCommandParameterException(typeof(int), typeof(string));
|
||||
|
||||
ICommand command = new AsyncValueCommand<int>(IntParameterTask);
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
command.Execute("Hello World");
|
||||
await NoParameterTask();
|
||||
await NoParameterTask();
|
||||
}
|
||||
catch (InvalidCommandParameterException e)
|
||||
{
|
||||
actualInvalidCommandParameterException = e;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(actualInvalidCommandParameterException);
|
||||
Assert.Equal(expectedInvalidCommandParameterException.Message, actualInvalidCommandParameterException?.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ICommand_Execute_ValueTypeParameter_Test()
|
||||
{
|
||||
// Arrange
|
||||
InvalidCommandParameterException actualInvalidCommandParameterException = null;
|
||||
var expectedInvalidCommandParameterException = new InvalidCommandParameterException(typeof(int));
|
||||
|
||||
ICommand command = new AsyncValueCommand<int>(IntParameterTask);
|
||||
|
||||
// Act
|
||||
try
|
||||
{
|
||||
command.Execute(null);
|
||||
await NoParameterTask();
|
||||
await NoParameterTask();
|
||||
}
|
||||
catch (InvalidCommandParameterException e)
|
||||
{
|
||||
actualInvalidCommandParameterException = e;
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(actualInvalidCommandParameterException);
|
||||
Assert.Equal(expectedInvalidCommandParameterException.Message, actualInvalidCommandParameterException?.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ICommand_Parameter_CanExecuteTrue_Test()
|
||||
{
|
||||
// Arrange
|
||||
ICommand command = new AsyncValueCommand<int>(IntParameterTask, CanExecuteTrue);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ICommand_Parameter_CanExecuteFalse_Test()
|
||||
{
|
||||
// Arrange
|
||||
ICommand command = new AsyncValueCommand<int>(IntParameterTask, CanExecuteFalse);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ICommand_NoParameter_CanExecuteFalse_Test()
|
||||
{
|
||||
// Arrange
|
||||
ICommand command = new AsyncValueCommand(NoParameterTask, CanExecuteFalse);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ICommand_Parameter_CanExecuteDynamic_Test()
|
||||
{
|
||||
// Arrange
|
||||
ICommand command = new AsyncValueCommand<int>(IntParameterTask, CanExecuteDynamic);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(true));
|
||||
Assert.False(command.CanExecute(false));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ICommand_Parameter_CanExecuteChanged_Test()
|
||||
{
|
||||
// Arrange
|
||||
ICommand command = new AsyncValueCommand<int>(IntParameterTask, CanExecuteDynamic);
|
||||
|
||||
// Act
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(true));
|
||||
Assert.False(command.CanExecute(false));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ICommand_Parameter_CanExecuteChanged_AllowsMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
ICommand command = new AsyncValueCommand<int>(IntParameterTask);
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
|
||||
// Act
|
||||
command.Execute(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await IntParameterTask(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(0, canExecuteChangedCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ICommand_Parameter_CanExecuteChanged_DoesNotAllowMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
ICommand command = new AsyncValueCommand<int>(IntParameterTask, allowsMultipleExecutions: false);
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
|
||||
// Act
|
||||
command.Execute(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await IntParameterTask(Delay);
|
||||
await IntParameterTask(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(2, canExecuteChangedCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ICommand_NoParameter_CanExecuteChanged_AllowsMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
ICommand command = new AsyncValueCommand(() => IntParameterTask(Delay));
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
|
||||
// Act
|
||||
command.Execute(null);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await IntParameterTask(Delay);
|
||||
await IntParameterTask(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(0, canExecuteChangedCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ICommand_NoParameter_CanExecuteChanged_DoesNotAllowMultipleExecutions_Test()
|
||||
{
|
||||
// Arrange
|
||||
var canExecuteChangedCount = 0;
|
||||
|
||||
ICommand command = new AsyncValueCommand(() => IntParameterTask(Delay), allowsMultipleExecutions: false);
|
||||
command.CanExecuteChanged += handleCanExecuteChanged;
|
||||
|
||||
void handleCanExecuteChanged(object sender, EventArgs e) => canExecuteChangedCount++;
|
||||
|
||||
// Act
|
||||
command.Execute(null);
|
||||
|
||||
// Assert
|
||||
Assert.False(command.CanExecute(null));
|
||||
|
||||
// Act
|
||||
await IntParameterTask(Delay);
|
||||
await IntParameterTask(Delay);
|
||||
|
||||
// Assert
|
||||
Assert.True(command.CanExecute(null));
|
||||
Assert.Equal(2, canExecuteChangedCount);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.CommunityToolkit.UnitTests.Mocks;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.UnitTests.ObjectModel.ICommandTests
|
||||
{
|
||||
public abstract class BaseCommandTests
|
||||
{
|
||||
public const int Delay = 500;
|
||||
|
||||
public BaseCommandTests() => Device.PlatformServices = new MockPlatformServices();
|
||||
|
||||
protected Task NoParameterTask() => Task.Delay(Delay);
|
||||
|
||||
protected Task IntParameterTask(int delay) => Task.Delay(delay);
|
||||
|
||||
protected Task StringParameterTask(string text) => Task.Delay(Delay);
|
||||
|
||||
protected Task NoParameterImmediateNullReferenceExceptionTask() => throw new NullReferenceException();
|
||||
|
||||
protected Task ParameterImmediateNullReferenceExceptionTask(int delay) => throw new NullReferenceException();
|
||||
|
||||
protected async Task NoParameterDelayedNullReferenceExceptionTask()
|
||||
{
|
||||
await Task.Delay(Delay);
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
protected async Task IntParameterDelayedNullReferenceExceptionTask(int delay)
|
||||
{
|
||||
await Task.Delay(delay);
|
||||
throw new NullReferenceException();
|
||||
}
|
||||
|
||||
protected bool CanExecuteTrue(bool parameter) => true;
|
||||
|
||||
protected bool CanExecuteTrue(string parameter) => true;
|
||||
|
||||
protected bool CanExecuteTrue(object parameter) => true;
|
||||
|
||||
protected bool CanExecuteFalse(bool parameter) => false;
|
||||
|
||||
protected bool CanExecuteFalse(string parameter) => false;
|
||||
|
||||
protected bool CanExecuteFalse(object parameter) => false;
|
||||
|
||||
protected bool CanExecuteDynamic(object booleanParameter)
|
||||
{
|
||||
if (booleanParameter is bool parameter)
|
||||
return parameter;
|
||||
|
||||
throw new InvalidCastException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@
|
|||
<ItemGroup>
|
||||
<Folder Include="Converters\" />
|
||||
<Folder Include="Mocks\" />
|
||||
<Folder Include="Helpers\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
|
||||
// Inspired by AsyncAwaitBestPractices.MVVM.InvalidCommandParameterException: https://github.com/brminnick/AsyncAwaitBestPractices
|
||||
namespace Xamarin.CommunityToolkit.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents errors that occur during IAsyncCommand execution.
|
||||
/// </summary>
|
||||
public class InvalidCommandParameterException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.InvalidCommandParameterException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="expectedType">Expected parameter type for AsyncCommand.Execute.</param>
|
||||
/// <param name="actualType">Actual parameter type for AsyncCommand.Execute.</param>
|
||||
/// <param name="innerException">Inner Exception</param>
|
||||
public InvalidCommandParameterException(Type expectedType, Type actualType, Exception innerException)
|
||||
: base(CreateErrorMessage(expectedType, actualType), innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.InvalidCommandParameterException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="expectedType">Expected parameter type for AsyncCommand.Execute.</param>
|
||||
/// <param name="actualType">Actual parameter type for AsyncCommand.Execute.</param>
|
||||
public InvalidCommandParameterException(Type expectedType, Type actualType)
|
||||
: base(CreateErrorMessage(expectedType, actualType))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.InvalidCommandParameterException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="expectedType">Expected parameter type for AsyncCommand.Execute.</param>
|
||||
/// <param name="innerException">Inner Exception</param>
|
||||
public InvalidCommandParameterException(Type expectedType, Exception innerException)
|
||||
: base(CreateErrorMessage(expectedType), innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:TaskExtensions.MVVM.InvalidCommandParameterException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="expectedType">Expected parameter type for AsyncCommand.Execute.</param>
|
||||
public InvalidCommandParameterException(Type expectedType)
|
||||
: base(CreateErrorMessage(expectedType))
|
||||
{
|
||||
}
|
||||
|
||||
static string CreateErrorMessage(Type expectedType) => $"Invalid type for parameter. Expected Type {expectedType}";
|
||||
|
||||
static string CreateErrorMessage(Type expectedType, Type actualType) => $"Invalid type for parameter. Expected Type {expectedType}, but received Type {actualType}";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
// Inspired by AsyncAwaitBestPractices.InvalidHandleEventException: https://github.com/brminnick/AsyncAwaitBestPractices
|
||||
namespace Xamarin.CommunityToolkit.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents errors that occur during WeakEventManager.HandleEvent execution.
|
||||
/// </summary>
|
||||
public class InvalidHandleEventException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:AsyncAwaitBestPractices.InvalidHandleEventException"/> class.
|
||||
/// </summary>
|
||||
/// <param name="message">Message.</param>
|
||||
/// <param name="targetParameterCountException">Target parameter count exception.</param>
|
||||
public InvalidHandleEventException(string message, TargetParameterCountException targetParameterCountException)
|
||||
: base(message, targetParameterCountException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
// Inspired by AsyncAwaitBestPractices.Subscription: https://github.com/brminnick/AsyncAwaitBestPractices
|
||||
namespace Xamarin.CommunityToolkit.Helpers
|
||||
{
|
||||
struct Subscription
|
||||
{
|
||||
public WeakReference Subscriber { get; }
|
||||
|
||||
public MethodInfo Handler { get; }
|
||||
|
||||
public Subscription(WeakReference subscriber, MethodInfo handler)
|
||||
{
|
||||
Subscriber = subscriber;
|
||||
Handler = handler ?? throw new ArgumentNullException(nameof(handler));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
using static System.String;
|
||||
|
||||
// Inspired by AsyncAwaitBestPractices.WeakEventManager: https://github.com/brminnick/AsyncAwaitBestPractices
|
||||
namespace Xamarin.CommunityToolkit.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Weak event manager that allows for garbage collection when the EventHandler is still subscribed
|
||||
/// </summary>
|
||||
/// <typeparam name="TEventArgs">Event args type.</typeparam>
|
||||
public partial class WeakEventManager<TEventArgs>
|
||||
{
|
||||
readonly Dictionary<string, List<Subscription>> eventHandlers = new Dictionary<string, List<Subscription>>();
|
||||
|
||||
/// <summary>
|
||||
/// Adds the event handler
|
||||
/// </summary>
|
||||
/// <param name="handler">Handler</param>
|
||||
/// <param name="eventName">Event name</param>
|
||||
public void AddEventHandler(EventHandler<TEventArgs> handler, [CallerMemberName] string eventName = "")
|
||||
{
|
||||
if (IsNullOrWhiteSpace(eventName))
|
||||
throw new ArgumentNullException(nameof(eventName));
|
||||
|
||||
if (handler == null)
|
||||
throw new ArgumentNullException(nameof(handler));
|
||||
|
||||
EventManagerService.AddEventHandler(eventName, handler.Target, handler.GetMethodInfo(), eventHandlers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the event handler
|
||||
/// </summary>
|
||||
/// <param name="action">Handler</param>
|
||||
/// <param name="eventName">Event name</param>
|
||||
public void AddEventHandler(Action<TEventArgs> action, [CallerMemberName] string eventName = "")
|
||||
{
|
||||
if (IsNullOrWhiteSpace(eventName))
|
||||
throw new ArgumentNullException(nameof(eventName));
|
||||
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
|
||||
EventManagerService.AddEventHandler(eventName, action.Target, action.GetMethodInfo(), eventHandlers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the event handler
|
||||
/// </summary>
|
||||
/// <param name="handler">Handler</param>
|
||||
/// <param name="eventName">Event name</param>
|
||||
public void RemoveEventHandler(EventHandler<TEventArgs> handler, [CallerMemberName] string eventName = "")
|
||||
{
|
||||
if (IsNullOrWhiteSpace(eventName))
|
||||
throw new ArgumentNullException(nameof(eventName));
|
||||
|
||||
if (handler == null)
|
||||
throw new ArgumentNullException(nameof(handler));
|
||||
|
||||
EventManagerService.RemoveEventHandler(eventName, handler.Target, handler.GetMethodInfo(), eventHandlers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the event handler
|
||||
/// </summary>
|
||||
/// <param name="action">Handler</param>
|
||||
/// <param name="eventName">Event name</param>
|
||||
public void RemoveEventHandler(Action<TEventArgs> action, [CallerMemberName] string eventName = "")
|
||||
{
|
||||
if (IsNullOrWhiteSpace(eventName))
|
||||
throw new ArgumentNullException(nameof(eventName));
|
||||
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
|
||||
EventManagerService.RemoveEventHandler(eventName, action.Target, action.GetMethodInfo(), eventHandlers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the event EventHandler
|
||||
/// </summary>
|
||||
/// <param name="sender">Sender</param>
|
||||
/// <param name="eventArgs">Event arguments</param>
|
||||
/// <param name="eventName">Event name</param>
|
||||
public void RaiseEvent(object sender, TEventArgs eventArgs, string eventName) =>
|
||||
EventManagerService.HandleEvent(eventName, sender, eventArgs, eventHandlers);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the event Action
|
||||
/// </summary>
|
||||
/// <param name="eventArgs">Event arguments</param>
|
||||
/// <param name="eventName">Event name</param>
|
||||
public void RaiseEvent(TEventArgs eventArgs, string eventName) =>
|
||||
EventManagerService.HandleEvent(eventName, eventArgs, eventHandlers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Weak event manager that allows for garbage collection when the EventHandler is still subscribed
|
||||
/// </summary>
|
||||
public partial class WeakEventManager
|
||||
{
|
||||
readonly Dictionary<string, List<Subscription>> eventHandlers = new Dictionary<string, List<Subscription>>();
|
||||
|
||||
/// <summary>
|
||||
/// Adds the event handler
|
||||
/// </summary>
|
||||
/// <param name="handler">Handler</param>
|
||||
/// <param name="eventName">Event name</param>
|
||||
public void AddEventHandler(Delegate handler, [CallerMemberName] string eventName = "")
|
||||
{
|
||||
if (IsNullOrWhiteSpace(eventName))
|
||||
throw new ArgumentNullException(nameof(eventName));
|
||||
|
||||
if (handler == null)
|
||||
throw new ArgumentNullException(nameof(handler));
|
||||
|
||||
EventManagerService.AddEventHandler(eventName, handler.Target, handler.GetMethodInfo(), eventHandlers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the event handler.
|
||||
/// </summary>
|
||||
/// <param name="handler">Handler</param>
|
||||
/// <param name="eventName">Event name</param>
|
||||
public void RemoveEventHandler(Delegate handler, [CallerMemberName] string eventName = "")
|
||||
{
|
||||
if (IsNullOrWhiteSpace(eventName))
|
||||
throw new ArgumentNullException(nameof(eventName));
|
||||
|
||||
if (handler == null)
|
||||
throw new ArgumentNullException(nameof(handler));
|
||||
|
||||
EventManagerService.RemoveEventHandler(eventName, handler.Target, handler.GetMethodInfo(), eventHandlers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the event EventHandler
|
||||
/// </summary>
|
||||
/// <param name="sender">Sender</param>
|
||||
/// <param name="eventArgs">Event arguments</param>
|
||||
/// <param name="eventName">Event name</param>
|
||||
public void RaiseEvent(object sender, object? eventArgs, string eventName) =>
|
||||
EventManagerService.HandleEvent(eventName, sender, eventArgs, eventHandlers);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the event Action
|
||||
/// </summary>
|
||||
/// <param name="eventName">Event name</param>
|
||||
public void RaiseEvent(string eventName) => EventManagerService.HandleEvent(eventName, eventHandlers);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Xamarin.CommunityToolkit.Exceptions;
|
||||
|
||||
// Inspired by AsyncAwaitBestPractices.WeakEventManagerService: https://github.com/brminnick/AsyncAwaitBestPractices
|
||||
namespace Xamarin.CommunityToolkit.Helpers
|
||||
{
|
||||
static class EventManagerService
|
||||
{
|
||||
internal static void AddEventHandler(in string eventName, in object handlerTarget, in MethodInfo methodInfo, in Dictionary<string, List<Subscription>> eventHandlers)
|
||||
{
|
||||
var doesContainSubscriptions = eventHandlers.TryGetValue(eventName, out var targets);
|
||||
if (!doesContainSubscriptions || targets == null)
|
||||
{
|
||||
targets = new List<Subscription>();
|
||||
eventHandlers.Add(eventName, targets);
|
||||
}
|
||||
|
||||
if (handlerTarget == null)
|
||||
targets.Add(new Subscription(null, methodInfo));
|
||||
else
|
||||
targets.Add(new Subscription(new WeakReference(handlerTarget), methodInfo));
|
||||
}
|
||||
|
||||
internal static void RemoveEventHandler(in string eventName, in object handlerTarget, in MemberInfo methodInfo, in Dictionary<string, List<Subscription>> eventHandlers)
|
||||
{
|
||||
var doesContainSubscriptions = eventHandlers.TryGetValue(eventName, out var subscriptions);
|
||||
if (!doesContainSubscriptions || subscriptions == null)
|
||||
return;
|
||||
|
||||
for (var n = subscriptions.Count; n > 0; n--)
|
||||
{
|
||||
var current = subscriptions[n - 1];
|
||||
|
||||
if (current.Subscriber?.Target != handlerTarget
|
||||
|| current.Handler.Name != methodInfo?.Name)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
subscriptions.Remove(current);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void HandleEvent(in string eventName, in object sender, in object eventArgs, in Dictionary<string, List<Subscription>> eventHandlers)
|
||||
{
|
||||
AddRemoveEvents(eventName, eventHandlers, out var toRaise);
|
||||
|
||||
for (var i = 0; i < toRaise.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (instance, eventHandler) = toRaise[i];
|
||||
if (eventHandler.IsLightweightMethod())
|
||||
{
|
||||
var method = TryGetDynamicMethod(eventHandler);
|
||||
method?.Invoke(instance, new[] { sender, eventArgs });
|
||||
}
|
||||
else
|
||||
{
|
||||
eventHandler.Invoke(instance, new[] { sender, eventArgs });
|
||||
}
|
||||
}
|
||||
catch (TargetParameterCountException e)
|
||||
{
|
||||
throw new InvalidHandleEventException("Parameter count mismatch. If invoking an `event Action` use `HandleEvent(string eventName)` or if invoking an `event Action<T>` use `HandleEvent(object eventArgs, string eventName)`instead.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void HandleEvent(in string eventName, in object actionEventArgs, in Dictionary<string, List<Subscription>> eventHandlers)
|
||||
{
|
||||
AddRemoveEvents(eventName, eventHandlers, out var toRaise);
|
||||
|
||||
for (var i = 0; i < toRaise.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (instance, eventHandler) = toRaise[i];
|
||||
if (eventHandler.IsLightweightMethod())
|
||||
{
|
||||
var method = TryGetDynamicMethod(eventHandler);
|
||||
method?.Invoke(instance, new[] { actionEventArgs });
|
||||
}
|
||||
else
|
||||
{
|
||||
eventHandler.Invoke(instance, new[] { actionEventArgs });
|
||||
}
|
||||
}
|
||||
catch (TargetParameterCountException e)
|
||||
{
|
||||
throw new InvalidHandleEventException("Parameter count mismatch. If invoking an `event EventHandler` use `HandleEvent(object sender, TEventArgs eventArgs, string eventName)` or if invoking an `event Action` use `HandleEvent(string eventName)`instead.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void HandleEvent(in string eventName, in Dictionary<string, List<Subscription>> eventHandlers)
|
||||
{
|
||||
AddRemoveEvents(eventName, eventHandlers, out var toRaise);
|
||||
|
||||
for (var i = 0; i < toRaise.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (instance, eventHandler) = toRaise[i];
|
||||
if (eventHandler.IsLightweightMethod())
|
||||
{
|
||||
var method = TryGetDynamicMethod(eventHandler);
|
||||
method?.Invoke(instance, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
eventHandler.Invoke(instance, null);
|
||||
}
|
||||
}
|
||||
catch (TargetParameterCountException e)
|
||||
{
|
||||
throw new InvalidHandleEventException("Parameter count mismatch. If invoking an `event EventHandler` use `HandleEvent(object sender, TEventArgs eventArgs, string eventName)` or if invoking an `event Action<T>` use `HandleEvent(object eventArgs, string eventName)`instead.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void AddRemoveEvents(in string eventName, in Dictionary<string, List<Subscription>> eventHandlers, out List<(object Instance, MethodInfo EventHandler)> toRaise)
|
||||
{
|
||||
var toRemove = new List<Subscription>();
|
||||
toRaise = new List<(object, MethodInfo)>();
|
||||
|
||||
var doesContainEventName = eventHandlers.TryGetValue(eventName, out var target);
|
||||
if (doesContainEventName && target != null)
|
||||
{
|
||||
for (var i = 0; i < target.Count; i++)
|
||||
{
|
||||
var subscription = target[i];
|
||||
var isStatic = subscription.Subscriber == null;
|
||||
|
||||
if (isStatic)
|
||||
{
|
||||
toRaise.Add((null, subscription.Handler));
|
||||
continue;
|
||||
}
|
||||
|
||||
var subscriber = subscription.Subscriber?.Target;
|
||||
|
||||
if (subscriber == null)
|
||||
toRemove.Add(subscription);
|
||||
else
|
||||
toRaise.Add((subscriber, subscription.Handler));
|
||||
}
|
||||
|
||||
for (var i = 0; i < toRemove.Count; i++)
|
||||
{
|
||||
var subscription = toRemove[i];
|
||||
target.Remove(subscription);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static DynamicMethod TryGetDynamicMethod(in MethodInfo rtDynamicMethod)
|
||||
{
|
||||
var typeInfoRTDynamicMethod = typeof(DynamicMethod).GetTypeInfo().GetDeclaredNestedType("RTDynamicMethod");
|
||||
var typeRTDynamicMethod = typeInfoRTDynamicMethod?.AsType();
|
||||
|
||||
return (typeInfoRTDynamicMethod?.IsAssignableFrom(rtDynamicMethod.GetType().GetTypeInfo()) ?? false) ?
|
||||
(DynamicMethod)typeRTDynamicMethod.GetRuntimeFields().First(f => f.Name is "m_owner").GetValue(rtDynamicMethod)
|
||||
: null;
|
||||
}
|
||||
|
||||
static bool IsLightweightMethod(this MethodBase method)
|
||||
{
|
||||
var typeInfoRTDynamicMethod = typeof(DynamicMethod).GetTypeInfo().GetDeclaredNestedType("RTDynamicMethod");
|
||||
return method is DynamicMethod || (typeInfoRTDynamicMethod?.IsAssignableFrom(method.GetType().GetTypeInfo()) ?? false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
// Inspired by AsyncAwaitBestPractices.MVVM.AsyncCommand<T>: https://github.com/brminnick/AsyncAwaitBestPractices
|
||||
namespace Xamarin.CommunityToolkit.ObjectModel
|
||||
{
|
||||
/// <summary>
|
||||
/// An implementation of IAsyncCommand. Allows Commands to safely be used asynchronously with Task.
|
||||
/// </summary>
|
||||
public class AsyncCommand<TExecute, TCanExecute> : BaseAsyncCommand<TExecute, TCanExecute>, IAsyncCommand<TExecute, TCanExecute>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the AsyncCommand
|
||||
/// </summary>
|
||||
/// <param name="execute">The Function executed when Execute or ExecuteAsync is called. This does not check canExecute before executing and will execute even if canExecute is false</param>
|
||||
/// <param name="canExecute">The Function that verifies whether or not AsyncCommand should execute.</param>
|
||||
/// <param name="onException">If an exception is thrown in the Task, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
|
||||
/// <param name="continueOnCapturedContext">If set to <c>true</c> continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c> continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
|
||||
public AsyncCommand(
|
||||
Func<TExecute, Task> execute,
|
||||
Func<TCanExecute, bool> canExecute = null,
|
||||
Action<Exception> onException = null,
|
||||
bool continueOnCapturedContext = false,
|
||||
bool allowsMultipleExecutions = true)
|
||||
: base(execute, canExecute, onException, continueOnCapturedContext, allowsMultipleExecutions)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the Command as a Task
|
||||
/// </summary>
|
||||
/// <returns>The executed Task</returns>
|
||||
public new Task ExecuteAsync(TExecute parameter) => base.ExecuteAsync(parameter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An implementation of IAsyncCommand. Allows Commands to safely be used asynchronously with Task.
|
||||
/// </summary>
|
||||
public class AsyncCommand<T> : BaseAsyncCommand<T, object>, IAsyncCommand<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of AsyncCommand
|
||||
/// </summary>
|
||||
/// <param name="execute">The Function executed when Execute or ExecuteAsync is called. This does not check canExecute before executing and will execute even if canExecute is false</param>
|
||||
/// <param name="canExecute">The Function that verifies whether or not AsyncCommand should execute.</param>
|
||||
/// <param name="onException">If an exception is thrown in the Task, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
|
||||
/// <param name="continueOnCapturedContext">If set to <c>true</c> continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c> continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
|
||||
public AsyncCommand(
|
||||
Func<T, Task> execute,
|
||||
Func<object, bool> canExecute = null,
|
||||
Action<Exception> onException = null,
|
||||
bool continueOnCapturedContext = false,
|
||||
bool allowsMultipleExecutions = true)
|
||||
: base(execute, canExecute, onException, continueOnCapturedContext, allowsMultipleExecutions)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the Command as a Task
|
||||
/// </summary>
|
||||
/// <returns>The executed Task</returns>
|
||||
public new Task ExecuteAsync(T parameter) => base.ExecuteAsync(parameter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An implementation of IAsyncCommand. Allows Commands to safely be used asynchronously with Task.
|
||||
/// </summary>
|
||||
public class AsyncCommand : BaseAsyncCommand<object, object>, IAsyncCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of AsyncCommand
|
||||
/// </summary>
|
||||
/// <param name="execute">The Function executed when Execute or ExecuteAsync is called. This does not check canExecute before executing and will execute even if canExecute is false</param>
|
||||
/// <param name="canExecute">The Function that verifies whether or not AsyncCommand should execute.</param>
|
||||
/// <param name="onException">If an exception is thrown in the Task, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
|
||||
/// <param name="continueOnCapturedContext">If set to <c>true</c> continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c> continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
|
||||
public AsyncCommand(
|
||||
Func<Task> execute,
|
||||
Func<object, bool> canExecute = null,
|
||||
Action<Exception> onException = null,
|
||||
bool continueOnCapturedContext = false,
|
||||
bool allowsMultipleExecutions = true)
|
||||
: base(ConvertExecute(execute), canExecute, onException, continueOnCapturedContext, allowsMultipleExecutions)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the Command as a Task
|
||||
/// </summary>
|
||||
/// <returns>The executed Task</returns>
|
||||
public Task ExecuteAsync() => ExecuteAsync(null);
|
||||
|
||||
static Func<object, Task> ConvertExecute(Func<Task> execute)
|
||||
{
|
||||
if (execute == null)
|
||||
return null;
|
||||
|
||||
return _ => execute();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// Inspired by AsyncAwaitBestPractices.MVVM.AsyncCommand<T>: https://github.com/brminnick/AsyncAwaitBestPractices
|
||||
namespace Xamarin.CommunityToolkit.ObjectModel
|
||||
{
|
||||
public class AsyncValueCommand<TExecute, TCanExecute> : AsyncValueCommand<TExecute>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
// Inspired by AsyncAwaitBestPractices.MVVM.AsyncCommand<T>: https://github.com/brminnick/AsyncAwaitBestPractices
|
||||
namespace Xamarin.CommunityToolkit.ObjectModel
|
||||
{
|
||||
public class AsyncValueCommand<TExecute, TCanExecute> : BaseAsyncValueCommand<TExecute, TCanExecute>, IAsyncValueCommand<TExecute, TCanExecute>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of AsyncValueCommand
|
||||
/// </summary>
|
||||
/// <param name="execute">The Function executed when Execute or ExecuteAsync is called. This does not check canExecute before executing and will execute even if canExecute is false</param>
|
||||
/// <param name="canExecute">The Function that verifies whether or not AsyncCommand should execute.</param>
|
||||
/// <param name="onException">If an exception is thrown in the Task, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
|
||||
/// <param name="continueOnCapturedContext">If set to <c>true</c> continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c> continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
|
||||
public AsyncValueCommand(
|
||||
Func<TExecute, ValueTask> execute,
|
||||
Func<TCanExecute, bool> canExecute = null,
|
||||
Action<Exception> onException = null,
|
||||
bool continueOnCapturedContext = false,
|
||||
bool allowsMultipleExecutions = true)
|
||||
: base(execute, canExecute, onException, continueOnCapturedContext, allowsMultipleExecutions)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the Command as a ValueTask
|
||||
/// </summary>
|
||||
/// <returns>The executed ValueTask</returns>
|
||||
public new ValueTask ExecuteAsync(TExecute parameter) => base.ExecuteAsync(parameter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An implementation of IAsyncValueCommand. Allows Commands to safely be used asynchronously with Task.
|
||||
/// </summary>
|
||||
public class AsyncValueCommand<T> : BaseAsyncValueCommand<T, object>, IAsyncValueCommand<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of AsyncValueCommand
|
||||
/// </summary>
|
||||
/// <param name="execute">The Function executed when Execute or ExecuteAsync is called. This does not check canExecute before executing and will execute even if canExecute is false</param>
|
||||
/// <param name="canExecute">The Function that verifies whether or not AsyncCommand should execute.</param>
|
||||
/// <param name="onException">If an exception is thrown in the Task, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
|
||||
/// <param name="continueOnCapturedContext">If set to <c>true</c> continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c> continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
|
||||
public AsyncValueCommand(
|
||||
Func<T, ValueTask> execute,
|
||||
Func<object, bool> canExecute = null,
|
||||
Action<Exception> onException = null,
|
||||
bool continueOnCapturedContext = false,
|
||||
bool allowsMultipleExecutions = true)
|
||||
: base(execute, canExecute, onException, continueOnCapturedContext, allowsMultipleExecutions)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the Command as a ValueTask
|
||||
/// </summary>
|
||||
/// <returns>The executed ValueTask</returns>
|
||||
public new ValueTask ExecuteAsync(T parameter) => base.ExecuteAsync(parameter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An implementation of IAsyncValueCommand. Allows Commands to safely be used asynchronously with Task.
|
||||
/// </summary>
|
||||
public class AsyncValueCommand : BaseAsyncValueCommand<object, object>, IAsyncValueCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of AsyncValueCommand
|
||||
/// </summary>
|
||||
/// <param name="execute">The Function executed when Execute or ExecuteAsync is called. This does not check canExecute before executing and will execute even if canExecute is false</param>
|
||||
/// <param name="canExecute">The Function that verifies whether or not AsyncCommand should execute.</param>
|
||||
/// <param name="onException">If an exception is thrown in the Task, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
|
||||
/// <param name="continueOnCapturedContext">If set to <c>true</c> continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c> continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
|
||||
public AsyncValueCommand(
|
||||
Func<ValueTask> execute,
|
||||
Func<object, bool> canExecute = null,
|
||||
Action<Exception> onException = null,
|
||||
bool continueOnCapturedContext = false,
|
||||
bool allowsMultipleExecutions = true)
|
||||
: base(ConvertExecute(execute), canExecute, onException, continueOnCapturedContext, allowsMultipleExecutions)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the Command as a ValueTask
|
||||
/// </summary>
|
||||
/// <returns>The executed ValueTask</returns>
|
||||
public ValueTask ExecuteAsync() => ExecuteAsync(null);
|
||||
|
||||
static Func<object, ValueTask> ConvertExecute(Func<ValueTask> execute)
|
||||
{
|
||||
if (execute == null)
|
||||
return null;
|
||||
|
||||
return _ => execute();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.CommunityToolkit.Exceptions;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.ObjectModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract Base Class used by AsyncValueCommand
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class BaseAsyncCommand<TExecute, TCanExecute> : BaseCommand<TCanExecute>, ICommand
|
||||
{
|
||||
readonly Func<TExecute, Task> execute;
|
||||
readonly Action<Exception> onException;
|
||||
readonly bool continueOnCapturedContext;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of BaseAsyncCommand
|
||||
/// </summary>
|
||||
/// <param name="execute">The Function executed when Execute or ExecuteAsync is called. This does not check canExecute before executing and will execute even if canExecute is false</param>
|
||||
/// <param name="canExecute">The Function that verifies whether or not AsyncCommand should execute.</param>
|
||||
/// <param name="onException">If an exception is thrown in the Task, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
|
||||
/// <param name="continueOnCapturedContext">If set to <c>true</c> continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c> continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
|
||||
public BaseAsyncCommand(
|
||||
Func<TExecute, Task> execute,
|
||||
Func<TCanExecute, bool> canExecute,
|
||||
Action<Exception> onException,
|
||||
bool continueOnCapturedContext,
|
||||
bool allowsMultipleExecutions)
|
||||
: base(canExecute, allowsMultipleExecutions)
|
||||
{
|
||||
this.execute = execute ?? throw new ArgumentNullException(nameof(execute), $"{nameof(execute)} cannot be null");
|
||||
this.onException = onException;
|
||||
this.continueOnCapturedContext = continueOnCapturedContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the Command as a Task
|
||||
/// </summary>
|
||||
/// <returns>The executed Task</returns>
|
||||
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
|
||||
private protected async Task ExecuteAsync(TExecute parameter)
|
||||
{
|
||||
ExecutionCount++;
|
||||
|
||||
try
|
||||
{
|
||||
await execute(parameter).ConfigureAwait(continueOnCapturedContext);
|
||||
}
|
||||
catch (Exception e) when (onException != null)
|
||||
{
|
||||
onException(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (--ExecutionCount <= 0)
|
||||
ExecutionCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool ICommand.CanExecute(object parameter) => parameter switch
|
||||
{
|
||||
TCanExecute validParameter => CanExecute(validParameter),
|
||||
null when !typeof(TCanExecute).GetTypeInfo().IsValueType => CanExecute((TCanExecute)parameter),
|
||||
null => throw new InvalidCommandParameterException(typeof(TCanExecute)),
|
||||
_ => throw new InvalidCommandParameterException(typeof(TCanExecute), parameter.GetType()),
|
||||
};
|
||||
|
||||
void ICommand.Execute(object parameter)
|
||||
{
|
||||
switch (parameter)
|
||||
{
|
||||
case TExecute validParameter:
|
||||
Execute(validParameter);
|
||||
break;
|
||||
|
||||
case null when !typeof(TExecute).GetTypeInfo().IsValueType:
|
||||
Execute((TExecute)parameter);
|
||||
break;
|
||||
|
||||
case null:
|
||||
throw new InvalidCommandParameterException(typeof(TExecute));
|
||||
|
||||
default:
|
||||
throw new InvalidCommandParameterException(typeof(TExecute), parameter.GetType());
|
||||
}
|
||||
|
||||
// Use local method to defer async void from ICommand.Execute, allowing InvalidCommandParameterException to be thrown on the calling thread context before reaching an async method
|
||||
async void Execute(TExecute parameter) => await ExecuteAsync(parameter).ConfigureAwait(continueOnCapturedContext);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.CommunityToolkit.Exceptions;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.ObjectModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract Base Class used by AsyncValueCommand
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class BaseAsyncValueCommand<TExecute, TCanExecute> : BaseCommand<TCanExecute>, ICommand
|
||||
{
|
||||
readonly Func<TExecute, ValueTask> execute;
|
||||
readonly Action<Exception> onException;
|
||||
readonly bool continueOnCapturedContext;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of BaseAsyncValueCommand
|
||||
/// </summary>
|
||||
/// <param name="execute">The Function executed when Execute or ExecuteAsync is called. This does not check canExecute before executing and will execute even if canExecute is false</param>
|
||||
/// <param name="canExecute">The Function that verifies whether or not AsyncCommand should execute.</param>
|
||||
/// <param name="onException">If an exception is thrown in the Task, <c>onException</c> will execute. If onException is null, the exception will be re-thrown</param>
|
||||
/// <param name="continueOnCapturedContext">If set to <c>true</c> continue on captured context; this will ensure that the Synchronization Context returns to the calling thread. If set to <c>false</c> continue on a different context; this will allow the Synchronization Context to continue on a different thread</param>
|
||||
public BaseAsyncValueCommand(
|
||||
Func<TExecute, ValueTask> execute,
|
||||
Func<TCanExecute, bool> canExecute,
|
||||
Action<Exception> onException,
|
||||
bool continueOnCapturedContext,
|
||||
bool allowsMultipleExecutions)
|
||||
: base(canExecute, allowsMultipleExecutions)
|
||||
{
|
||||
this.execute = execute ?? throw new ArgumentNullException(nameof(execute), $"{nameof(execute)} cannot be null");
|
||||
this.onException = onException;
|
||||
this.continueOnCapturedContext = continueOnCapturedContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the Command as a Task
|
||||
/// </summary>
|
||||
/// <returns>The executed Value</returns>
|
||||
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
|
||||
private protected async ValueTask ExecuteAsync(TExecute parameter)
|
||||
{
|
||||
ExecutionCount++;
|
||||
|
||||
try
|
||||
{
|
||||
await execute(parameter).ConfigureAwait(continueOnCapturedContext);
|
||||
}
|
||||
catch (Exception e) when (onException != null)
|
||||
{
|
||||
onException(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (--ExecutionCount <= 0)
|
||||
ExecutionCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool ICommand.CanExecute(object parameter) => parameter switch
|
||||
{
|
||||
TCanExecute validParameter => CanExecute(validParameter),
|
||||
null when !typeof(TCanExecute).GetTypeInfo().IsValueType => CanExecute((TCanExecute)parameter),
|
||||
null => throw new InvalidCommandParameterException(typeof(TCanExecute)),
|
||||
_ => throw new InvalidCommandParameterException(typeof(TCanExecute), parameter.GetType()),
|
||||
};
|
||||
|
||||
void ICommand.Execute(object parameter)
|
||||
{
|
||||
switch (parameter)
|
||||
{
|
||||
case TExecute validParameter:
|
||||
Execute(validParameter);
|
||||
break;
|
||||
|
||||
case null when !typeof(TExecute).GetTypeInfo().IsValueType:
|
||||
Execute((TExecute)parameter);
|
||||
break;
|
||||
|
||||
case null:
|
||||
throw new InvalidCommandParameterException(typeof(TExecute));
|
||||
|
||||
default:
|
||||
throw new InvalidCommandParameterException(typeof(TExecute), parameter.GetType());
|
||||
}
|
||||
|
||||
// Use local method to defer async void from ICommand.Execute, allowing InvalidCommandParameterException to be thrown on the calling thread context before reaching an async method
|
||||
async void Execute(TExecute parameter) => await ExecuteAsync(parameter).ConfigureAwait(continueOnCapturedContext);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using Xamarin.CommunityToolkit.Helpers;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.ObjectModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract Base Class used by AsyncCommand and AsyncValueCommand
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract class BaseCommand<TCanExecute>
|
||||
{
|
||||
readonly Func<TCanExecute, bool> canExecute;
|
||||
readonly WeakEventManager weakEventManager = new WeakEventManager();
|
||||
|
||||
int executionCount;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes BaseCommand
|
||||
/// </summary>
|
||||
/// <param name="canExecute"></param>
|
||||
/// <param name="allowsMultipleExecutions"></param>
|
||||
public BaseCommand(Func<TCanExecute, bool> canExecute, bool allowsMultipleExecutions)
|
||||
{
|
||||
this.canExecute = canExecute ?? (_ => true);
|
||||
AllowsMultipleExecutions = allowsMultipleExecutions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when changes occur that affect whether or not the command should execute
|
||||
/// </summary>
|
||||
public event EventHandler CanExecuteChanged
|
||||
{
|
||||
add => weakEventManager.AddEventHandler(value);
|
||||
remove => weakEventManager.RemoveEventHandler(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when the Command is currently executing. Returns false when the Command is not executing
|
||||
/// </summary>
|
||||
public bool IsExecuting => ExecutionCount > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the Command allows simultaneous executions
|
||||
/// </summary>
|
||||
public bool AllowsMultipleExecutions { get; }
|
||||
|
||||
protected int ExecutionCount
|
||||
{
|
||||
get => executionCount;
|
||||
set
|
||||
{
|
||||
var shouldRaiseCanExecuteChanged = AllowsMultipleExecutions switch
|
||||
{
|
||||
true => false,
|
||||
false when executionCount is 0 && value > 0 => true,
|
||||
false when executionCount > 0 && value is 0 => true,
|
||||
false => false
|
||||
};
|
||||
|
||||
executionCount = value;
|
||||
|
||||
if (shouldRaiseCanExecuteChanged)
|
||||
RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the command can execute in its current state
|
||||
/// </summary>
|
||||
/// <returns><c>true</c>, if this command can be executed; otherwise, <c>false</c>.</returns>
|
||||
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
|
||||
public bool CanExecute(TCanExecute parameter) => (AllowsMultipleExecutions, IsExecuting) switch
|
||||
{
|
||||
(true, _) => canExecute(parameter),
|
||||
(false, true) => false,
|
||||
(false, false) => canExecute(parameter),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Raises the CanExecuteChanged event.
|
||||
/// </summary>
|
||||
public void RaiseCanExecuteChanged()
|
||||
{
|
||||
// Automatically marshall to the Main Thread to adhere to the way that Xamarin.Forms automatically marshalls binding events to Main Thread
|
||||
Device.BeginInvokeOnMainThread(() => weakEventManager.RaiseEvent(this, EventArgs.Empty, nameof(CanExecuteChanged)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
// Inspired by AsyncAwaitBestPractices.MVVM.IAsyncCommand: https://github.com/brminnick/AsyncAwaitBestPractices
|
||||
namespace Xamarin.CommunityToolkit.ObjectModel
|
||||
{
|
||||
/// <summary>
|
||||
/// An Async implementation of ICommand for Task
|
||||
/// </summary>
|
||||
public interface IAsyncCommand<in TExecute, in TCanExecute> : IAsyncCommand<TExecute>
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the command can execute in its current state
|
||||
/// </summary>
|
||||
/// <returns><c>true</c>, if this command can be executed; otherwise, <c>false</c>.</returns>
|
||||
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
|
||||
bool CanExecute(TCanExecute parameter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An Async implementation of ICommand for Task
|
||||
/// </summary>
|
||||
public interface IAsyncCommand<in T> : System.Windows.Input.ICommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true when the Command is currently executing. Returns false when the Command is not executing
|
||||
/// </summary>
|
||||
bool IsExecuting { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the Command allows simultaneous executions
|
||||
/// </summary>
|
||||
bool AllowsMultipleExecutions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Executes the Command as a Task
|
||||
/// </summary>
|
||||
/// <returns>The Task to execute</returns>
|
||||
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
|
||||
System.Threading.Tasks.Task ExecuteAsync(T parameter);
|
||||
|
||||
/// <summary>
|
||||
/// Raises the CanExecuteChanged event.
|
||||
/// </summary>
|
||||
void RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An Async implementation of ICommand for Task
|
||||
/// </summary>
|
||||
public interface IAsyncCommand : System.Windows.Input.ICommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true when the Command is currently executing. Returns false when the Command is not executing
|
||||
/// </summary>
|
||||
bool IsExecuting { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the Command allows simultaneous executions
|
||||
/// </summary>
|
||||
bool AllowsMultipleExecutions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Executes the Command as a Task
|
||||
/// </summary>
|
||||
/// <returns>The Task to execute</returns>
|
||||
System.Threading.Tasks.Task ExecuteAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Raises the CanExecuteChanged event.
|
||||
/// </summary>
|
||||
void RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
// Inspired by AsyncAwaitBestPractices.MVVM.IAsyncValueCommand: https://github.com/brminnick/AsyncAwaitBestPractices
|
||||
namespace Xamarin.CommunityToolkit.ObjectModel
|
||||
{
|
||||
/// <summary>
|
||||
/// An Async implementation of ICommand for ValueTask
|
||||
/// </summary>
|
||||
public interface IAsyncValueCommand<in TExecute, in TCanExecute> : IAsyncValueCommand<TExecute>
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the command can execute in its current state
|
||||
/// </summary>
|
||||
/// <returns><c>true</c>, if this command can be executed; otherwise, <c>false</c>.</returns>
|
||||
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
|
||||
bool CanExecute(TCanExecute parameter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An Async implementation of ICommand for ValueTask
|
||||
/// </summary>
|
||||
public interface IAsyncValueCommand<in T> : System.Windows.Input.ICommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true when the Command is currently executing. Returns false when the Command is not executing
|
||||
/// </summary>
|
||||
bool IsExecuting { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the Command allows simultaneous executions
|
||||
/// </summary>
|
||||
bool AllowsMultipleExecutions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Executes the Command as a ValueTask
|
||||
/// </summary>
|
||||
/// <returns>The ValueTask to execute</returns>
|
||||
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
|
||||
System.Threading.Tasks.ValueTask ExecuteAsync(T parameter);
|
||||
|
||||
/// <summary>
|
||||
/// Raises the CanExecuteChanged event.
|
||||
/// </summary>
|
||||
void RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An Async implementation of ICommand for ValueTask
|
||||
/// </summary>
|
||||
public interface IAsyncValueCommand : System.Windows.Input.ICommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true when the Command is currently executing. Returns false when the Command is not executing
|
||||
/// </summary>
|
||||
bool IsExecuting { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the Command allows simultaneous executions
|
||||
/// </summary>
|
||||
bool AllowsMultipleExecutions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Executes the Command as a ValueTask
|
||||
/// </summary>
|
||||
/// <returns>The ValueTask to execute</returns>
|
||||
System.Threading.Tasks.ValueTask ExecuteAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Raises the CanExecuteChanged event.
|
||||
/// </summary>
|
||||
void RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.UI.Views
|
||||
|
@ -22,8 +21,7 @@ namespace Xamarin.CommunityToolkit.UI.Views
|
|||
set => SetValue(IsBusyProperty, value);
|
||||
}
|
||||
|
||||
public static readonly BindableProperty IsAvailableProperty = BindableProperty.Create(nameof(IsAvailable), typeof(bool), typeof(CameraView), false,
|
||||
propertyChanged: (b, o, n) => ((CameraView)b).OnAvailable?.Invoke(b, (bool)n));
|
||||
public static readonly BindableProperty IsAvailableProperty = BindableProperty.Create(nameof(IsAvailable), typeof(bool), typeof(CameraView), false, propertyChanged: (b, o, n) => ((CameraView)b).OnAvailable?.Invoke(b, (bool)n));
|
||||
|
||||
public bool IsAvailable
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.CommunityToolkit.Helpers;
|
||||
using Xamarin.Forms;
|
||||
using static System.Math;
|
||||
|
||||
|
@ -12,7 +13,13 @@ namespace Xamarin.CommunityToolkit.UI.Views
|
|||
|
||||
const uint defaultAnimationLength = 250;
|
||||
|
||||
public event EventHandler Tapped;
|
||||
readonly WeakEventManager tappedEventManager = new WeakEventManager();
|
||||
|
||||
public event EventHandler Tapped
|
||||
{
|
||||
add => tappedEventManager.AddEventHandler(value);
|
||||
remove => tappedEventManager.RemoveEventHandler(value);
|
||||
}
|
||||
|
||||
ContentView contentHolder;
|
||||
|
||||
|
@ -208,7 +215,7 @@ namespace Xamarin.CommunityToolkit.UI.Views
|
|||
}
|
||||
IsExpanded = !IsExpanded;
|
||||
Command?.Execute(CommandParameter);
|
||||
Tapped?.Invoke(this, EventArgs.Empty);
|
||||
OnTapped();
|
||||
})
|
||||
};
|
||||
control.Spacing = 0;
|
||||
|
@ -444,5 +451,7 @@ namespace Xamarin.CommunityToolkit.UI.Views
|
|||
State = ExpandState.Expanded;
|
||||
});
|
||||
}
|
||||
|
||||
void OnTapped() => tappedEventManager.RaiseEvent(this, EventArgs.Empty, nameof(Tapped));
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="MSBuild.Sdk.Extras/2.0.54">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard1.0;netstandard2.0;Xamarin.iOS10;MonoAndroid90;MonoAndroid10.0;Xamarin.TVOS10;Xamarin.WatchOS10;Xamarin.Mac20;tizen40</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard1.0;netstandard2.0;netstandard2.1;Xamarin.iOS10;MonoAndroid90;MonoAndroid10.0;Xamarin.TVOS10;Xamarin.WatchOS10;Xamarin.Mac20;tizen40</TargetFrameworks>
|
||||
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);uap10.0.16299</TargetFrameworks>
|
||||
<AssemblyName>Xamarin.CommunityToolkit</AssemblyName>
|
||||
<RootNamespace>Xamarin.CommunityToolkit</RootNamespace>
|
||||
|
@ -66,12 +66,21 @@
|
|||
<None Include="..\LICENSE" PackagePath="" Pack="true" />
|
||||
<None Include="..\assets\XamarinCommunityToolkit_128x128.png" PackagePath="icon.png" Pack="true" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard1.')) ">
|
||||
<ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard1.0')) ">
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
<PackageReference Include="System.ComponentModel" Version="4.3.0" />
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard')) ">
|
||||
<ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard2.0')) ">
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard2.1')) ">
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" $(TargetFramework.StartsWith('uap10.0')) ">
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform" Version="6.2.9" />
|
||||
<Compile Include="**\*.uwp.cs" />
|
||||
<Compile Include="**\*.uwp.*.cs" />
|
||||
|
@ -92,6 +101,10 @@
|
|||
<AndroidResource Include="Resources\**\*.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" $(TargetFramework.StartsWith('MonoAndroid9.0')) ">
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" $(TargetFramework.StartsWith('Xamarin.iOS')) ">
|
||||
<Compile Include="**\*.ios.cs" />
|
||||
<Compile Include="**\*.ios.*.cs" />
|
||||
|
@ -104,6 +117,8 @@
|
|||
|
||||
<ItemGroup Condition=" $(TargetFramework.StartsWith('Xamarin.Mac')) ">
|
||||
<Reference Include="netstandard" Condition=" '$(OS)' == 'Windows_NT' " />
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
|
||||
<Compile Include="**\*.macos.cs" />
|
||||
<Compile Include="**\*.macos.*.cs" />
|
||||
</ItemGroup>
|
||||
|
@ -114,6 +129,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" $(TargetFramework.StartsWith('tizen')) ">
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
|
||||
<Compile Include="**\*.tizen.cs" />
|
||||
<Compile Include="**\*.tizen.*.cs" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.CommunityToolkit.Sample.Models;
|
||||
using Xamarin.Forms;
|
||||
|
||||
|
@ -11,8 +12,8 @@ namespace Xamarin.CommunityToolkit.Sample.Pages
|
|||
|
||||
public Color DetailColor { get; set; }
|
||||
|
||||
public ICommand NavigateCommand => navigateCommand ??= new Command(parameter
|
||||
=> Navigation.PushAsync(PreparePage((SectionModel)parameter)));
|
||||
public ICommand NavigateCommand => navigateCommand ??= new AsyncCommand<SectionModel>(sectionModel
|
||||
=> Navigation.PushAsync(PreparePage(sectionModel)));
|
||||
|
||||
Page PreparePage(SectionModel model)
|
||||
{
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Octokit;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.CommunityToolkit.Sample.Resx;
|
||||
using Xamarin.Essentials;
|
||||
using Xamarin.Forms;
|
||||
|
@ -39,7 +40,7 @@ namespace Xamarin.CommunityToolkit.Sample.ViewModels
|
|||
set => Set(ref emptyViewText, value);
|
||||
}
|
||||
|
||||
public ICommand SelectedContributorCommand => selectedContributorCommand ??= new Command(async () =>
|
||||
public ICommand SelectedContributorCommand => selectedContributorCommand ??= new AsyncCommand(async () =>
|
||||
{
|
||||
if (SelectedContributor is null)
|
||||
return;
|
||||
|
@ -56,9 +57,10 @@ namespace Xamarin.CommunityToolkit.Sample.ViewModels
|
|||
try
|
||||
{
|
||||
var contributors = await gitHubClient.Repository.GetAllContributors("xamarin", "XamarinCommunityToolkit");
|
||||
//Initiate poor mans randomizer for lists
|
||||
//Note: there are better options for real production worthy large lists : https://stackoverflow.com/questions/273313/randomize-a-listt
|
||||
//But for now this linq version will do
|
||||
|
||||
// Initiate poor mans randomizer for lists
|
||||
// Note: there are better options for real production worthy large lists : https://stackoverflow.com/questions/273313/randomize-a-listt
|
||||
// But for now this linq version will do
|
||||
var random = new Random();
|
||||
var result = contributors?.OrderBy(x => random.Next()).ToArray();
|
||||
if (result != null)
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Xamarin.CommunityToolkit.Helpers;
|
||||
|
||||
namespace Xamarin.CommunityToolkit.Sample.ViewModels
|
||||
{
|
||||
public class BaseViewModel : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
readonly WeakEventManager propertyChangedEventManager = new WeakEventManager();
|
||||
|
||||
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
|
||||
{
|
||||
add => propertyChangedEventManager.AddEventHandler(value);
|
||||
remove => propertyChangedEventManager.RemoveEventHandler(value);
|
||||
}
|
||||
|
||||
protected bool Set<T>(ref T backingStore, T value, [CallerMemberName] string name = null)
|
||||
{
|
||||
|
@ -18,7 +25,7 @@ namespace Xamarin.CommunityToolkit.Sample.ViewModels
|
|||
return true;
|
||||
}
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string name = null)
|
||||
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string name = "")
|
||||
=> propertyChangedEventManager.RaiseEvent(this, new PropertyChangedEventArgs(name), nameof(INotifyPropertyChanged.PropertyChanged));
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.CommunityToolkit.Sample.Resx;
|
||||
using Xamarin.Forms;
|
||||
|
||||
|
@ -15,7 +16,7 @@ namespace Xamarin.CommunityToolkit.Sample.ViewModels.Converters
|
|||
new Person() { Id = 3, Name = "Person 3" }
|
||||
};
|
||||
|
||||
public ICommand ItemSelectedCommand { get; private set; } = new Command<Person>(async (person)
|
||||
=> await Application.Current.MainPage.DisplayAlert($"{AppResources.ItemTapped}: ", person.Name, AppResources.Cancel));
|
||||
public ICommand ItemSelectedCommand { get; private set; } = new AsyncCommand<Person>(person
|
||||
=> Application.Current.MainPage.DisplayAlert($"{AppResources.ItemTapped}: ", person.Name, AppResources.Cancel));
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.CommunityToolkit.ObjectModel;
|
||||
using Xamarin.CommunityToolkit.Sample.Resx;
|
||||
using Xamarin.Forms;
|
||||
|
||||
|
@ -15,13 +16,14 @@ namespace Xamarin.CommunityToolkit.Sample.ViewModels.Converters
|
|||
new Person() { Id = 3, Name = "Person 3" }
|
||||
};
|
||||
|
||||
public ICommand ItemTappedCommand { get; private set; } = new Command<Person>(async (person)
|
||||
=> await Application.Current.MainPage.DisplayAlert($"{AppResources.ItemTapped}: ", person.Name, AppResources.Cancel));
|
||||
public ICommand ItemTappedCommand { get; private set; } = new AsyncCommand<Person>(person
|
||||
=> Application.Current.MainPage.DisplayAlert($"{AppResources.ItemTapped}: ", person.Name, AppResources.Cancel));
|
||||
}
|
||||
|
||||
public class Person
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ variables:
|
|||
MONO_VERSION: 6_4_0
|
||||
XCODE_VERSION: 11.4
|
||||
NETCORE_VERSION: '3.1.x'
|
||||
NETCORE_TEST_VERSION: '2.2.x'
|
||||
NETCORE_TEST_VERSION: '3.1.x'
|
||||
RunPoliCheck: 'false'
|
||||
PathToCsproj: 'XamarinCommunityToolkit/Xamarin.CommunityToolkit.csproj'
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче