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
14 changes: 10 additions & 4 deletions src/Kontext.Presentation/GitChangeRow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,23 @@ public bool InitiallyChecked
/// <summary>Told when the user checks or unchecks the row.</summary>
public Action<GitChangeRow, bool>? Toggled { get; init; }

public string StatusLetter => Change?.Status switch
public string StatusGlyph => Change?.Status switch
{
FileChangeStatus.Modified => "M",
// A new file is "+", staged or not.
FileChangeStatus.Added or FileChangeStatus.Untracked => "+",
FileChangeStatus.Deleted => "D",
FileChangeStatus.Renamed => "R",
FileChangeStatus.Modified or FileChangeStatus.Renamed => "●",
FileChangeStatus.Deleted => "−",
FileChangeStatus.Conflicted => "!",
_ => string.Empty,
};

public bool IsAddedGlyph => Change?.Status is FileChangeStatus.Added or FileChangeStatus.Untracked;

public bool IsChangedGlyph => Change?.Status is FileChangeStatus.Modified or FileChangeStatus.Renamed;

/// <summary>Drawn in the critical colour: deleted files, and conflicted ones since they need attention.</summary>
public bool IsRemovedGlyph => Change?.Status is FileChangeStatus.Deleted or FileChangeStatus.Conflicted;

public string FileName => Change is null ? string.Empty : Change.Path[(Change.Path.LastIndexOf('/') + 1)..];

public string Folder => Change is null ? string.Empty : Change.Path[..Math.Max(Change.Path.LastIndexOf('/'), 0)];
Expand Down
25 changes: 25 additions & 0 deletions src/Kontext.Presentation/IssueSidebarViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -373,8 +373,33 @@ private void OnGroupExpandedChanged(RepositoryGroupViewModel group)
_collapsedRepositories.Save(_collapsed);
}

private void ForgetClearedWorkspaces(IReadOnlyList<ExistingWorkspace> workspaces)
{
var existing = workspaces.Select(workspace => workspace.Issue).ToHashSet();

foreach (var item in _all.ToList())
{
if (existing.Contains(item.Key))
{
continue;
}

var state = _tracker.StateOf(item.Key);
if (state.Status is IssueWorkStatus.Ready or IssueWorkStatus.Done && state.WorktreePath is not null)
{
_tracker.Report(item.Key, IssueWorkState.NotStarted);
}
else if (state.Status == IssueWorkStatus.NotStarted)
{
item.HasSavedSession = false;
}
}
}

