OverviewFilesCommitsIssuesDocsReleasesPeers

ifsrenderer-linux / AvaloniaShell.Tests / ValueEditingTests.cs

123 lines · csharp ref:
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);
    }
}