Imported GitHub issues can now adopt their source issue as the tracking issue without changing ordinary task defaults. - Add the project-scoped githubLinkImportedIssuesToTracking setting with docs, Settings UI, and defaults. - Honor the import-only setting across dashboard, CLI, and extension GitHub issue import paths. - Cover saved settings and import tracking behavior with CLI, dashboard, and parity tests. - Add a minor changeset for the published CLI package. Files changed: .changeset/fn-7367-github-import-tracking.md | 7 +++ docs/settings-reference.md | 3 +- packages/cli/src/__tests__/extension.test.ts | 73 ++++++++++++++++++++++ .../task-command-github-import-tracking.test.ts | 16 +++++ packages/cli/src/commands/__tests__/task.test.ts | 28 +++++++++ packages/cli/src/commands/task.ts | 45 +++++++------ packages/cli/src/extension.ts | 45 +++++++------ .../core/src/__tests__/settings-parity.test.ts | 4 ++ packages/core/src/settings-schema.ts | 1 + packages/core/src/types.ts | 6 ++ .../app/__tests__/settings-save-split.test.ts | 19 ++++++ .../dashboard/app/components/SettingsModal.tsx | 6 ++ .../__tests__/SettingsModal.general.test.tsx | 59 +++++++++++++++++ .../__tests__/SettingsModal.test-harness.tsx | 1 + .../settings/sections/GeneralSection.tsx | 9 +++ .../dashboard/src/__tests__/routes-github.test.ts | 45 +++++++++++++ .../dashboard/src/routes/register-git-github.ts | 7 +++ 17 files changed, 325 insertions(+), 49 deletions(-) Fusion-Task-Id: FN-7367 Fusion-Task-Lineage: 43671cdf-7e0d-4b7a-9ba2-54c9a367dfec Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
136 lines
4.5 KiB
TypeScript
136 lines
4.5 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const taskStoreCtorMock = vi.hoisted(() => vi.fn());
|
|
const runGhJsonAsyncMock = vi.hoisted(() => vi.fn());
|
|
const resolveProjectMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock("@fusion/core", async (importActual) => {
|
|
const actual = await importActual<typeof import("@fusion/core")>();
|
|
return {
|
|
...actual,
|
|
TaskStore: taskStoreCtorMock,
|
|
};
|
|
});
|
|
|
|
vi.mock("@fusion/core/gh-cli", () => ({
|
|
isGhAvailable: vi.fn(() => true),
|
|
isGhAuthenticated: vi.fn(() => true),
|
|
runGhJsonAsync: runGhJsonAsyncMock,
|
|
getGhErrorMessage: vi.fn((error: unknown) => (error instanceof Error ? error.message : String(error))),
|
|
}));
|
|
|
|
vi.mock("../project-context.js", () => ({
|
|
resolveProject: resolveProjectMock,
|
|
}));
|
|
|
|
vi.mock("@fusion/dashboard", () => ({
|
|
registerGithubTrackingHook: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@fusion/engine", () => ({
|
|
createFnAgent: vi.fn(),
|
|
runAiMerge: vi.fn(),
|
|
landWorkspaceTask: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@fusion/dashboard/planning", () => ({
|
|
createSession: vi.fn(),
|
|
submitResponse: vi.fn(),
|
|
RateLimitError: class RateLimitError extends Error {},
|
|
SessionNotFoundError: class SessionNotFoundError extends Error {},
|
|
InvalidSessionStateError: class InvalidSessionStateError extends Error {},
|
|
}));
|
|
|
|
import { runTaskImportFromGitHub } from "../commands/task.js";
|
|
|
|
describe("fn task import GitHub tracking defaults", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
resolveProjectMock.mockRejectedValue(new Error("No project context"));
|
|
runGhJsonAsyncMock.mockResolvedValue([
|
|
{
|
|
number: 1,
|
|
title: "Imported Issue",
|
|
body: "Imported issue body",
|
|
html_url: "https://github.com/owner/repo/issues/1",
|
|
labels: [],
|
|
},
|
|
]);
|
|
});
|
|
|
|
function mockStore(options: { projectSettings?: Record<string, unknown>; globalSettings?: Record<string, unknown> } = {}) {
|
|
const createTask = vi.fn().mockImplementation((input) => Promise.resolve({
|
|
id: "FN-001",
|
|
title: input.title,
|
|
description: input.description,
|
|
column: "triage",
|
|
}));
|
|
taskStoreCtorMock.mockImplementation(function () {
|
|
return {
|
|
init: vi.fn().mockResolvedValue(undefined),
|
|
listTasks: vi.fn().mockResolvedValue([]),
|
|
createTask,
|
|
getSettings: vi.fn().mockResolvedValue(options.projectSettings ?? {}),
|
|
getGlobalSettingsStore: vi.fn().mockReturnValue({
|
|
getSettings: vi.fn().mockResolvedValue(options.globalSettings ?? {}),
|
|
}),
|
|
};
|
|
});
|
|
return { createTask };
|
|
}
|
|
|
|
it("sets githubTracking.enabled for fn task import when project tracking defaults are on", async () => {
|
|
const { createTask } = mockStore({ projectSettings: { githubTrackingEnabledByDefault: true } });
|
|
|
|
await runTaskImportFromGitHub("owner/repo", { limit: 1 });
|
|
|
|
expect(createTask).toHaveBeenCalledWith(expect.objectContaining({
|
|
githubTracking: { enabled: true },
|
|
sourceIssue: expect.objectContaining({
|
|
provider: "github",
|
|
repository: "owner/repo",
|
|
issueNumber: 1,
|
|
}),
|
|
}));
|
|
});
|
|
|
|
it("sets githubTracking.enabled for fn task import when global tracking defaults are on", async () => {
|
|
const { createTask } = mockStore({ globalSettings: { githubTrackingDefaultEnabledForNewTasks: true } });
|
|
|
|
await runTaskImportFromGitHub("owner/repo", { limit: 1 });
|
|
|
|
expect(createTask).toHaveBeenCalledWith(expect.objectContaining({
|
|
githubTracking: { enabled: true },
|
|
sourceIssue: expect.objectContaining({ issueNumber: 1 }),
|
|
}));
|
|
});
|
|
|
|
it("sets githubTracking.enabled for fn task import when import linking is on and new-task defaults are off", async () => {
|
|
const { createTask } = mockStore({
|
|
projectSettings: {
|
|
githubTrackingEnabledByDefault: false,
|
|
githubLinkImportedIssuesToTracking: true,
|
|
},
|
|
});
|
|
|
|
await runTaskImportFromGitHub("owner/repo", { limit: 1 });
|
|
|
|
expect(createTask).toHaveBeenCalledWith(expect.objectContaining({
|
|
githubTracking: { enabled: true },
|
|
sourceIssue: expect.objectContaining({ provider: "github", repository: "owner/repo", issueNumber: 1 }),
|
|
}));
|
|
});
|
|
|
|
it("does not force githubTracking for fn task import when tracking defaults are off", async () => {
|
|
const { createTask } = mockStore();
|
|
|
|
await runTaskImportFromGitHub("owner/repo", { limit: 1 });
|
|
|
|
expect(createTask).toHaveBeenCalledWith(expect.not.objectContaining({
|
|
githubTracking: expect.anything(),
|
|
}));
|
|
});
|
|
});
|