Files
paseo/packages/app/src/add-project-flow/model.test.ts
T
Mohamed Boudra 943d03ad99 fix(projects): separate registration from workspace setup
Adding or cloning a project now registers it and opens workspace setup
instead of creating a workspace implicitly. Keep Add Project independently
mounted from Search so closing one cannot control the other.
2026-07-15 23:13:55 +02:00

201 lines
6.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
backAddProjectPage,
chooseAddProjectHost,
currentAddProjectPage,
moveAddProjectActiveIndex,
moveAddProjectSelection,
openAddProjectFlow,
openDirectorySearchPage,
openGithubLocationPage,
openNewDirectoryNamePage,
openNewDirectoryParentPage,
setAddProjectActiveIndex,
setAddProjectPageInput,
setNewDirectoryName,
type AddProjectHost,
} from "./model";
import {
buildAddProjectMethods,
buildCloneLocationOptions,
buildManualGithubRepositoryChoices,
} from "./options";
const HOST: AddProjectHost = {
serverId: "host-1",
label: "Local",
canAddProject: true,
canBrowse: true,
canCloneGithubRepositories: true,
canSearchGithubRepositories: true,
canCreateDirectory: true,
};
describe("Add Project navigation", () => {
it("skips a single connected host without adding it to history", () => {
const state = openAddProjectFlow({ hosts: [HOST] });
expect(currentAddProjectPage(state)).toEqual({
kind: "method",
hostId: "host-1",
query: "",
activeIndex: 0,
error: null,
});
expect(backAddProjectPage(state)).toBeNull();
});
it("restores page input and selection after Back", () => {
const secondHost = { ...HOST, serverId: "host-2", label: "Remote" };
let state = openAddProjectFlow({ hosts: [HOST, secondHost] });
state = setAddProjectPageInput(state, "rem");
state = setAddProjectActiveIndex(state, 1);
state = chooseAddProjectHost(state, secondHost.serverId);
state = openDirectorySearchPage(state, secondHost.serverId);
state = backAddProjectPage(state) ?? state;
state = backAddProjectPage(state) ?? state;
expect(currentAddProjectPage(state)).toEqual({
kind: "host",
query: "rem",
activeIndex: 1,
error: null,
});
});
it("wraps keyboard selection in both directions", () => {
expect(moveAddProjectActiveIndex(2, 3, "next")).toBe(0);
expect(moveAddProjectActiveIndex(0, 3, "previous")).toBe(2);
expect(moveAddProjectSelection(0, [true, false, true], "next")).toBe(2);
});
it("restores a directory name after returning to and reselecting its parent", () => {
let state = openAddProjectFlow({ hosts: [HOST] });
state = openNewDirectoryParentPage(state, HOST.serverId);
state = openNewDirectoryNamePage(state, HOST.serverId, "~/dev");
state = setNewDirectoryName(state, "command-center");
state = backAddProjectPage(state) ?? state;
state = openNewDirectoryNamePage(state, HOST.serverId, "~/dev");
expect(currentAddProjectPage(state)).toMatchObject({
kind: "new-directory-name",
parentPath: "~/dev",
name: "command-center",
});
});
it("restores the GitHub destination query and active parent when reopening a repository", () => {
const repository = {
id: "repo-1",
nameWithOwner: "getpaseo/paseo",
cloneUrl: "git@github.com:getpaseo/paseo.git",
description: null,
visibility: "public",
updatedAt: null,
};
let state = openAddProjectFlow({ hosts: [HOST] });
state = openGithubLocationPage(state, HOST.serverId, repository);
state = setAddProjectPageInput(state, "~/dev");
state = setAddProjectActiveIndex(state, 2);
state = backAddProjectPage(state) ?? state;
state = openGithubLocationPage(state, HOST.serverId, repository);
expect(currentAddProjectPage(state)).toMatchObject({
kind: "github-location",
query: "~/dev",
activeIndex: 2,
});
});
});
describe("Add Project options", () => {
it("keeps host-upgrade methods discoverable while hiding local-only Browse", () => {
expect(
buildAddProjectMethods({
...HOST,
canBrowse: false,
canCloneGithubRepositories: false,
canSearchGithubRepositories: false,
canCreateDirectory: false,
}),
).toEqual([
{
id: "directory-search",
label: "Search for directory",
description: "Find a directory on Local",
},
{
id: "github",
label: "Clone from GitHub",
description: "Update this host to clone GitHub repositories",
disabled: true,
},
{
id: "new-directory",
label: "New directory",
description: "Update this host to create directories",
disabled: true,
},
]);
});
it("offers manual URL and protocol-specific owner/repo clone choices", () => {
expect(buildManualGithubRepositoryChoices("git@github.com:getpaseo/paseo.git")).toEqual([
expect.objectContaining({
id: "manual:git@github.com:getpaseo/paseo.git",
nameWithOwner: "getpaseo/paseo",
cloneUrl: "git@github.com:getpaseo/paseo.git",
}),
]);
expect(buildManualGithubRepositoryChoices("getpaseo/paseo")).toEqual([
expect.objectContaining({ cloneProtocol: "https", cloneUrl: "getpaseo/paseo" }),
expect.objectContaining({ cloneProtocol: "ssh", cloneUrl: "getpaseo/paseo" }),
]);
expect(buildManualGithubRepositoryChoices("paseo")).toEqual([]);
});
it("shows final clone paths while retaining parent paths as values", () => {
expect(
buildCloneLocationOptions({
parents: ["~/dev", "~/workspace"],
repositoryName: "paseo",
existingPaths: ["~/workspace/paseo"],
}),
).toEqual([
{
id: "~/dev",
path: "~/dev",
displayPath: "~/dev/paseo",
secondaryText: "Parent directory: ~/dev",
disabled: false,
},
{
id: "~/workspace",
path: "~/workspace",
displayPath: "~/workspace/paseo",
secondaryText: "Already exists",
disabled: true,
},
]);
});
it("shows equivalent absolute-home and tilde destinations only once", () => {
expect(
buildCloneLocationOptions({
parents: ["/Users/moboudra/dev", "~/dev"],
repositoryName: "dotfiles",
existingPaths: [],
}),
).toEqual([
{
id: "/Users/moboudra/dev",
path: "/Users/moboudra/dev",
displayPath: "/Users/moboudra/dev/dotfiles",
secondaryText: "Parent directory: /Users/moboudra/dev",
disabled: false,
},
]);
});
});