* 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:
Yann Zahringer Ferrando 2020-09-19 00:47:54 +02:00 коммит произвёл GitHub
Родитель 0e93d82cc8
Коммит 23baecafbd
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
38 изменённых файлов: 3892 добавлений и 23 удалений

Просмотреть файл

@ -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'