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
83 changes: 83 additions & 0 deletions ProjectDirector.Test/BrowserDirectoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) 2023-2026 ktsu-dev contributors

namespace ktsu.ProjectDirector.Test;

using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using Semantics.Paths;

/// <summary>
/// Covers how the repository browsers tell a directory from a file.
/// </summary>
/// <remarks>
/// The compare browser used to look for a trailing separator on each entry. The entries are
/// <see cref="RelativePath"/>s, which do not keep one, so it found no directories at all and listed
/// every directory among the files, where clicking it could not open it.
/// </remarks>
[TestClass]
public sealed class BrowserDirectoryTests
{
private static readonly string[] ExpectedDirectories = ["docs", "src"];

private static readonly string[] NestedDirectories = [Path.Join("src", "Inner")];

private string root = string.Empty;

[TestInitialize]
public void CreateRepositories()
{
root = Path.Join(Path.GetTempPath(), $"browser-dirs-{Guid.NewGuid():N}");
_ = Directory.CreateDirectory(Path.Join(root, "A", "src"));
_ = Directory.CreateDirectory(Path.Join(root, "B", "docs"));
File.WriteAllText(Path.Join(root, "A", "README.md"), "a");
File.WriteAllText(Path.Join(root, "B", "README.md"), "b");
}

[TestCleanup]
public void DeleteRepositories() => Directory.Delete(root, recursive: true);

[TestMethod]
public void ARelativePathDoesNotKeepItsTrailingSeparator()
{
RelativePath entry = RelativePath.Create<RelativePath>("src" + Path.DirectorySeparatorChar);

Assert.IsFalse(entry.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal), "If this starts passing, the spelling of an entry says whether it is a directory again.");
}

[TestMethod]
public void DirectoriesAreFoundInWhicheverRepositoryHasThem()
{
RelativePath[] entries = [Entry("docs"), Entry("README.md"), Entry("src")];

Collection<RelativePath> directories = ProjectDirector.ListBrowserDirectories(entries, Path.Join(root, "A"), Path.Join(root, "B"));

Assert.AreSequenceEqual(ExpectedDirectories, directories.Select(x => x.ToString()));
}

[TestMethod]
public void ANestedEntryIsFoundFromTheRepositoryRoot()
{
_ = Directory.CreateDirectory(Path.Join(root, "A", "src", "Inner"));
File.WriteAllText(Path.Join(root, "A", "src", "a.cs"), "a");
RelativePath[] entries = [Entry(Path.Join("src", "Inner")), Entry(Path.Join("src", "a.cs"))];

Collection<RelativePath> directories = ProjectDirector.ListBrowserDirectories(entries, Path.Join(root, "A"));

Assert.AreSequenceEqual(NestedDirectories, directories.Select(x => x.ToString()));
}

[TestMethod]
public void AFileIsNeverADirectory()
{
RelativePath[] entries = [Entry("README.md")];

Assert.IsEmpty(ProjectDirector.ListBrowserDirectories(entries, Path.Join(root, "A")));
}

private static RelativePath Entry(string name) => RelativePath.Create<RelativePath>(name + (Path.GetFileName(name).Contains('.', StringComparison.Ordinal) ? string.Empty : Path.DirectorySeparatorChar.ToString()));
}
27 changes: 24 additions & 3 deletions ProjectDirector/ProjectDirector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
using ktsu.ImGui.Widgets;
using ktsu.ImGui.Styler;
using Octokit;
// using OpenAI.Chat;

Check warning on line 22 in ProjectDirector/ProjectDirector.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this commented out code.

Check warning on line 22 in ProjectDirector/ProjectDirector.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this commented out code.
using Semantics.Paths;

#pragma warning disable CA1506
Expand Down Expand Up @@ -62,7 +62,7 @@
/// </summary>
private CloneTracker Clones { get; } = new();

// private ChatClient ChatClient { get; init; }

Check warning on line 65 in ProjectDirector/ProjectDirector.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this commented out code.

Check warning on line 65 in ProjectDirector/ProjectDirector.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this commented out code.

private static void Main(string[] _) => ImGuiApp.Start(new ProjectDirector().BuildConfig());

