diff --git a/src/DiffEngineViewer.Tests/TrackedLabelTests.cs b/src/DiffEngineViewer.Tests/TrackedLabelTests.cs
new file mode 100644
index 00000000..f74cdf99
--- /dev/null
+++ b/src/DiffEngineViewer.Tests/TrackedLabelTests.cs
@@ -0,0 +1,82 @@
+///
+/// Labels for tracked moves and deletes. One verified file name in two projects of a solution is
+/// the ordinary case - every project has a SampleTests with a sample.verified.txt - and the label
+/// is the only thing on the row.
+///
+public class TrackedLabelTests
+{
+ [Test]
+ public async Task Two_moves_of_one_file_name_are_told_apart()
+ {
+ var state = Tracked(
+ Move("ProjectA"),
+ Move("ProjectB"));
+
+ await Assert.That(Labels(state)).IsEquivalentTo(
+ [
+ "ProjectA/sample.verified.txt",
+ "ProjectB/sample.verified.txt"
+ ]);
+ }
+
+ [Test]
+ public async Task Two_deletes_of_one_file_name_are_told_apart()
+ {
+ var state = Tracked(
+ Delete("ProjectA"),
+ Delete("ProjectB"));
+
+ await Assert.That(Labels(state)).IsEquivalentTo(
+ [
+ "ProjectA/extra.verified.txt",
+ "ProjectB/extra.verified.txt"
+ ]);
+ }
+
+ ///
+ /// A name that is already distinct is left as it is: the label is the shortest form that tells
+ /// one entry from another, and the path is what the tooltip is for.
+ ///
+ [Test]
+ public async Task One_move_keeps_its_bare_name()
+ {
+ var state = Tracked(Move("ProjectA"));
+
+ await Assert.That(Labels(state)).IsEquivalentTo(["sample.verified.txt"]);
+ }
+
+ static IReadOnlyList Labels(SessionState state) =>
+ QueueProjection.Rows(state)
+ .Where(_ => _.Kind == QueueRowKind.Entry)
+ .Select(_ => _.Label.Trim())
+ .ToList();
+
+ static SessionState Tracked(params QueueEntry[] entries)
+ {
+ var state = SessionState.Start(ViewerMode.Inline, Fixtures.Columns, Fixtures.Rows);
+ foreach (var entry in entries)
+ {
+ state = ViewerSession.EnqueueTracked(state, entry);
+ }
+
+ return state;
+ }
+
+ static QueueEntry Move(string project) =>
+ QueueEntry.ForMove(
+ $"move:{project}",
+ "sample.verified.txt",
+ "SolutionA",
+ $"temp/{project}/sample.received.txt",
+ $"code/SolutionA/{project}/sample.verified.txt",
+ FileSide.OfText("received"),
+ FileSide.OfText("expected"));
+
+ static QueueEntry Delete(string project) =>
+ QueueEntry.ForDelete(
+ $"delete:{project}",
+ "extra.verified.txt",
+ "SolutionA",
+ $"code/SolutionA/{project}/extra.verified.txt",
+ FileSide.OfText("expected"));
+}
diff --git a/src/DiffEngineViewer/QueueProjection.cs b/src/DiffEngineViewer/QueueProjection.cs
index a62f83c8..aa103523 100644
--- a/src/DiffEngineViewer/QueueProjection.cs
+++ b/src/DiffEngineViewer/QueueProjection.cs
@@ -328,6 +328,20 @@ static QueueItem EntryRow(
? $"{entry.Patch.SourceFile.ToLowerInvariant()}|{entry.TestName}"
: null;
+ ///
+ /// The path an entry's label can be grown from: the source file for an inline entry, and the
+ /// file a tracked one is about. Tracked entries used to have none, so two verified files with
+ /// the same name in two projects of one solution were left showing the same label.
+ ///
+ static string? LabelPath(QueueEntry entry) =>
+ entry.Kind switch
+ {
+ QueueEntryKind.Inline => entry.Patch?.SourceFile,
+ QueueEntryKind.Move => entry.TargetFile ?? entry.LeftFile,
+ QueueEntryKind.Delete => entry.LeftFile,
+ _ => null
+ };
+
///
/// The label an entry shows when it stands alone: the test name when one is known, else the
/// call site or the tracked file name. Collisions within a solution — the same file name and
@@ -357,7 +371,7 @@ static string[] Labels(IReadOnlyList entries)
{
var entry = entries[index];
var baseLabel = entry.TestName ?? entry.Name;
- labels[index] = WithDirectories(entry.Patch!.SourceFile, baseLabel, depth);
+ labels[index] = WithDirectories(LabelPath(entry)!, baseLabel, depth);
}
}
@@ -367,7 +381,7 @@ static string[] Labels(IReadOnlyList entries)
foreach (var index in Collisions(entries, labels))
{
var entry = entries[index];
- labels[index] = $"{entry.TestName ?? entry.Name} ({Path.GetFileName(entry.Patch!.SourceFile)})";
+ labels[index] = $"{entry.TestName ?? entry.Name} ({Path.GetFileName(LabelPath(entry)!)})";
}
return labels;
@@ -378,7 +392,7 @@ static List Collisions(IReadOnlyList entries, string[] labels)
var collisions = new List();
for (var index = 0; index < entries.Count; index++)
{
- if (entries[index] is not { Kind: QueueEntryKind.Inline, Patch: not null })
+ if (LabelPath(entries[index]) is null)
{
continue;
}