diff --git a/src/DiffEngineViewer.Tests/ReselectTests.cs b/src/DiffEngineViewer.Tests/ReselectTests.cs new file mode 100644 index 00000000..1556a4cc --- /dev/null +++ b/src/DiffEngineViewer.Tests/ReselectTests.cs @@ -0,0 +1,73 @@ +/// +/// Selecting the entry that is already selected. A left click on the highlighted row, a right +/// click to open its menu, and a focus naming it all arrive as a selection, and the reader is part +/// way down a comparison while they do. +/// +public class ReselectTests +{ + [Test] + public async Task Opening_the_menu_on_the_entry_being_read_keeps_the_scroll() + { + var state = Scrolled(); + var row = VisibleRowOf(state, state.Selected); + + var opened = ViewerSession.OpenMenu(state, row); + + await Assert.That(opened.Menu).IsNotNull(); + await Assert.That(opened.ScrollTop).IsEqualTo(state.ScrollTop); + } + + [Test] + public async Task Focusing_the_entry_being_read_keeps_the_scroll() + { + var state = Scrolled(); + + var focused = ViewerSession.SelectKey(state, state.Current!.Key); + + await Assert.That(focused.ScrollTop).IsEqualTo(state.ScrollTop); + } + + /// + /// A different entry is a different comparison, so that one does start at the top. + /// + [Test] + public async Task Selecting_another_entry_starts_at_its_top() + { + var state = Scrolled(); + + var selected = ViewerSession.SelectKey(state, state.Queue[0].Key); + + await Assert.That(selected.Current!.Key).IsEqualTo(state.Queue[0].Key); + await Assert.That(selected.ScrollTop).IsEqualTo(0); + } + + static SessionState Scrolled() + { + var state = Fixtures.Inline( + Fixtures.Patch("A.cs", 1, null, Fixtures.Long(true)), + Fixtures.Patch("B.cs", 2, null, Fixtures.Long(true))); + state = ViewerSession.SelectKey(state, state.Queue[1].Key); + state = ViewerSession.Apply(state, CommandKind.PageDown); + if (state.ScrollTop == 0) + { + throw new("The entry did not scroll, so nothing below asserts anything."); + } + + return state; + } + + static int VisibleRowOf(SessionState state, int entry) + { + var visible = QueueProjection.Visible(state, ScreenBuilder.BodyRows(state), out _); + for (var index = 0; index < visible.Count; index++) + { + if (visible[index].Kind == QueueRowKind.Entry && + visible[index].EntryIndex == entry) + { + return index; + } + } + + throw new($"Entry {entry} is not on screen."); + } +} diff --git a/src/DiffEngineViewer/ViewerSession.cs b/src/DiffEngineViewer/ViewerSession.cs index d59969e7..671441af 100644 --- a/src/DiffEngineViewer/ViewerSession.cs +++ b/src/DiffEngineViewer/ViewerSession.cs @@ -890,6 +890,14 @@ static SessionState Select(SessionState state, int index) return state; } + // Already the entry on screen. Selecting it is what a click on it does, what a right click + // opening its menu does, and what a focus naming it does, and none of those asks to be + // taken back to the top of what is being read. + if (index == state.Selected) + { + return Clamp(state); + } + return Clamp(state with { Selected = index,