using System.Collections.ObjectModel;
using System.Linq;
using Xunit;
using ytLive.Models;
namespace ytLive.Tests;
///
/// Preview undo/redo (creator directive 2026-11-01). The contract that matters: one gesture is
/// one undo step, a restored layout is exactly what was there before, and redo survives several
/// undos — because a stack that only works one step deep is worse than none.
///
public class PreviewHistoryTests
{
private static Scene NewScene(params SceneElement[] elements)
{
var scene = new Scene { Name = "Live" };
foreach (var element in elements) scene.Elements.Add(element);
return scene;
}
private static SceneElement NewElement(double x = 0, double y = 0, double w = 100, double h = 100)
=> new Source { Name = "Layer", X = x, Y = y, Width = w, Height = h };
[Fact]
public void Undo_restores_geometry_and_redo_reapplies_it()
{
var element = NewElement(10, 20);
var scenes = new ObservableCollection { NewScene(element) };
var history = new PreviewHistory();
history.Capture(scenes);
element.X = 300;
element.Y = 400;
Assert.True(history.Undo(scenes));
Assert.Equal(10, element.X);
Assert.Equal(20, element.Y);
Assert.True(history.Redo(scenes));
Assert.Equal(300, element.X);
Assert.Equal(400, element.Y);
}
[Fact]
public void Undo_removes_a_layer_added_after_the_snapshot_and_redo_brings_it_back()
{
var first = NewElement();
var scene = NewScene(first);
var scenes = new ObservableCollection { scene };
var history = new PreviewHistory();
history.Capture(scenes);
var added = NewElement();
scene.Elements.Add(added);
history.Undo(scenes);
Assert.Single(scene.Elements);
Assert.DoesNotContain(added, scene.Elements);
history.Redo(scenes);
Assert.Equal(2, scene.Elements.Count);
Assert.Contains(added, scene.Elements);
}
[Fact]
public void Undo_restores_a_removed_layer_in_its_original_position()
{
var top = NewElement();
var middle = NewElement();
var bottom = NewElement();
var scene = NewScene(top, middle, bottom);
var scenes = new ObservableCollection { scene };
var history = new PreviewHistory();
history.Capture(scenes);
scene.Elements.Remove(middle);
history.Undo(scenes);
Assert.Equal(new SceneElement[] { top, middle, bottom }, scene.Elements.ToArray());
}
[Fact]
public void A_new_edit_after_undo_drops_the_redo_branch()
{
var element = NewElement();
var scenes = new ObservableCollection { NewScene(element) };
var history = new PreviewHistory();
history.Capture(scenes);
element.X = 50;
history.Undo(scenes);
Assert.True(history.CanRedo);
history.Capture(scenes);
Assert.False(history.CanRedo);
}
[Fact]
public void The_stack_is_bounded()
{
var scenes = new ObservableCollection { NewScene(NewElement()) };
var history = new PreviewHistory();
for (var i = 0; i < PreviewHistory.MaxDepth + 20; i++) history.Capture(scenes);
Assert.Equal(PreviewHistory.MaxDepth, history.UndoDepth);
}
[Fact]
public void Undo_with_nothing_captured_is_a_no_op()
{
var element = NewElement(5, 5);
var scenes = new ObservableCollection { NewScene(element) };
var history = new PreviewHistory();
Assert.False(history.Undo(scenes));
Assert.Equal(5, element.X);
}
}