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); } }