FN-6043: default GitHub imports to origin remote
Prefer the origin remote when preselecting GitHub imports in the dashboard modal. - default the GitHub import modal to the origin remote when multiple remotes are available - keep the remote picker unselected when multiple remotes exist without an origin entry - expand dashboard tests to cover origin defaults for issues, pull requests, and mobile flows Files changed: .../dashboard/app/components/GitHubImportModal.tsx | 16 ++-- .../__tests__/GitHubImportModal.test.tsx | 95 ++++++++++++++++------ 2 files changed, 77 insertions(+), 34 deletions(-) Fusion-Task-Id: FN-6043 Fusion-Task-Lineage: b03b621d-757a-422c-b31c-ca0c34c3c168
This commit is contained in:
@@ -143,14 +143,16 @@ export function GitHubImportModal({ isOpen, onClose, onImport, tasks, projectId
|
||||
setRemotes(fetchedRemotes);
|
||||
setLoadingRemotes(false);
|
||||
|
||||
if (fetchedRemotes.length === 1) {
|
||||
// Single remote: auto-select it
|
||||
const remote = fetchedRemotes[0];
|
||||
setOwner(remote.owner);
|
||||
setRepo(remote.repo);
|
||||
setSelectedRemoteName(remote.name);
|
||||
const defaultRemote = fetchedRemotes.length === 1
|
||||
? fetchedRemotes[0]
|
||||
: fetchedRemotes.find((remote) => remote.name === "origin");
|
||||
|
||||
if (defaultRemote) {
|
||||
setOwner(defaultRemote.owner);
|
||||
setRepo(defaultRemote.repo);
|
||||
setSelectedRemoteName(defaultRemote.name);
|
||||
} else if (fetchedRemotes.length > 1) {
|
||||
// Multiple remotes: don't auto-select, user must choose
|
||||
// Multiple remotes without origin: don't auto-select, user must choose.
|
||||
setOwner("");
|
||||
setRepo("");
|
||||
setSelectedRemoteName("");
|
||||
|
||||
@@ -61,6 +61,11 @@ const multipleRemotes: GitRemote[] = [
|
||||
{ name: "upstream", owner: "upstream", repo: "kb", url: "https://github.com/upstream/kb.git" },
|
||||
];
|
||||
|
||||
const multipleRemotesWithoutOrigin: GitRemote[] = [
|
||||
{ name: "upstream", owner: "upstream", repo: "kb", url: "https://github.com/upstream/kb.git" },
|
||||
{ name: "fork", owner: "dustinbyrne", repo: "kb", url: "https://github.com/dustinbyrne/kb.git" },
|
||||
];
|
||||
|
||||
const mockPulls = [
|
||||
{ number: 1, title: "Test PR", body: "PR body", html_url: "https://github.com/owner/repo/pull/1", headBranch: "feature", baseBranch: "main" },
|
||||
{ number: 2, title: "Another PR", body: "Another PR body", html_url: "https://github.com/owner/repo/pull/2", headBranch: "bugfix", baseBranch: "main" },
|
||||
@@ -289,19 +294,7 @@ describe("GitHubImportModal", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("disables Load button when no remote is selected", async () => {
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce(multipleRemotes);
|
||||
render(<GitHubImportModal isOpen={true} onClose={onClose} onImport={onImport} tasks={[]} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("combobox")).toBeTruthy();
|
||||
});
|
||||
|
||||
const loadButton = screen.getByRole("button", { name: /Load issues/i }) as HTMLButtonElement;
|
||||
expect(loadButton.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it("enables Load button and auto-loads after selecting a remote", async () => {
|
||||
it("defaults to origin and auto-loads when multiple remotes include origin", async () => {
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce(multipleRemotes);
|
||||
vi.mocked(apiFetchGitHubIssues).mockResolvedValueOnce([
|
||||
{ number: 1, title: "Auto-loaded from origin", body: "", html_url: "https://github.com/dustinbyrne/kb/issues/1", labels: [] },
|
||||
@@ -310,21 +303,30 @@ describe("GitHubImportModal", () => {
|
||||
render(<GitHubImportModal isOpen={true} onClose={onClose} onImport={onImport} tasks={[]} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("combobox")).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.change(screen.getByRole("combobox"), { target: { value: "origin" } });
|
||||
|
||||
await waitFor(() => {
|
||||
const select = screen.getByRole("combobox") as HTMLSelectElement;
|
||||
expect(select.value).toBe("origin");
|
||||
expect(apiFetchGitHubIssues).toHaveBeenCalledWith("dustinbyrne", "kb", 30, undefined);
|
||||
expect(screen.getByText("Auto-loaded from origin")).toBeTruthy();
|
||||
});
|
||||
|
||||
// After loading completes, button should be enabled
|
||||
const loadButton = screen.getByRole("button", { name: /Load issues/i }) as HTMLButtonElement;
|
||||
expect(loadButton.disabled).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps placeholder selected and does not auto-load when multiple remotes omit origin", async () => {
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce(multipleRemotesWithoutOrigin);
|
||||
render(<GitHubImportModal isOpen={true} onClose={onClose} onImport={onImport} tasks={[]} />);
|
||||
|
||||
await waitFor(() => {
|
||||
const select = screen.getByRole("combobox") as HTMLSelectElement;
|
||||
expect(select.value).toBe("");
|
||||
});
|
||||
|
||||
const loadButton = screen.getByRole("button", { name: /Load issues/i }) as HTMLButtonElement;
|
||||
expect(loadButton.disabled).toBe(true);
|
||||
expect(apiFetchGitHubIssues).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("switches owner/repo and auto-loads when changing remote selection", async () => {
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce(multipleRemotes);
|
||||
vi.mocked(apiFetchGitHubIssues)
|
||||
@@ -334,18 +336,13 @@ describe("GitHubImportModal", () => {
|
||||
render(<GitHubImportModal isOpen={true} onClose={onClose} onImport={onImport} tasks={[]} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("combobox")).toBeTruthy();
|
||||
});
|
||||
|
||||
const select = screen.getByRole("combobox");
|
||||
fireEvent.change(select, { target: { value: "origin" } });
|
||||
|
||||
await waitFor(() => {
|
||||
const select = screen.getByRole("combobox") as HTMLSelectElement;
|
||||
expect(select.value).toBe("origin");
|
||||
expect(apiFetchGitHubIssues).toHaveBeenCalledWith("dustinbyrne", "kb", 30, undefined);
|
||||
expect(screen.getByText("Issue from origin")).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.change(select, { target: { value: "upstream" } });
|
||||
fireEvent.change(screen.getByRole("combobox"), { target: { value: "upstream" } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(apiFetchGitHubIssues).toHaveBeenLastCalledWith("upstream", "kb", 30, undefined);
|
||||
@@ -574,6 +571,31 @@ describe("GitHubImportModal", () => {
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
});
|
||||
|
||||
it("defaults to origin and auto-loads on mobile", async () => {
|
||||
Object.defineProperty(window, "innerWidth", {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
value: 640,
|
||||
});
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce(multipleRemotes);
|
||||
vi.mocked(apiFetchGitHubIssues).mockResolvedValueOnce([
|
||||
{ number: 1, title: "Mobile origin issue", body: "", html_url: "https://github.com/dustinbyrne/kb/issues/1", labels: [] },
|
||||
]);
|
||||
|
||||
render(<GitHubImportModal isOpen={true} onClose={onClose} onImport={onImport} tasks={[]} />);
|
||||
|
||||
await waitFor(() => {
|
||||
const select = screen.getByRole("combobox") as HTMLSelectElement;
|
||||
expect(select.value).toBe("origin");
|
||||
expect(apiFetchGitHubIssues).toHaveBeenCalledWith("dustinbyrne", "kb", 30, undefined);
|
||||
expect(screen.getByText("Mobile origin issue")).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(screen.getByTestId("github-import-list-pane").classList.contains("mobile")).toBe(true);
|
||||
});
|
||||
|
||||
it("shows back button in preview header when on mobile", async () => {
|
||||
// Set mobile viewport
|
||||
Object.defineProperty(window, "innerWidth", {
|
||||
@@ -884,6 +906,25 @@ describe("GitHubImportModal", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the default origin remote when switching to Pull Requests", async () => {
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce(multipleRemotes);
|
||||
vi.mocked(apiFetchGitHubPulls).mockResolvedValueOnce(mockPulls);
|
||||
|
||||
render(<GitHubImportModal isOpen={true} onClose={onClose} onImport={onImport} tasks={[]} />);
|
||||
|
||||
await waitFor(() => {
|
||||
const select = screen.getByRole("combobox") as HTMLSelectElement;
|
||||
expect(select.value).toBe("origin");
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("tab", { name: /Pull Requests/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(apiFetchGitHubPulls).toHaveBeenCalledWith("dustinbyrne", "kb", 30);
|
||||
expect(screen.getByText("Test PR")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("displays PR list with branch info", async () => {
|
||||
vi.mocked(fetchGitRemotes).mockResolvedValueOnce(singleRemote);
|
||||
vi.mocked(apiFetchGitHubPulls).mockResolvedValueOnce(mockPulls);
|
||||
|
||||
Reference in New Issue
Block a user