using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Headless;
using Avalonia.Headless.XUnit;
using Avalonia.Input;
using Avalonia.Threading;
using Avalonia.VisualTree;
using IFSRenderer.AvaloniaShell.Controls;
using Xunit;
using Xunit.Abstractions;
namespace IFSRenderer.AvaloniaShell.Tests;
/// <summary>
/// "double clicking a value on the left highlights the decimal only."
/// </summary>
/// <remarks>
/// <para>
/// The canonical <c>valueEditor_MouseDoubleClick</c> resets the value to its default and then
/// selects the WHOLE number, ready to be typed over. The port reproduced that, but Avalonia's
/// TextBox performs its own word selection from the same second press — and that lands after
/// the handler, leaving one run highlighted ("0" of "1.0").
/// </para>
/// <para>
/// Reproducing it needs REAL pointer input: the word selection comes from the TextBox's own
/// press handling, so raising the DoubleTapped event directly would pass whether or not the
/// bug is present. These tests drive the headless window's mouse.
/// </para>
/// </remarks>
public class ValueEditingTests
{
private readonly ITestOutputHelper _out;
public ValueEditingTests(ITestOutputHelper output) => _out = output;
/// <summary>Centre of a control, in window coordinates.</summary>
private static Point CentreOf(Control control, Window window) =>
control.TranslatePoint(
new Point(control.Bounds.Width / 2, control.Bounds.Height / 2), window) ?? default;
private static void Click(Window window, Point at)
{
window.MouseDown(at, MouseButton.Left);
window.MouseUp(at, MouseButton.Left);
Dispatcher.UIThread.RunJobs();
}
private static (MainWindow Window, ValueSlider Slider) SliderInLaidOutWindow(string name)
{
(MainWindow window, _) = MainWindowHarness.Start();
window.Measure(new Size(1500, 900));
window.Arrange(new Rect(0, 0, 1500, 900));
Dispatcher.UIThread.RunJobs();
var slider = window.FindControl<ValueSlider>(name);
Assert.NotNull(slider);
return (window, slider);
}
[AvaloniaFact]
public void Double_clicking_a_value_selects_the_whole_number()
{
(MainWindow window, ValueSlider slider) = SliderInLaidOutWindow("BrightnessSlider");
var field = slider.GetVisualDescendants().OfType<Border>()
.FirstOrDefault(b => b.Name == "ValueField");
var editor = slider.GetVisualDescendants().OfType<TextBox>()
.FirstOrDefault(t => t.Name == "ValueEditor");
Assert.NotNull(field);
Assert.NotNull(editor);
Point at = CentreOf(field, window);
_out.WriteLine($"field centre in window: {at}, value before: {slider.Value}");
// First click opens the editor, exactly as a user's would.
Click(window, at);
_out.WriteLine($"after 1st click: editing={editor.IsVisible} text='{editor.Text}' " +
$"selected='{editor.SelectedText}'");
// Second click, at the same point and immediately after, is the double-click.
Click(window, at);
Dispatcher.UIThread.RunJobs();
_out.WriteLine($"after 2nd click: editing={editor.IsVisible} text='{editor.Text}' " +
$"selected='{editor.SelectedText}'");
Assert.True(editor.IsVisible, "double-click should leave the value editable");
Assert.False(string.IsNullOrEmpty(editor.Text), "the editor should hold the value");
// The reported symptom: only one run of the number was highlighted.
Assert.Equal(editor.Text, editor.SelectedText);
// And the half of the canonical behaviour that was missing entirely — the handler
// never ran at all, so the value was not reset either.
Assert.Equal(slider.DefaultValue, slider.Value, 6);
}
/// <summary>
/// A single click opens the editor with the value selected, and must NOT reset it —
/// only the double-click resets.
/// </summary>
[AvaloniaFact]
public void A_single_click_opens_the_editor_without_changing_the_value()
{
(MainWindow window, ValueSlider slider) = SliderInLaidOutWindow("BrightnessSlider");
var field = slider.GetVisualDescendants().OfType<Border>()
.FirstOrDefault(b => b.Name == "ValueField");
var editor = slider.GetVisualDescendants().OfType<TextBox>()
.FirstOrDefault(t => t.Name == "ValueEditor");
double before = slider.Value;
Click(window, CentreOf(field, window));
Assert.True(editor.IsVisible, "a click should open the editor");
Assert.Equal(before, slider.Value, 6);
Assert.Equal(editor.Text, editor.SelectedText);
}
}