Expand Down Expand Up @@ -101,7 +101,7 @@
_ = MakeLoadedOptionsSafe(Options, QueueLog);

Options.Save();
// ChatClient = new(model: "gpt-4o", new ApiKeyCredential(Options.OpenAIToken));

Check warning on line 104 in ProjectDirector/ProjectDirector.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this commented out code.

Check warning on line 104 in ProjectDirector/ProjectDirector.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this commented out code.
DividerDiff = new("DiffDivider", DividerResized, ImGuiWidgets.DividerLayout.Columns);
DividerContainerCols = new("VerticalDivider", DividerResized, ImGuiWidgets.DividerLayout.Columns);
DividerContainerRows = new("HorizontalDivider", DividerResized, ImGuiWidgets.DividerLayout.Rows);
Expand Down Expand Up @@ -717,6 +717,11 @@

private void ShowTopPanel(float dt)
{
// The divider draws each zone as its own child window, so this window is the whole top zone.
// Marking it lets a picture of the application stop above the splitter and the log.
Vector2 topPanelPosition = ImGui.GetWindowPos();
ImGuiProbes.MarkRegion("TopPanel", topPanelPosition, topPanelPosition + ImGui.GetWindowSize());

if (Options.Repos.TryGetValue(Options.BaseRepo, out GitRepository? repo))
{
ImGui.TextUnformatted($"Selected Repo: {Options.BaseRepo}");
Expand Down Expand Up @@ -760,7 +765,7 @@
});
}

//int fetchInterval = repo.MinFetchIntervalSeconds;

Check warning on line 768 in ProjectDirector/ProjectDirector.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this commented out code.

Check warning on line 768 in ProjectDirector/ProjectDirector.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this commented out code.
//if (ImGuiWidgets.Knob("Min Fetch Interval", ref fetchInterval, 0, 300, 150))
//{
// repo.MinFetchIntervalSeconds = fetchInterval;
Expand Down Expand Up @@ -885,7 +890,9 @@
}

