Skip to content

Commit

Permalink
Observable related feature (#22)
Browse files Browse the repository at this point in the history
* feat: add command and observable base and tests.

* feat: remove warnings.

* feat: implement INPChanging.
  • Loading branch information
rabbitism authored Jul 30, 2024
1 parent 210f7c2 commit 6c474cd
Show file tree
Hide file tree
Showing 16 changed files with 429 additions and 26 deletions.
3 changes: 0 additions & 3 deletions sample/Sample/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System.Linq;
using Avalonia.Controls;
using Avalonia.Media;
using Irihi.Avalonia.Shared.Helpers;

namespace Sample;

Expand Down
42 changes: 42 additions & 0 deletions src/Irihi.Avalonia.Shared.Public/Common/IRIHI_CommandBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace Irihi.Avalonia.Shared.Common;

/// <summary>
/// This is a ICommand implementation for internal use, to avoid irrelevant dependencies.
/// This should not be used by your application.
/// </summary>
public class IRIHI_CommandBase: ICommand
{
private readonly Action _execute;
private readonly Func<bool>? _canExecute;
public event EventHandler? CanExecuteChanged;

public IRIHI_CommandBase(Action execute)
{
_execute = execute;
}

public IRIHI_CommandBase(Action execute, Func<bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanExecute(object? parameter)
{
return _canExecute is null || _canExecute.Invoke();
}

public void Execute(object? parameter)
{
_execute.Invoke();
}

public void NotifyCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
81 changes: 81 additions & 0 deletions src/Irihi.Avalonia.Shared.Public/Common/IRIHI_CommandBase`T.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace Irihi.Avalonia.Shared.Common;

/// <summary>
/// This is a ICommand implementation for internal use, to avoid irrelevant dependencies.
/// This should not be used by your application.
/// </summary>
public class IRIHI_CommandBase<T>: ICommand
{
private readonly Action<T?> _execute;
private readonly Predicate<T?>? _canExecute;
public event EventHandler? CanExecuteChanged;

public IRIHI_CommandBase(Action<T?> execute)
{
_execute = execute;
}

public IRIHI_CommandBase(Action<T?> execute, Predicate<T?> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanExecute(T? parameter)
{
return _canExecute is null || _canExecute.Invoke(parameter);
}

public bool CanExecute(object? parameter)
{
if (!TryGetCommandArgument(parameter, out var result))
{
throw new ArgumentException();
}
return CanExecute(result);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Execute(T? parameter)
{
_execute(parameter);
}

public void Execute(object? parameter)
{
if (!TryGetCommandArgument(parameter, out var result))
{
throw new ArgumentException();
}
Execute(result);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool TryGetCommandArgument(object? parameter, out T? result)
{
if (parameter is null && default(T) is null)
{
result = default;
return true;
}
if (parameter is T obj)
{
result = obj;
return true;
}
result = default;
return false;
}

public void NotifyCanExecuteChanged()
{
EventHandler? canExecuteChanged = this.CanExecuteChanged;
if (canExecuteChanged is null)
return;
canExecuteChanged(this, EventArgs.Empty);
}
}
45 changes: 45 additions & 0 deletions src/Irihi.Avalonia.Shared.Public/Common/IRIHI_ObservableBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace Irihi.Avalonia.Shared.Common;

/// <summary>
/// This is a INotifyPropertyChanged and INotifyPropertyChanging implementation for internal use, to avoid irrelevant
/// dependencies.
/// This should not be used by your application.
/// </summary>
public class IRIHI_ObservableBase : INotifyPropertyChanged, INotifyPropertyChanging
{
public event PropertyChangedEventHandler? PropertyChanged;
public event PropertyChangingEventHandler? PropertyChanging;

protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}

protected virtual void OnPropertyChanging(PropertyChangingEventArgs e)
{
PropertyChanging?.Invoke(this, e);
}

protected void OnPropertyChanging([CallerMemberName] string? propertyName = null)
{
OnPropertyChanging(new PropertyChangingEventArgs(propertyName));
}

protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}

protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
return false;
OnPropertyChanging(propertyName);
field = newValue;
OnPropertyChanged(propertyName);
return true;
}
}
3 changes: 0 additions & 3 deletions src/Irihi.Avalonia.Shared.Public/Helpers/MathHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
using Avalonia.Controls.Platform;
using Avalonia.Utilities;

namespace Irihi.Avalonia.Shared.Helpers;

public static class MathHelpers
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Reactive;
using Avalonia.Reactive;

namespace Irihi.Avalonia.Shared.Helpers;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
<Import_RootNamespace>Irihi.Avalonia.Shared.ShareProject</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Common\IRIHI_CommandBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Common\IRIHI_CommandBase`T.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Common\IRIHI_ObservableBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Common\PartNames.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Common\PseudoClassName.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Contracts\IClearControl.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
namespace Irihi.Avalonia.Shared.Common.Tests;

public class IRIHI_CommandBaseGenericTests
{
[Fact]
public void ExecuteInvokesActionWithParameter()
{
int executedParam = 0;
var command = new IRIHI_CommandBase<int>(param => executedParam = param);

command.Execute(5);

Assert.Equal(5, executedParam);
}

[Fact]
public void CanExecuteReturnsTrueByDefault()
{
var command = new IRIHI_CommandBase<object>(_ => { });

bool canExecute = command.CanExecute(null);

Assert.True(canExecute);
}

[Fact]
public void CanExecuteReturnsFalseWhenPredicateSpecifiedAndReturnsFalse()
{
var command = new IRIHI_CommandBase<int>(_ => { }, _ => false);

bool canExecute = command.CanExecute(0);

Assert.False(canExecute);
}

[Fact]
public void CanExecuteThrowsArgumentExceptionForInvalidParameterType()
{
var command = new IRIHI_CommandBase<int>(_ => { });

Assert.Throws<ArgumentException>(() => command.CanExecute("invalid"));
}

[Fact]
public void ExecuteThrowsArgumentExceptionForInvalidParameterType()
{
var command = new IRIHI_CommandBase<int>(_ => { });

Assert.Throws<ArgumentException>(() => command.Execute("invalid"));
}

[Fact]
public void NotifyCanExecuteChangedRaisesEvent()
{
var command = new IRIHI_CommandBase<object>(_ => { });
bool eventRaised = false;
command.CanExecuteChanged += (_, _) => eventRaised = true;

command.NotifyCanExecuteChanged();

Assert.True(eventRaised);
}

[Fact]
public void CanExecuteWithValidParameterTypeReturnsTrue()
{
var command = new IRIHI_CommandBase<int>(_ => { }, _ => true);
bool canExecute = command.CanExecute(1);
Assert.True(canExecute);
}

[Fact]
public void ExecuteWithValidParameterInvokesAction()
{
int executedParam = 0;
var command = new IRIHI_CommandBase<int>(param => executedParam = param);
command.Execute(10);
Assert.Equal(10, executedParam);
}

[Fact]
public void UnsubscribingFromCanExecuteChangedEventWorks()
{
var command = new IRIHI_CommandBase<object>(_ => { });
bool eventRaised = false;

EventHandler handler = (_, _) => eventRaised = true;
command.CanExecuteChanged += handler;
command.CanExecuteChanged -= handler;

command.NotifyCanExecuteChanged();
Assert.False(eventRaised);
}

[Fact]
public void CanExecuteWithRightTypeReturnsTrue()
{
object o = 1;
var command = new IRIHI_CommandBase<int>(_ => { });
bool canExecute = command.CanExecute(o);
Assert.True(canExecute);
}

[Fact]
public void ExecuteWithRightTypeInvokesAction()
{
object o = 1;
int executedParam = 0;
var command = new IRIHI_CommandBase<int>(param => executedParam = param);
command.Execute(o);
Assert.Equal(1, executedParam);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace Irihi.Avalonia.Shared.Common.Tests;

public class IRIHI_CommandBaseTests
{
[Fact]
public void ExecuteInvokesAction()
{
bool executed = false;
var command = new IRIHI_CommandBase(() => executed = true);

command.Execute(null);

Assert.True(executed);
}

[Fact]
public void CanExecuteReturnsTrueByDefault()
{
var command = new IRIHI_CommandBase(() => { });

bool canExecute = command.CanExecute(null);

Assert.True(canExecute);
}

[Fact]
public void CanExecuteReturnsFalseWhenSpecified()
{
var command = new IRIHI_CommandBase(() => { }, () => false);

bool canExecute = command.CanExecute(null);

Assert.False(canExecute);
}

[Fact]
public void NotifyCanExecuteChangedRaisesEvent()
{
var command = new IRIHI_CommandBase(() => { });
bool eventRaised = false;
command.CanExecuteChanged += (_, _) => eventRaised = true;

command.NotifyCanExecuteChanged();

Assert.True(eventRaised);
}
}
Loading

0 comments on commit 6c474cd

Please sign in to comment.