OverviewFilesCommitsIssuesDocsReleasesPeers

ifsrenderer-linux / AvaloniaShell.Tests / AnimationViewportRefreshTests.cs

77 lines · csharp ref:
using System;
using System.Linq;

using Avalonia;
using Avalonia.Controls;
using Avalonia.Headless.XUnit;
using Avalonia.LogicalTree;

using IFSEngine.Animation;

using IFSRenderer.AvaloniaShell.Tests.Fakes;

using Xunit;

namespace IFSRenderer.AvaloniaShell.Tests;

/// <summary>
/// Editing a keyframe has to reach the viewport.
/// </summary>
/// <remarks>
/// <para>
/// "if you edit a keyframe there it won't update the viewport until you change the frame."
/// </para>
/// <para>
/// The same ORDERING bug this harness was built for, in a third place. <c>TimeChanged</c>
/// re-evaluated the dopesheet onto the fractal and invalidated the params buffer;
/// <c>Changed</c> - which every keyframe edit raises - pushed the panel state and stopped. The
/// fractal kept the values the last <c>EvaluateAt</c> had written, so the edit became visible
/// only when the playhead next moved and re-evaluated for its own reasons.
/// </para>
/// <para>
/// Asserted through the queue rather than by reading the fractal: the point is that the engine
/// was asked to re-render, and <see cref="FakeRenderQueue"/> defers exactly as the real service
/// does, so a fix that merely mutated the model on the UI thread would not pass.
/// </para>
/// </remarks>
public class AnimationViewportRefreshTests
{
    [AvaloniaFact]
    public void Editing_a_keyframe_re_evaluates_without_moving_the_playhead()
    {
        (MainWindow window, FakeRenderQueue queue) = MainWindowHarness.Start();

        // By name, and through the LOGICAL tree: the animation panel lives in a tab that the
        // headless run never realizes, so it has no visual tree to walk.
        AnimationPanel panel = window.FindControl<AnimationPanel>("AnimationView");
        Assert.NotNull(panel);
        Dopesheet sheet = queue.Current.Dopesheet;

        // Two keys on one channel, made the way the ◇ diamond makes them.
        foreach ((double t, double v) in new[] { (0.0, 1.0), (4.0, 5.0) })
            sheet.AddOrUpdateChannel("Brightness", "Brightness",
                TimeOnly.FromTimeSpan(TimeSpan.FromSeconds(t)), v);
        panel.SetFractal(queue.Current);
        panel.ReloadChannels();
        MainWindowHarness.Settle(queue);

        CurveCanvas curve = panel.GetLogicalDescendants().OfType<CurveCanvas>().Single();
        Keyframe key = sheet.Channels["Brightness"].Keyframes[0];
        curve.BeginDrag(key, new Point(0, 0));

        queue.Renderer.Calls.Clear();
        int postsBefore = queue.PostCount;

        // The edit, with the playhead left exactly where it was.
        key.Value = 42;
        curve.EndDrag();
        MainWindowHarness.Settle(queue);

        Assert.True(queue.PostCount > postsBefore,
            "editing a keyframe queued no engine work at all");
        Assert.Contains(nameof(FakeRenderer.InvalidateParamsBuffer), queue.Renderer.Calls);
        // The evaluation ran, so the fractal now carries the edited value.
        Assert.Equal(42, queue.Current.Brightness, 6);
    }
}