private void MarkExistingWorkspaces(IReadOnlyList<ExistingWorkspace> workspaces)
{
ForgetClearedWorkspaces(workspaces);

foreach (var workspace in workspaces)
{
if (!_itemsByKey.TryGetValue(workspace.Issue, out var item))
Expand Down
26 changes: 20 additions & 6 deletions src/Kontext/Views/GitChangesView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,26 @@
AutomationProperties.AutomationId="GitFileCheck"
AutomationProperties.Name="{x:Bind Text}"
IsChecked="{x:Bind IsChecked, Mode=TwoWay}" />
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
FontFamily="Consolas"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="{x:Bind StatusLetter}" />
<Grid Grid.Column="1" VerticalAlignment="Center">
<TextBlock
HorizontalAlignment="Center"
FontWeight="Bold"
Foreground="{ThemeResource SystemFillColorSuccessBrush}"
Text="{x:Bind StatusGlyph}"
Visibility="{x:Bind IsAddedGlyph}" />
<TextBlock
HorizontalAlignment="Center"
FontWeight="Bold"
Foreground="{ThemeResource SystemFillColorCautionBrush}"
Text="{x:Bind StatusGlyph}"
Visibility="{x:Bind IsChangedGlyph}" />
<TextBlock
HorizontalAlignment="Center"
FontWeight="Bold"
Foreground="{ThemeResource SystemFillColorCriticalBrush}"
Text="{x:Bind StatusGlyph}"
Visibility="{x:Bind IsRemovedGlyph}" />
</Grid>
<TextBlock
Grid.Column="2"
VerticalAlignment="Center"
Expand Down
24 changes: 15 additions & 9 deletions tests/Kontext.Presentation.UnitTests/GitChangeRowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@ public class GitChangeRowTests
private static GitChangeRow File(string path = "src/Views/Foo.cs", FileChangeStatus status = FileChangeStatus.Modified, int? additions = 12, int? deletions = 3)
=> new(path, new FileChange(path, null, status, IsStaged: false, additions, deletions, IsBinary: false));

[TestCase(FileChangeStatus.Modified, "M")]
[TestCase(FileChangeStatus.Added, "+")]
[TestCase(FileChangeStatus.Deleted, "D")]
[TestCase(FileChangeStatus.Renamed, "R")]
[TestCase(FileChangeStatus.Untracked, "+")]
[TestCase(FileChangeStatus.Conflicted, "!")]
public void ShowsTheStatusLetterForEachStatus(FileChangeStatus status, string letter)
[TestCase(FileChangeStatus.Added, "+", true, false, false)]
[TestCase(FileChangeStatus.Untracked, "+", true, false, false)]
[TestCase(FileChangeStatus.Modified, "●", false, true, false)]
[TestCase(FileChangeStatus.Renamed, "●", false, true, false)]
[TestCase(FileChangeStatus.Deleted, "−", false, false, true)]
[TestCase(FileChangeStatus.Conflicted, "!", false, false, true)]
public void ShowsAColouredGlyphForEachStatus(FileChangeStatus status, string glyph, bool added, bool changed, bool removed)
{
Assert.That(File(status: status).StatusLetter, Is.EqualTo(letter));
var row = File(status: status);

Assert.That(row.StatusGlyph, Is.EqualTo(glyph));
Assert.That(row.IsAddedGlyph, Is.EqualTo(added));
Assert.That(row.IsChangedGlyph, Is.EqualTo(changed));
Assert.That(row.IsRemovedGlyph, Is.EqualTo(removed));
}

[Test]
Expand Down Expand Up @@ -54,7 +59,8 @@ public void AHeaderRowHasNoFile()

Assert.That(header.IsHeader, Is.True);
Assert.That(header.Text, Is.EqualTo("Staged (1)"));
Assert.That(header.StatusLetter, Is.Empty);
Assert.That(header.StatusGlyph, Is.Empty);
Assert.That(header.IsAddedGlyph || header.IsChangedGlyph || header.IsRemovedGlyph, Is.False);
Assert.That(header.HasCounts, Is.False);
Assert.That(File().IsHeader, Is.False);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class IssueSidebarAutoRefreshTests
private FakeTimeProvider _clock = null!;
private InMemoryIssueWorkTracker _tracker = null!;
private FakeGitHubService _gitHub = null!;
private FakeExistingWorkspaceFinder _workspaces = null!;
private IssueSidebarViewModel? _sidebar;

[SetUp]
Expand All @@ -20,6 +21,7 @@ public void Setup()
_clock = new FakeTimeProvider();
_tracker = new InMemoryIssueWorkTracker();
_gitHub = new FakeGitHubService { Issues = [Issues.Create(4, "Add notifications", "acme/memento")] };
_workspaces = new FakeExistingWorkspaceFinder();
}

[TearDown]
Expand All @@ -29,7 +31,7 @@ private IssueSidebarViewModel CreateSidebar(TimeSpan interval) =>
_sidebar = new(
_gitHub,
_tracker,
new FakeExistingWorkspaceFinder(),
_workspaces,
new WorkspaceIssues(_gitHub),
new InMemoryHiddenRepositoryStore(),
new InMemoryCollapsedRepositoryStore(),
Expand Down Expand Up @@ -97,6 +99,21 @@ public async Task DoesNotRefreshWhenTheIntervalIsZero()
Assert.That(IssueCount(sidebar), Is.EqualTo(1));
}

[Test]
public async Task NoticesWhenASessionIsClearedElsewhere()
{
var issue = _gitHub.Issues[0];
_workspaces.Found.Add(new ExistingWorkspace(issue.Repository, issue.Number, new WorktreeInfo(@"C:\wt\4", "kontext/4-add", "abc", IsMain: false)));
var sidebar = CreateSidebar(Interval);
await sidebar.RefreshCommand.ExecuteAsync(null);
Assert.That(_tracker.StateOf(issue.Key).Status, Is.EqualTo(IssueWorkStatus.Ready));

_workspaces.Found.Clear();
_clock.Advance(Interval);

await AsyncAssert.EventuallyAsync(() => _tracker.StateOf(issue.Key).Status == IssueWorkStatus.NotStarted, "the cleared session to be noticed");
}

[Test]
public async Task StopsRefreshingAfterSignOut()
{
Expand Down