diff --git a/ProjectDirector/ProjectDirector.cs b/ProjectDirector/ProjectDirector.cs index 5c400ef..1b26be5 100644 --- a/ProjectDirector/ProjectDirector.cs +++ b/ProjectDirector/ProjectDirector.cs @@ -894,7 +894,12 @@ private void ShowCollapsiblePanel(string name, Action contentDelegate) ImGuiProbes.MarkItem("panel", name); if (expanded) { + // CollapsingHeader opens no ID scope of its own, so without this two panels drawing a widget + // with the same label (each owner's ".github" row) would give both the same ID, and ImGui would + // hand a click on the second to the first. + ImGui.PushID(name); contentDelegate?.Invoke(); + ImGui.PopID(); open = true; } else @@ -1020,7 +1025,7 @@ private void ShowRepos(GitHubOwnerName owner) ImGui.SameLine(); bool isSelected = Options.BaseRepo == repoName; bool clicked = ImGui.Selectable(gitHubRepo.RepoName, ref isSelected); - ImGuiProbes.MarkItem("repo", gitHubRepo.RepoName); + ImGuiProbes.MarkItem("repo", repoName); if (clicked) { SwitchPage(repoName); diff --git a/tests/ProjectDirector.UITests/Gallery/GalleryApp.cs b/tests/ProjectDirector.UITests/Gallery/GalleryApp.cs index b85c9d1..3540794 100644 --- a/tests/ProjectDirector.UITests/Gallery/GalleryApp.cs +++ b/tests/ProjectDirector.UITests/Gallery/GalleryApp.cs @@ -39,7 +39,13 @@ private GalleryApp(ImGuiAppHarness harness, ProjectDirectorOptions options, Cred /// Starts the application at the given display size. /// The size of the window the application draws into. /// The running application. - internal static GalleryApp Start((int Width, int Height) display) + internal static GalleryApp Start((int Width, int Height) display) => Start(display, GalleryRepositories.CreateOptions); + + /// Starts the application at the given display size over settings the caller builds. + /// The size of the window the application draws into. + /// Builds the settings the application runs on, after the settings file and secret store are redirected. + /// The running application. + internal static GalleryApp Start((int Width, int Height) display, Func createOptions) { AppData.ConfigureForTesting(() => new MockFileSystem()); CredentialCache credentials = new(new InMemoryCredentialStore()); @@ -47,7 +53,7 @@ internal static GalleryApp Start((int Width, int Height) display) try { - ProjectDirectorOptions options = GalleryRepositories.CreateOptions(); + ProjectDirectorOptions options = createOptions(); ProjectDirector director = new(options); ImGuiAppHarness harness = ImGuiAppHarness.Start(director.BuildConfig(), new HarnessOptions { @@ -68,7 +74,7 @@ internal static GalleryApp Start((int Width, int Height) display) /// The repository's own name. internal void SelectRepository(string repo) { - Harness.Click($"repo/{repo}"); + Harness.Click($"repo/{GalleryRepositories.FullName(repo)}"); GitRepository selected = Options.Repos[GalleryRepositories.FullName(repo)]; // The comparison runs git on a background task, which publishes whenever it finishes rather diff --git a/tests/ProjectDirector.UITests/RepoPanelTests.cs b/tests/ProjectDirector.UITests/RepoPanelTests.cs new file mode 100644 index 0000000..0230a2a --- /dev/null +++ b/tests/ProjectDirector.UITests/RepoPanelTests.cs @@ -0,0 +1,64 @@ +// Copyright (c) 2023-2026 ktsu-dev contributors + +namespace ktsu.ProjectDirector.UITests; + +using ktsu.ProjectDirector.UITests.Gallery; +using ktsu.Semantics.Paths; + +/// +/// Drives the left panel's repository list, where every owner's repositories are drawn as rows of +/// the same kind of widget. +/// +[TestClass] +public sealed class RepoPanelTests +{ + private const string SharedName = ".github"; + + private static readonly string[] Owners = ["ktsu-dev", "matt-edmondson"]; + + [TestMethod] + public void ClickingASharedRepoName_SelectsThatOwnersRepository() + { + using GalleryApp app = GalleryApp.Start(AppGallery.Display, CreateOptions); + app.Harness.Step(2); + + // Click the later owner first: it is the row an ID clash with the earlier owner's row swallows. + foreach (string owner in Owners.Reverse()) + { + FullyQualifiedGitHubRepoName expected = FullName(owner); + app.Harness.Click($"repo/{expected}"); + app.Harness.Step(2); + + Assert.AreEqual(expected, app.Options.BaseRepo, $"Clicking {owner}'s {SharedName} did not select it."); + } + } + + private static FullyQualifiedGitHubRepoName FullName(string owner) => + FullyQualifiedGitHubRepoName.Create($"{owner}.{SharedName}"); + + private static ProjectDirectorOptions CreateOptions() + { + // Nothing is cloned, so the paths are never created; they only have to differ per owner. + string root = Path.Combine(Path.GetTempPath(), "projectdirector-repo-panel"); + ProjectDirectorOptions options = new() + { + DevDirectory = AbsoluteDirectoryPath.Create(root), + }; + + foreach (string ownerName in Owners) + { + GitHubOwnerName owner = GitHubOwnerName.Create(ownerName); + _ = options.GitHubOwners.Add(owner); + options.Repos[FullName(ownerName)] = new GitHubRepository() + { + OwnerName = owner, + RepoName = GitHubRepoName.Create(SharedName), + LocalPath = FullyQualifiedLocalRepoPath.Create(Path.Combine(root, ownerName, SharedName)), + RemotePath = GitRemotePath.Create($"https://github.com/{ownerName}/{SharedName}"), + MinFetchIntervalSeconds = 0, + }; + } + + return options; + } +}