Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions src/DiffEngineViewer.Tests/ReEnqueueTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/// <summary>
/// A patch arriving for the entry already on screen. A continuous test runner sends one every few
/// seconds for as long as the test keeps failing, and most of them say exactly what the last one
/// said.
/// </summary>
public class ReEnqueueTests
{
[Test]
public async Task An_identical_re_send_leaves_the_scroll_alone()
{
var state = Scrolled();

var again = ViewerSession.EnqueueInline(state, Patch(Fixtures.Long(true)));

await Assert.That(again.Queue[0]).IsSameReferenceAs(state.Queue[0]);
await Assert.That(again.ScrollTop).IsEqualTo(state.ScrollTop);
}

/// <summary>
/// A re-send that says something else is a new comparison, and that one does start at the top.
/// </summary>
[Test]
public async Task A_re_send_of_different_content_starts_at_the_top()
{
var state = Scrolled();

var again = ViewerSession.EnqueueInline(state, Patch($"{Fixtures.Long(true)}\nand one more line"));

await Assert.That(again.ScrollTop).IsEqualTo(0);
}

static SessionState Scrolled()
{
var state = ViewerSession.Apply(Fixtures.Inline(Patch(Fixtures.Long(true))), CommandKind.PageDown);
if (state.ScrollTop == 0)
{
throw new("The entry did not scroll, so nothing below asserts anything.");
}

return state;
}

static InlinePatch Patch(string content) =>
Fixtures.Patch("A.cs", 1, null, content);
}
26 changes: 18 additions & 8 deletions src/DiffEngineViewer/ViewerSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,29 @@ public static SessionState Resize(SessionState state, int columns, int rows) =>
public static SessionState EnqueueInline(SessionState state, InlinePatch patch)
{
var key = InlineKey.For(patch.SourceFile, patch.LineHint);
var replacedCurrent = state.Current?.Key == key;
var current = state.Current;
var queue = Rebuild(state, Pending(state).Enqueue(patch));
// Grouping can reorder the list, so the selection follows its key rather than its index.
var currentKey = state.Current?.Key;
var selected = currentKey is null ? 0 : IndexOf(queue, currentKey);
var selected = current is null ? 0 : IndexOf(queue, current.Key);
if (selected < 0)
{
selected = 0;
}

// Start the reader at the top again only when the text under them changed. Folding into an
// entry further down the list is not it, and neither is a re-send of what is already
// there: Fold reports an identical patch as unchanged and Project hands back the same
// entry, so a continuous runner re-sending the same failing snapshot every few seconds
// used to bounce the reader to the top on every run.
var replaced = current is not null &&
current.Key == key &&
!ReferenceEquals(queue[selected], current);

return Clamp(state with
{
Queue = queue,
Selected = selected < 0 ? 0 : selected,
// The text under the reader just changed, so start it at the top again. Only when it
// is the item on screen; folding into one further down the list should not move
// anything.
ScrollTop = replacedCurrent ? 0 : state.ScrollTop,
Selected = selected,
ScrollTop = replaced ? 0 : state.ScrollTop,
// The open menu indexes the queue it was opened over, which just changed.
Menu = null
});
Expand Down
Loading