Files
kandev/apps/web/components/task-create-dialog-computed.test.ts
T
2026-08-05 21:21:51 +01:00

230 lines
6.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { ExecutorProfile } from "@/lib/types/http";
import {
computeHasRepositorySelection,
computeSelectedRepoCount,
filterCompatibleAgentProfiles,
} from "./task-create-dialog-computed";
import type { DialogFormState } from "@/components/task-create-dialog-types";
import type { AgentProfileOption } from "@/lib/state/slices/settings/types";
const URL_A = "github.com/a/b";
const URL_B = "github.com/c/d";
// Minimal fs stub for the two pure helpers below. Each function only reads
// `noRepository`, `useRemote`, `remoteRepos`, and `repositories`, so we cast a
// partial through `unknown` to avoid having to materialise the full
// DialogFormState surface in tests.
function fsStub(overrides: {
noRepository?: boolean;
useRemote?: boolean;
remoteRepos?: Array<{ url?: string; branch?: string }>;
repositories?: Array<{ repositoryId?: string; localPath?: string; branch?: string }>;
}): DialogFormState {
return {
noRepository: false,
useRemote: false,
remoteRepos: [],
repositories: [],
...overrides,
} as unknown as DialogFormState;
}
describe("computeHasRepositorySelection", () => {
it("returns true in no-repository mode", () => {
expect(computeHasRepositorySelection(fsStub({ noRepository: true }))).toBe(true);
});
it("returns true when any workspace/local row is set", () => {
expect(computeHasRepositorySelection(fsStub({ repositories: [{ repositoryId: "r-1" }] }))).toBe(
true,
);
expect(
computeHasRepositorySelection(fsStub({ repositories: [{ localPath: "/tmp/repo" }] })),
).toBe(true);
});
it("Remote mode: returns true when ANY row has a non-empty URL", () => {
// First row empty, second row populated — the previous implementation
// only inspected `remoteRepos[0]` and would have returned false here.
expect(
computeHasRepositorySelection(
fsStub({
useRemote: true,
remoteRepos: [{ url: "" }, { url: "github.com/acme/site" }],
}),
),
).toBe(true);
});
it("Remote mode: returns true when only the first row is populated", () => {
expect(
computeHasRepositorySelection(
fsStub({
useRemote: true,
remoteRepos: [{ url: "github.com/acme/site" }],
}),
),
).toBe(true);
});
it("Remote mode: returns false when every row is empty / whitespace", () => {
expect(
computeHasRepositorySelection(
fsStub({
useRemote: true,
remoteRepos: [{ url: "" }, { url: " " }],
}),
),
).toBe(false);
});
it("returns false on the empty form", () => {
expect(computeHasRepositorySelection(fsStub({}))).toBe(false);
});
it("Remote mode: ignores hidden workspace/local rows", () => {
// Toggle-back preserves the workspace/local rows when useRemote flips on
// — they're hidden from the UI and must not count toward the selection
// gate. Otherwise the submit button stays enabled in Remote mode even
// when the user hasn't filled in any URLs.
expect(
computeHasRepositorySelection(
fsStub({
useRemote: true,
repositories: [{ repositoryId: "r-1" }],
remoteRepos: [{ url: "" }],
}),
),
).toBe(false);
});
});
describe("computeSelectedRepoCount", () => {
it("counts workspace/local rows", () => {
expect(
computeSelectedRepoCount(
fsStub({ repositories: [{ repositoryId: "r-1" }, { localPath: "/x" }] }),
),
).toBe(2);
});
it("ignores empty workspace rows", () => {
// Half-filled rows (no repositoryId, no localPath) shouldn't count
// toward the multi-repo gate even though they exist in the array.
expect(computeSelectedRepoCount(fsStub({ repositories: [{ branch: "main" }] }))).toBe(0);
});
it("Remote mode: counts non-empty URL rows only", () => {
// Two remote URLs — without the local rows, this alone trips the gate.
expect(
computeSelectedRepoCount(
fsStub({
useRemote: true,
remoteRepos: [{ url: URL_A }, { url: URL_B }],
}),
),
).toBe(2);
});
it("Remote mode: ignores hidden workspace/local rows", () => {
// useRemote=true hides the workspace/local list — those rows must not
// contribute to the count even if they're still in the form state from
// a prior mode (toggle-back preserves them).
expect(
computeSelectedRepoCount(
fsStub({
useRemote: true,
repositories: [{ repositoryId: "r-1" }, { localPath: "/tmp/x" }],
remoteRepos: [{ url: URL_A }],
}),
),
).toBe(1);
});
it("Remote mode: returns 0 when only hidden workspace/local rows exist", () => {
expect(
computeSelectedRepoCount(
fsStub({
useRemote: true,
repositories: [{ repositoryId: "r-1" }],
remoteRepos: [],
}),
),
).toBe(0);
});
it("returns 0 in no-repository mode regardless of stale rows", () => {
expect(
computeSelectedRepoCount(
fsStub({
noRepository: true,
repositories: [{ repositoryId: "r-1" }],
remoteRepos: [{ url: URL_A }],
useRemote: true,
}),
),
).toBe(0);
});
it("ignores remote rows when useRemote is false", () => {
// The Remote toggle gates the count — switching modes shouldn't smuggle
// the URL rows into the local count.
expect(
computeSelectedRepoCount(
fsStub({
useRemote: false,
remoteRepos: [{ url: URL_A }, { url: URL_B }],
}),
),
).toBe(0);
});
it("trips the multi-repo gate (>1) on a 2-row Remote selection", () => {
// Multi-row Remote selection must trigger the same executor gating as
// a 2-workspace-repo task — the previous implementation only counted
// workspace/local rows, so this would have stayed at 0 even with two
// populated URLs.
const count = computeSelectedRepoCount(
fsStub({
useRemote: true,
remoteRepos: [{ url: "https://github.com/a/b" }, { url: "https://github.com/c/d" }],
}),
);
expect(count).toBe(2);
expect(count > 1).toBe(true);
});
});
describe("filterCompatibleAgentProfiles", () => {
it("removes disabled raw profiles before compatibility and autopick", () => {
const profiles = [
{
id: "enabled",
label: "Agent • Enabled",
agent_id: "a1",
agent_name: "mock-agent",
cli_passthrough: false,
enabled: true,
},
{
id: "disabled",
label: "Agent • Disabled",
agent_id: "a1",
agent_name: "mock-agent",
cli_passthrough: false,
enabled: false,
},
] satisfies AgentProfileOption[];
expect(
filterCompatibleAgentProfiles(
profiles,
{ executor_type: "local", config: {} } as unknown as ExecutorProfile,
true,
[],
).map((p) => p.id),
).toEqual(["enabled"]);
});
});