Files
kandev/apps/web/components/task-create-dialog-helpers.test.ts
T
Henry f441399441 feat(tasks): show and set task priority on the kanban board (#3333)
Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
2026-09-05 10:15:19 +00:00

299 lines
8.9 KiB
TypeScript

import { describe, expect, it, vi, beforeEach } from "vitest";
import {
autoSelectBranch,
buildCreateTaskPayload,
buildRepositoriesPayload,
findUnresolvedProviderRemote,
shouldShowTaskTitleField,
validateCreateInputs,
} from "./task-create-dialog-helpers";
import type { TaskRemoteRepoRow } from "./task-create-dialog-types";
const STORAGE_KEYS = { LAST_BRANCH: "kandev.dialog.lastBranch" } as const;
// The agent-bearing fields every buildCreateTaskPayload case needs but none of
// them is asserting; each test spreads these and overrides only what it checks.
const AGENT_PAYLOAD_DEFAULTS = {
workspaceId: "ws-1",
effectiveWorkflowId: "wf-1",
repositoriesPayload: [],
agentProfileId: "agent-1",
executorId: "executor-1",
executorProfileId: "profile-1",
withAgent: true,
};
beforeEach(() => {
localStorage.clear();
});
describe("autoSelectBranch", () => {
const branches = [
{ name: "main", type: "local" as const },
{ name: "feature", type: "local" as const },
];
it("prefers a store-backed branch over a divergent localStorage branch", () => {
const setBranch = vi.fn();
localStorage.setItem(STORAGE_KEYS.LAST_BRANCH, JSON.stringify("feature"));
autoSelectBranch(branches, setBranch, { lastUsedBranch: "main" });
expect(setBranch).toHaveBeenCalledWith("main");
});
it("uses the backend last-used branch before settings finish loading", () => {
const setBranch = vi.fn();
autoSelectBranch(branches, setBranch, {
lastUsedBranch: "feature",
userSettingsLoaded: false,
});
expect(setBranch).toHaveBeenCalledWith("feature");
});
it("uses the backend last-used branch when browser storage is stale", () => {
const setBranch = vi.fn();
localStorage.setItem(STORAGE_KEYS.LAST_BRANCH, JSON.stringify("deleted"));
autoSelectBranch(branches, setBranch, {
lastUsedBranch: "feature",
userSettingsLoaded: true,
});
expect(setBranch).toHaveBeenCalledWith("feature");
});
it("defers preferred fallback while user settings are still loading", () => {
const setBranch = vi.fn();
autoSelectBranch(branches, setBranch, { userSettingsLoaded: false });
expect(setBranch).not.toHaveBeenCalled();
});
it("falls back to preferred branch after user settings have loaded without a valid last-used branch", () => {
const setBranch = vi.fn();
autoSelectBranch(branches, setBranch, { userSettingsLoaded: true });
expect(setBranch).toHaveBeenCalledWith("main");
});
it("ignores a stale localStorage branch after user settings have loaded", () => {
const setBranch = vi.fn();
localStorage.setItem(STORAGE_KEYS.LAST_BRANCH, JSON.stringify("feature"));
autoSelectBranch(branches, setBranch, { userSettingsLoaded: true });
expect(setBranch).toHaveBeenCalledWith("main");
});
it("matches remote branch display names for store-backed last-used branch", () => {
const setBranch = vi.fn();
autoSelectBranch([{ name: "feature", type: "remote" as const, remote: "origin" }], setBranch, {
lastUsedBranch: "origin/feature",
userSettingsLoaded: true,
});
expect(setBranch).toHaveBeenCalledWith("origin/feature");
});
it("does not pick a branch from an empty branch list", () => {
const setBranch = vi.fn();
autoSelectBranch([], setBranch, { lastUsedBranch: "main", userSettingsLoaded: true });
expect(setBranch).not.toHaveBeenCalled();
});
});
describe("shouldShowTaskTitleField", () => {
it.each([
{
name: "started edit",
isCreateMode: false,
isEditMode: true,
isTaskStarted: true,
expected: true,
},
{
name: "new task",
isCreateMode: true,
isEditMode: false,
isTaskStarted: false,
expected: true,
},
{
name: "create from running task",
isCreateMode: true,
isEditMode: false,
isTaskStarted: true,
expected: false,
},
{
name: "session",
isCreateMode: false,
isEditMode: false,
isTaskStarted: false,
expected: false,
},
])(
"returns $expected for $name mode",
({ isCreateMode, isEditMode, isTaskStarted, expected }) => {
expect(shouldShowTaskTitleField(isCreateMode, isEditMode, isTaskStarted)).toBe(expected);
},
);
});
describe("buildRepositoriesPayload — inspected provider repository", () => {
it("sends the exact inspected clone URL and complete Data Center descriptor", () => {
const remoteRow = {
key: "remote-0",
url: "https://bitbucket.example.test/bitbucket/projects/PLATFORM/repos/web/pull-requests/42",
remoteUrl: "https://bitbucket.example.test/bitbucket/scm/PLATFORM/web.git",
branch: "feature/dc",
source: "paste",
provider: "bitbucket",
providerHost: "https://bitbucket.example.test/bitbucket",
providerRepoId: "web-42",
providerOwner: "PLATFORM",
providerName: "web",
prNumber: 42,
prBaseBranch: "main",
prHeadBranch: "feature/dc",
} as unknown as TaskRemoteRepoRow;
const payload = buildRepositoriesPayload({
useRemote: true,
remoteRepos: [remoteRow],
repositories: [],
discoveredRepositories: [],
});
expect(payload).toEqual([
expect.objectContaining({
repository_id: "",
remote_url: "https://bitbucket.example.test/bitbucket/scm/PLATFORM/web.git",
provider: "bitbucket",
provider_host: "https://bitbucket.example.test/bitbucket",
provider_repo_id: "web-42",
provider_owner: "PLATFORM",
provider_name: "web",
base_branch: "main",
checkout_branch: "feature/dc",
pr_number: 42,
}),
]);
expect(payload[0]).not.toHaveProperty("github_url");
});
});
describe("findUnresolvedProviderRemote", () => {
it("blocks a plugin URL until inspection attaches provider identity", () => {
const row = {
key: "remote-0",
url: "https://bitbucket.example.test/projects/TEAM/repos/app",
branch: "",
source: "paste",
} as TaskRemoteRepoRow;
const matches = (url: string) => url.includes("bitbucket.example.test");
expect(findUnresolvedProviderRemote([row], matches)).toBe(row);
expect(findUnresolvedProviderRemote([{ ...row, provider: "bitbucket" }], matches)).toBeNull();
});
});
describe("auto-title creation helpers", () => {
const base = {
workspaceId: "ws-1",
effectiveWorkflowId: "wf-1",
repositories: [{ key: "repo-1", repositoryId: "repo-1", branch: "main" }],
agentProfileId: "agent-1",
noRepository: false,
};
it("requires a prompt instead of a title when auto-title mode is enabled", () => {
expect(
validateCreateInputs({ ...base, trimmedTitle: "", trimmedDescription: "", autoTitle: true }),
).toBe(false);
expect(
validateCreateInputs({
...base,
trimmedTitle: "",
trimmedDescription: "Describe the work",
autoTitle: true,
}),
).toBe(true);
});
it("omits the manual title and opts into backend provisional naming", () => {
const payload = buildCreateTaskPayload({
...AGENT_PAYLOAD_DEFAULTS,
trimmedTitle: "ignored",
trimmedDescription: "Fix the login flow",
autoTitle: true,
});
expect(payload).toMatchObject({ auto_title: true });
expect(payload).not.toHaveProperty("title");
});
it("includes autopilot only when the create form opts in", () => {
const payload = buildCreateTaskPayload({
...AGENT_PAYLOAD_DEFAULTS,
trimmedTitle: "Autonomous task",
trimmedDescription: "Run the migration",
autopilot: true,
});
expect(payload.autopilot).toBe(true);
});
it("keeps autopilot off when the create form does not opt in", () => {
const payload = buildCreateTaskPayload({
...AGENT_PAYLOAD_DEFAULTS,
trimmedTitle: "Manual task",
trimmedDescription: "Run the migration",
autopilot: false,
});
expect(payload.autopilot).toBeUndefined();
});
});
describe("buildCreateTaskPayload dependencies", () => {
const base = {
...AGENT_PAYLOAD_DEFAULTS,
trimmedTitle: "Second step",
trimmedDescription: "Runs after the first",
};
it("sends the selected predecessors as blocked_by", () => {
const payload = buildCreateTaskPayload({ ...base, blockedBy: ["task-a", "task-b"] });
expect(payload.blocked_by).toEqual(["task-a", "task-b"]);
});
it("omits blocked_by when nothing is selected", () => {
expect(buildCreateTaskPayload({ ...base, blockedBy: [] }).blocked_by).toBeUndefined();
expect(buildCreateTaskPayload(base).blocked_by).toBeUndefined();
});
});
describe("buildCreateTaskPayload priority", () => {
const base = {
...AGENT_PAYLOAD_DEFAULTS,
trimmedTitle: "Fix the bug",
trimmedDescription: "",
};
it("submits the selected priority token", () => {
expect(buildCreateTaskPayload({ ...base, priority: "critical" }).priority).toBe("critical");
});
it("defaults to medium when no priority is selected", () => {
expect(buildCreateTaskPayload(base).priority).toBe("medium");
});
});