Skip to content

Commit

Permalink
gitextensions#5125 RevisionGrid Graph: Nearest branch in tooltip (3b …
Browse files Browse the repository at this point in the history
…of 3a-c)

Extract new interface IRevisionGraphRow
Adapt LaneNodeLocatorTests
  • Loading branch information
mstv committed Oct 31, 2018
1 parent af07056 commit c61b7a5
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 128 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ private Brush GetBrushForRevision(RevisionGraphRevision revisionGraphRevision, b
return brush;
}

private static int GetLaneForRow(RevisionGraphRow row, RevisionGraphSegment revisionGraphRevision)
private static int GetLaneForRow(IRevisionGraphRow row, RevisionGraphSegment revisionGraphRevision)
{
if (row != null)
{
Expand Down
46 changes: 27 additions & 19 deletions GitUI/UserControls/RevisionGrid/Graph/LaneNodeLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,45 @@ internal sealed class LaneNodeLocator : ILaneNodeLocator
{
private readonly IRevisionGraphRowProvider _revisionGraphRowProvider;

public LaneNodeLocator(IRevisionGraphRowProvider laneRowProvider)
public LaneNodeLocator(IRevisionGraphRowProvider revisionGraphRowProvider)
{
_revisionGraphRowProvider = laneRowProvider;
_revisionGraphRowProvider = revisionGraphRowProvider;
}

public (RevisionGraphRevision, bool isAtNode) FindPrevNode(int rowIndex, int lane)
{
var row = _revisionGraphRowProvider.GetSegmentsForRow(rowIndex);
if (row != null)
if (rowIndex < 0 || lane < 0)
{
if (row.GetCurrentRevisionLane() == lane)
// as unlikely as it may be...
// don't throw, just pretend we couldn't find it
}
else
{
IRevisionGraphRow row = _revisionGraphRowProvider.GetSegmentsForRow(rowIndex);
if (row != null)
{
return (row.Revision, isAtNode: true);
}
if (row.GetCurrentRevisionLane() == lane)
{
return (row.Revision, isAtNode: true);
}

var segmentsForLane = row.GetSegmentsForIndex(lane);
if (segmentsForLane.Count() > 0)
{
#if DEBUG
var firstParent = segmentsForLane.First().Parent;
foreach (var segment in segmentsForLane)
var segmentsForLane = row.GetSegmentsForIndex(lane);
if (segmentsForLane.Count() > 0)
{
if (segment.Parent != firstParent)
#if DEBUG
var firstParent = segmentsForLane.First().Parent;
foreach (var segment in segmentsForLane)
{
throw new System.Exception(string.Format("All segments for a lane should have the same parent.\n"
+ "Not fulfilled for rowIndex {0} lane {1} with {2} segments.",
rowIndex, lane, segmentsForLane.Count()));
if (segment.Parent != firstParent)
{
throw new System.Exception(string.Format("All segments for a lane should have the same parent.\n"
+ "Not fulfilled for rowIndex {0} lane {1} with {2} segments.",
rowIndex, lane, segmentsForLane.Count()));
}
}
}
#endif
return (segmentsForLane.First().Parent, isAtNode: false);
return (segmentsForLane.First().Parent, isAtNode: false);
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions GitUI/UserControls/RevisionGrid/Graph/RevisionGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace GitUI.UserControls.RevisionGrid.Graph
{
internal interface IRevisionGraphRowProvider
{
RevisionGraphRow GetSegmentsForRow(int row);
IRevisionGraphRow GetSegmentsForRow(int row);
}

// The RevisionGraph contains all the basic structures needed to render the graph.
Expand Down Expand Up @@ -178,7 +178,7 @@ public RevisionGraphRevision GetNodeForRow(int row)
return _orderedNodesCache.ElementAt(row);
}

public RevisionGraphRow GetSegmentsForRow(int row)
public IRevisionGraphRow GetSegmentsForRow(int row)
{
if (_orderedRowCache == null || row >= _orderedRowCache.Count)
{
Expand Down
12 changes: 11 additions & 1 deletion GitUI/UserControls/RevisionGrid/Graph/RevisionGraphRow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@

namespace GitUI.UserControls.RevisionGrid.Graph
{
public interface IRevisionGraphRow
{
RevisionGraphRevision Revision { get; }
IReadOnlyList<RevisionGraphSegment> Segments { get; }
int GetCurrentRevisionLane();
int GetLaneCount();
IEnumerable<RevisionGraphSegment> GetSegmentsForIndex(int index);
int GetLaneIndexForSegment(RevisionGraphSegment revisionGraphRevision);
}

// The RevisionGraphRow contains an ordered list of Segments that crosses the row or connects to the revision in the row.
// The segments can be returned in the order how it is stored.
// Segments are not the same as lanes.A crossing segment is a lane, but multiple segments can connect to the revision.
// Therefor, a single lane can have multiple segments.
public class RevisionGraphRow
public class RevisionGraphRow : IRevisionGraphRow
{
public RevisionGraphRow(RevisionGraphRevision revision, IReadOnlyList<RevisionGraphSegment> segments)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public class LaneInfoProviderTests
"yyy", "zz.z"
};

#if false
private Node _artificialCommitNode;
private Node _realCommitNode;
private Node _mergeCommitNode;
Expand Down Expand Up @@ -465,5 +466,6 @@ public void GetLaneInfo_should_not_display_a_branch_if_none_to_detect()
Environment.NewLine,
_realCommitNode.Revision.Body));
}
#endif
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FluentAssertions;
using System.Collections.Generic;
using FluentAssertions;
using GitUI.UserControls.RevisionGrid.Graph;
using NSubstitute;
using NUnit.Framework;
Expand All @@ -8,51 +9,64 @@ namespace GitUITests.UserControls.RevisionGrid.Graph
[TestFixture]
public class LaneNodeLocatorTests
{
private ILaneRowProvider _laneRowProvider;
private IRevisionGraphRowProvider _revisionGraphRowProvider;
private LaneNodeLocator _laneNodeLocator;

[SetUp]
public void Setup()
{
_laneRowProvider = Substitute.For<ILaneRowProvider>();
_laneNodeLocator = new LaneNodeLocator(_laneRowProvider);
_revisionGraphRowProvider = Substitute.For<IRevisionGraphRowProvider>();
_laneNodeLocator = new LaneNodeLocator(_revisionGraphRowProvider);
}

private Node SetupLaneRow(int row, int laneCount, int nodeLane = -1)
private RevisionGraphRevision SetupLaneRow(int row, int lane, int laneCount, int nodeLane = -1, RevisionGraphSegment firstSegment = null)
{
var node = new Node(GitUIPluginInterfaces.ObjectId.WorkTreeId);
var laneRow = Substitute.For<ILaneRow>();
laneRow.Count.Returns(laneCount);
laneRow.NodeLane.Returns(nodeLane);
laneRow.Node.Returns(node);
_laneRowProvider.GetLaneRow(row).Returns(x => laneRow);
var node = new RevisionGraphRevision(GitUIPluginInterfaces.ObjectId.WorkTreeId, 0);
var revisionGraphRow = Substitute.For<IRevisionGraphRow>();

var segments = new List<RevisionGraphSegment>();
if (firstSegment != null)
{
segments.Add(firstSegment);
}

if (lane < laneCount)
{
revisionGraphRow.GetSegmentsForIndex(lane).Returns(segments);
}

revisionGraphRow.GetCurrentRevisionLane().Returns(nodeLane);
if (lane == nodeLane)
{
revisionGraphRow.Revision.Returns(node);
}
else
{
segments.Add(new RevisionGraphSegment(node, null));
}

_revisionGraphRowProvider.GetSegmentsForRow(row).Returns(x => revisionGraphRow);
return node;
}

[Test]
public void FindPrevNode_should_return_null_if_x_negative()
public void FindPrevNode_should_return_null_if_lane_negative()
{
_laneNodeLocator.FindPrevNode(-1, 0, 1).Should().Be((null, false));
_laneNodeLocator.FindPrevNode(0, -1).Should().Be((null, false));
}

[Test]
public void FindPrevNode_should_return_null_if_rowIndex_negative()
{
_laneNodeLocator.FindPrevNode(0, -1, 1).Should().Be((null, false));
}

[Test]
public void FindPrevNode_should_return_null_if_laneWidth_less_than_one()
{
_laneNodeLocator.FindPrevNode(0, 0, 0).Should().Be((null, false));
_laneNodeLocator.FindPrevNode(-1, 0).Should().Be((null, false));
}

[Test]
public void FindPrevNode_should_return_null_if_model_does_not_have_row()
{
const int row = 100;
_laneRowProvider.GetLaneRow(row).Returns(x => null);
_laneNodeLocator.FindPrevNode(0, row, 1).Should().Be((null, false));
_revisionGraphRowProvider.GetSegmentsForRow(row).Returns(x => null);
_laneNodeLocator.FindPrevNode(row, 0).Should().Be((null, false));
}

[Test]
Expand All @@ -68,121 +82,76 @@ public void FindPrevNode_should_return_the_node_if_it_is_at_the_node_lane_althou
{
const int row = 100;
const int lane = 0;
const int width = 5;
var node = SetupLaneRow(row, laneCount: 0, lane);
var node = SetupLaneRow(row, lane, laneCount: 0, nodeLane: lane);

// lane == laneRow.NodeLane
_laneNodeLocator.FindPrevNode(lane * width, row, width).Should().Be((node, true));
// row.GetCurrentRevisionLane() == lane
_laneNodeLocator.FindPrevNode(row, lane).Should().Be((node, true));
}

[Test]
public void FindPrevNode_should_return_null_if_lane_exceeds_lane_count_and_lane_is_not_the_node_lane()
{
const int row = 100;
const int lane = 10;
const int width = 5;
SetupLaneRow(row, laneCount: lane);
SetupLaneRow(row, lane, laneCount: lane);

// lane >= _laneRowProvider.GetLaneRow(rowIndex).Count
_laneNodeLocator.FindPrevNode(lane * width, row, width).Should().Be((null, false));
// lane >= _revisionGraphRowProvider.GetSegmentsForRow(rowIndex).Count
_laneNodeLocator.FindPrevNode(row, lane).Should().Be((null, false));
}

[Test]
public void FindPrevNode_should_return_null_if_there_is_no_lane_info()
{
const int row = 100;
const int lane = 3;
const int width = 5;
SetupLaneRow(row, laneCount: lane + 1);
_laneRowProvider.GetLaneRow(row).LaneInfoCount(lane).Returns(x => 0);
SetupLaneRow(row, lane, laneCount: lane + 1);
_revisionGraphRowProvider.GetSegmentsForRow(row).GetSegmentsForIndex(lane).Returns(x => new List<RevisionGraphSegment>());

// empty loop "for (laneInfoIndex)"
_laneNodeLocator.FindPrevNode(lane * width, row, width).Should().Be((null, false));
// segmentsForLane.Count() <= 0
_laneNodeLocator.FindPrevNode(row, lane).Should().Be((null, false));
}

[Test]
public void FindPrevNode_should_return_null_if_the_junction_is_empty()
{
// const int row = 100;
// const int lane = 3;
// const int width = 5;

// empty loop "for (nodeIndex)"
// not testable since Junction cannot be mocked up without nodes
// _laneNodeLocator.FindPrevNode(lane * width, row, width).Should().Be((null, false));
}

[Test]
public void FindPrevNode_should_return_the_first_junction_node_of_the_first_LaneInfo()
public void FindPrevNode_should_return_the_parent_node_of_the_single_segment()
{
const int row = 100;
const int lane = 3;
const int width = 5;
var laneNode = SetupLaneRow(row, laneCount: lane + 1);
var junctionNode0 = new Node(GitUIPluginInterfaces.ObjectId.WorkTreeId);
var junctionNode1 = new Node(GitUIPluginInterfaces.ObjectId.WorkTreeId);
var junction = new Junction(junctionNode0, junctionNode1);
var laneInfo = new LaneInfo(lane, junction);
var laneRow = _laneRowProvider.GetLaneRow(row);
laneRow.LaneInfoCount(lane).Returns(x => 2);
laneRow[lane, 0].Returns(x => laneInfo);

// match in the first iteration of the loop "for (laneInfoIndex)"
// match in the first iteration of the loop "for (nodeIndex)"
_laneNodeLocator.FindPrevNode(lane * width, row, width).Should().Be((junctionNode0, false));
}
var laneNode = SetupLaneRow(row, lane, laneCount: lane + 1);

[Test]
public void FindPrevNode_should_return_the_second_junction_node_of_the_first_LaneInfo()
{
const int row = 100;
const int lane = 3;
const int width = 5;
var laneNode = SetupLaneRow(row, laneCount: lane + 1);
var junctionNode0 = new Node(GitUIPluginInterfaces.ObjectId.WorkTreeId)
{
Index = row
};
var junctionNode1 = new Node(GitUIPluginInterfaces.ObjectId.WorkTreeId);
var junction = new Junction(junctionNode0, junctionNode1);
var laneInfo = new LaneInfo(lane, junction);
var laneRow = _laneRowProvider.GetLaneRow(row);
laneRow.LaneInfoCount(lane).Returns(x => 2);
laneRow[lane, 0].Returns(x => laneInfo);

// match in the first iteration of the loop "for (laneInfoIndex)"
// match in the second iteration of the loop "for (nodeIndex)"
_laneNodeLocator.FindPrevNode(lane * width, row, width).Should().Be((junctionNode1, false));
// innermost "return"
_laneNodeLocator.FindPrevNode(row, lane).Should().Be((laneNode, false));
}

[Test]
public void FindPrevNode_should_return_the_first_junction_node_of_the_second_LaneInfo()
public void FindPrevNode_should_return_the_parent_node_of_the_first_segment_and_throw_in_debug_build()
{
const int row = 100;
const int lane = 3;
const int width = 5;
var laneNode = SetupLaneRow(row, laneCount: lane + 1);
var junctionNode0 = new Node(GitUIPluginInterfaces.ObjectId.WorkTreeId)
var parentNode = new RevisionGraphRevision(GitUIPluginInterfaces.ObjectId.WorkTreeId, 0);
var childNode = new RevisionGraphRevision(GitUIPluginInterfaces.ObjectId.WorkTreeId, 0);
var segment = new RevisionGraphSegment(parentNode, childNode);
var laneNode = SetupLaneRow(row, lane, laneCount: lane + 1, firstSegment: segment);

try
{
// innermost "return"
_laneNodeLocator.FindPrevNode(row, lane).Should().Be((parentNode, false));
#if DEBUG
throw new AssertionException("the debug build should throw an exception");
}
catch (System.Exception x)
{
Index = row
};
var junctionNode1 = new Node(GitUIPluginInterfaces.ObjectId.WorkTreeId)
x.Message.Should().Be(string.Format("All segments for a lane should have the same parent.\n"
+ "Not fulfilled for rowIndex {0} lane {1} with {2} segments.",
row, lane, 2));
}
#else
}
catch
{
Index = row
};
var junctionNode2 = new Node(GitUIPluginInterfaces.ObjectId.WorkTreeId);
var junction0 = new Junction(junctionNode0, junctionNode1);
var junction1 = new Junction(junctionNode2);
var laneInfo0 = new LaneInfo(lane, junction0);
var laneInfo1 = new LaneInfo(lane, junction1);
var laneRow = _laneRowProvider.GetLaneRow(row);
laneRow.LaneInfoCount(lane).Returns(x => 2);
laneRow[lane, 0].Returns(x => laneInfo0);
laneRow[lane, 1].Returns(x => laneInfo1);

// match in the second iteration of the loop "for (laneInfoIndex)"
// match in the first iteration of the loop "for (nodeIndex)"
_laneNodeLocator.FindPrevNode(lane * width, row, width).Should().Be((junctionNode2, false));
throw;
}
#endif
}
}
}

0 comments on commit c61b7a5

Please sign in to comment.