bool wasOpen = open;
if (ImGui.CollapsingHeader(name, flags))
bool expanded = ImGui.CollapsingHeader(name, flags);
ImGuiProbes.MarkItem("panel", name);
if (expanded)
{
contentDelegate?.Invoke();
open = true;
Expand Down Expand Up @@ -2067,7 +2074,7 @@
private void ShowCompareBrowser()
{
IEnumerable<RelativePath> allFilesystemEntries = BrowserContentsBase.Union(BrowserContentsCompare);
Collection<RelativePath> directories = allFilesystemEntries.Where(x => x.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)).ToCollection();
Collection<RelativePath> directories = ListBrowserDirectories(allFilesystemEntries, Options.Repos[Options.BaseRepo].LocalPath, Options.Repos[Options.CompareRepo].LocalPath);
Collection<RelativePath> files = allFilesystemEntries.Except(directories).ToCollection();

if (ImGui.BeginTable("CompareBrowser", 3, ImGuiTableFlags.Borders))
Expand Down Expand Up @@ -2238,7 +2245,7 @@
{
Collection<RelativePath> allFilesystemEntries = BrowserContentsBase;
GitRepository baseRepo = Options.Repos[Options.BaseRepo];
Collection<RelativePath> directories = allFilesystemEntries.Where(x => Directory.Exists(Path.Combine(baseRepo.LocalPath, Options.BrowsePath, x))).ToCollection();
Collection<RelativePath> directories = ListBrowserDirectories(allFilesystemEntries, baseRepo.LocalPath);
Collection<RelativePath> files = allFilesystemEntries.Except(directories).ToCollection();

bool shouldOpenPopup = false;
Expand Down Expand Up @@ -2293,7 +2300,7 @@

if (ImGui.TableNextColumn())
{
//if (ImGui.Button($"Propagate Directory###Propagate{path.Replace(Path.DirectorySeparatorChar, '.').Replace(Path.AltDirectorySeparatorChar, '.')}"))

Check warning on line 2303 in ProjectDirector/ProjectDirector.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this commented out code.

Check warning on line 2303 in ProjectDirector/ProjectDirector.cs

View workflow job for this annotation

GitHub Actions / ci / .NET / Analyze & Release

Remove this commented out code.
//{
// shouldOpenPopup |= true;
// Options.PropagatePath = path;
Expand Down Expand Up @@ -2421,6 +2428,20 @@
internal static IEnumerable<string> ListBrowserEntries(string directory) =>
Directory.EnumerateFileSystemEntries(directory).Order(StringComparer.OrdinalIgnoreCase);

/// <summary>
/// Picks out the entries of a browser listing that are directories in any of the given repositories.
/// </summary>
/// <param name="entries">The listing, each entry relative to the repository root (so already including the browsed directory).</param>
/// <param name="repoRoots">The working trees the listing was taken from.</param>
/// <returns>The entries that are directories, in listing order.</returns>
/// <remarks>
/// Asked of the file system rather than read off the entry's spelling: a <see cref="RelativePath"/>
/// does not keep a trailing separator, so a test for one finds no directories at all, and the
/// compare browser used to list every directory as a file that could not be opened.
/// </remarks>
internal static Collection<RelativePath> ListBrowserDirectories(IEnumerable<RelativePath> entries, params string[] repoRoots) =>
entries.Where(entry => repoRoots.Any(root => Directory.Exists(Path.Join(root, entry)))).ToCollection();

private void SwitchCompareBrowserPath(FullyQualifiedGitHubRepoName baseRepo, FullyQualifiedGitHubRepoName compareRepo, RelativeDirectoryPath newPath)
{
Options.BrowsePath = newPath;
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ canonical version out to the rest.

The [app gallery](docs/gallery/README.md) shows the main views and dialogs as the application draws them today.

[![Two copies of a file compared side by side](docs/gallery/comparing-a-file.png)](docs/gallery/README.md)

## Requirements

- .NET 9.0
Expand Down
8 changes: 6 additions & 2 deletions tests/ProjectDirector.UITests/Gallery/GalleryApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,15 @@ internal void RightClick(string name)
/// Gets everything above the log panel. The log prefixes each line with the wall-clock time it
/// was written, so it can never be photographed the same way twice.
/// </summary>
/// <remarks>
/// The crop ends at the bottom of the top zone rather than at the log itself, so neither the
/// splitter between the two nor the log's own border is left as a strip along the bottom.
/// </remarks>
/// <returns>The part of the window worth keeping.</returns>
internal Rectangle AboveTheLog()
{
Rectangle log = Harness.Probe.Rect("Log") ?? throw new InvalidOperationException("The log panel has not been drawn.");
return new Rectangle(0, 0, Harness.Options.Width, log.MinY - 1);
Rectangle top = Harness.Probe.Rect("TopPanel") ?? throw new InvalidOperationException("The top panel has not been drawn.");
return new Rectangle(0, 0, Harness.Options.Width, top.MaxY);
}

/// <inheritdoc/>
Expand Down
12 changes: 11 additions & 1 deletion tests/ProjectDirector.UITests/Gallery/GalleryCatalog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ internal static class GalleryCatalog
app =>
{
app.SelectRepository(GalleryRepositories.Alpha);

// The popup opens in the middle of the window, which with every panel open is across
// the similar repositories table, leaving the tops of its glyphs showing above the
// title bar. Folding the git actions away lifts the table clear of it.
app.Harness.Click("panel/Git Actions");
app.Harness.Step(2);
app.RightClick("browse/README.md");
app.Harness.Click("browse-menu/Propagate");
app.Harness.Step(3);
Expand All @@ -80,9 +86,13 @@ internal static class GalleryCatalog

private static Rectangle? AboveTheLog(GalleryApp app) => app.AboveTheLog();

/// <summary>Opens a popup from the File menu. The popup is only ever shown, never confirmed.</summary>
/// <summary>
/// Opens a popup from the File menu over a selected repository, so the popup is seen against the
/// application rather than an empty panel. The popup is only ever shown, never confirmed.
/// </summary>
private static void OpenMenuItem(GalleryApp app, string item)
{
app.SelectRepository(GalleryRepositories.Alpha);
app.Harness.Click("menu/File");
app.Harness.Step(2);
app.Harness.Click(item);
Expand Down
Loading