N picked the globally freshest project by git-mtime, ignoring the session the operator was actually on. Plain n's spawn form already seeds from the focused session (spawnSimilarProjectSeed); fast-forward now matches that, falling back to recency only when nothing is focused.
120 lines
4.0 KiB
TypeScript
120 lines
4.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
buildFastForwardRequest,
|
|
fastForwardSeed,
|
|
resolveFastForward,
|
|
} from "./fastForward.js";
|
|
import type { Project, SpawnPreset } from "./types.js";
|
|
|
|
const presets: SpawnPreset[] = [
|
|
{ id: "custom", label: "Custom…", command: "" },
|
|
{ id: "claude", label: "Claude", command: "claude" },
|
|
];
|
|
|
|
function project(overrides: Partial<Project>): Project {
|
|
return {
|
|
path: "/repos/p",
|
|
name: "p",
|
|
last_activity: new Date().toISOString(),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("resolveFastForward (#67)", () => {
|
|
it("resolves every default from the most recent project", () => {
|
|
const projects = [
|
|
project({ path: "/repos/old", name: "old", last_activity: "2020-01-01T00:00:00Z" }),
|
|
project({
|
|
path: "/repos/fresh",
|
|
name: "fresh",
|
|
base_repo: "/repos/fresh",
|
|
last_command: "aider",
|
|
}),
|
|
];
|
|
const res = resolveFastForward(projects, presets, "amber-falcon");
|
|
expect(res.project?.path).toBe("/repos/fresh");
|
|
expect(res.command).toBe("aider");
|
|
expect(res.name).toBe("amber-falcon");
|
|
|
|
const req = buildFastForwardRequest(res)!;
|
|
expect(req.cwd).toBe("/repos/fresh");
|
|
expect(req.command).toBe("aider");
|
|
expect(req.metadata).toEqual({ name: "amber-falcon" });
|
|
expect(req.worktree).toEqual({
|
|
base_repo: "/repos/fresh",
|
|
branch: "amber-falcon",
|
|
create_branch: true,
|
|
});
|
|
});
|
|
|
|
it("prefers the focused session's project over the freshest one", () => {
|
|
const projects = [
|
|
project({ path: "/repos/fresh", name: "fresh", last_command: "claude" }),
|
|
project({
|
|
path: "/repos/current",
|
|
name: "current",
|
|
last_activity: "2020-01-01T00:00:00Z",
|
|
last_command: "zsh",
|
|
}),
|
|
];
|
|
const res = resolveFastForward(projects, presets, "n", "/repos/current");
|
|
expect(res.project?.path).toBe("/repos/current");
|
|
expect(res.command).toBe("zsh");
|
|
});
|
|
|
|
it("falls back to the freshest project when the focused cwd is unknown", () => {
|
|
const projects = [
|
|
project({ path: "/repos/old", name: "old", last_activity: "2020-01-01T00:00:00Z" }),
|
|
project({ path: "/repos/fresh", name: "fresh" }),
|
|
];
|
|
const res = resolveFastForward(projects, presets, "n", "/repos/gone");
|
|
expect(res.project?.path).toBe("/repos/fresh");
|
|
});
|
|
|
|
it("falls back to the first non-custom preset when there is no last command", () => {
|
|
const res = resolveFastForward([project({ last_command: undefined })], presets, "n");
|
|
expect(res.command).toBe("claude");
|
|
});
|
|
|
|
it("treats an empty last command as no command", () => {
|
|
const res = resolveFastForward([project({ last_command: "" })], [presets[0]], "n");
|
|
expect(res.command).toBeUndefined();
|
|
expect(buildFastForwardRequest(res)).toBeNull();
|
|
});
|
|
|
|
it("resolves nothing when no projects are known yet", () => {
|
|
const res = resolveFastForward([], presets, "n");
|
|
expect(res.project).toBeUndefined();
|
|
expect(buildFastForwardRequest(res)).toBeNull();
|
|
});
|
|
|
|
it("downgrades to a draft when the generated name is missing", () => {
|
|
const res = resolveFastForward([project({ last_command: "claude" })], presets);
|
|
expect(res.name).toBeUndefined();
|
|
expect(buildFastForwardRequest(res)).toBeNull();
|
|
});
|
|
|
|
it("seeds the fallback draft with whatever did resolve", () => {
|
|
const res = resolveFastForward(
|
|
[project({ path: "/repos/p", base_repo: "/repos/p", last_command: "aider" })],
|
|
presets,
|
|
);
|
|
expect(fastForwardSeed(res)).toEqual({
|
|
cwd: "/repos/p",
|
|
command: "aider",
|
|
name: "",
|
|
worktreeEnabled: true,
|
|
worktreeBranch: undefined,
|
|
});
|
|
});
|
|
|
|
it("leaves the worktree default alone when not even a project resolved", () => {
|
|
// Not `false`: the form defaults the toggle on and a false would stick,
|
|
// so the fallback draft would disagree with a normally-opened draft and
|
|
// with the TUI's fast path.
|
|
const seed = fastForwardSeed(resolveFastForward([], presets));
|
|
expect(seed.worktreeEnabled).toBeUndefined();
|
|
expect(seed.cwd).toBe("");
|
|
});
|
|
});
|