Browse Source

Prevent toggle controls from changing state when commands cannot execute (#21126)

* Add failing tests for toggle command gating

* Prevent disabled toggle controls from changing state

* Rework toggle command gating tests around pointer input

* Add touch regression for toggle command gating

* Remove synthetic toggle command gating tests
release/12.0.1
Nathan Nguyen 2 months ago
committed by Julien Lebosquain
parent
commit
1479f52e62
  1. 5
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  2. 81
      tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs
  3. 4
      tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs

5
src/Avalonia.Controls/Primitives/ToggleButton.cs

@ -72,6 +72,11 @@ namespace Avalonia.Controls.Primitives
protected override void OnClick()
{
if (!IsEffectivelyEnabled)
{
return;
}
Toggle();
base.OnClick();
}

81
tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs

@ -1,6 +1,8 @@
using System;
using System.Windows.Input;
using Avalonia.Base.UnitTests.Input;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Rendering;
@ -242,6 +244,47 @@ namespace Avalonia.Input.UnitTests
Assert.Equal(3, doubleTappedExecutedTimes);
}
[Fact]
public void ToggleButton_Does_Not_Toggle_When_Command_Becomes_Disabled_Between_TouchBegin_And_TouchEnd()
{
using var app = UnitTestApp(new TimeSpan(200));
var renderer = new Mock<IHitTester>();
var impl = CreateTopLevelImplMock();
var command = new TestCommand(true);
var target = new ToggleButton
{
Width = 100,
Height = 100,
Command = command,
};
var root = CreateInputRoot(impl.Object, target, renderer.Object);
var device = new TouchDevice();
var touchBegin = new RawPointerEventArgs(device, 0, root.PresentationSource, RawPointerEventType.TouchBegin, new Point(50, 50), RawInputModifiers.None)
{
RawPointerId = 1
};
var touchEnd = new RawPointerEventArgs(device, 1, root.PresentationSource, RawPointerEventType.TouchEnd, new Point(50, 50), RawInputModifiers.None)
{
RawPointerId = 1
};
SetHit(renderer, target);
impl.Object.Input!(touchBegin);
Assert.True(target.IsPressed);
Assert.False(target.IsChecked ?? false);
command.IsEnabled = false;
Assert.False(target.IsEffectivelyEnabled);
impl.Object.Input!(touchEnd);
Assert.False(target.IsChecked ?? false);
}
private IDisposable UnitTestApp(TimeSpan doubleClickTime = new TimeSpan())
{
var unitTestApp = UnitTestApplication.Start(
@ -301,6 +344,44 @@ namespace Avalonia.Input.UnitTests
});
}
private sealed class TestCommand : ICommand
{
private bool _enabled;
private EventHandler? _canExecuteChanged;
public TestCommand(bool enabled)
{
_enabled = enabled;
}
public bool IsEnabled
{
get => _enabled;
set
{
if (_enabled == value)
{
return;
}
_enabled = value;
_canExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
public event EventHandler? CanExecuteChanged
{
add => _canExecuteChanged += value;
remove => _canExecuteChanged -= value;
}
public bool CanExecute(object? parameter) => _enabled;
public void Execute(object? parameter)
{
}
}
private class TestTopLevel(ITopLevelImpl impl) : TopLevel(impl)
{

4
tests/Avalonia.Controls.UnitTests/Primitives/ToggleButtonTests.cs

@ -1,4 +1,6 @@
using Avalonia.Data;
using System;
using Avalonia.Controls.UnitTests.Utils;
using Avalonia.Data;
using Avalonia.UnitTests;
using Xunit;

Loading…
Cancel
Save