дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
Projects now open with a user-owned trust decision. The prompt offers Preview in Restricted Mode, Don't Open and Trust Project, with an optional trusted parent folder; preview is the default focus so Enter never grants trust. Restricted projects keep browsing, editing, saving, search and highlighting. Project-controlled execution is denied by default in the backend: run, debug, build, tools, language servers, plugins and Git commands that could honour repository configuration. Trusting a project enables those paths without reopening it, and decisions can be revoked in Settings. Terminals stay available when the user opens one, without project shell settings, environment or virtualenv activation. A shell prompt that runs git in the folder can still honour the repository's core.fsmonitor. Fixes #462
80 рядки
3.1 KiB
TypeScript
80 рядки
3.1 KiB
TypeScript
import { afterEach, describe, expect, it } from "vitest";
|
|
import type { RecentProject } from "@/bindings";
|
|
import { useProject } from "@/platform/project";
|
|
import { filterRecent, tildify } from "./recent";
|
|
import { lastSessionProject, reopenEnabled, trackSession } from "./session";
|
|
|
|
const p = (name: string, path: string, branch: string | null = null): RecentProject => ({
|
|
name,
|
|
path,
|
|
branch,
|
|
openedAtMs: null,
|
|
});
|
|
|
|
const projects = [
|
|
p("acme-api", "/Users/me/dev/acme", "main"),
|
|
p("openidea-ide", "/Users/me/dev/openidea"),
|
|
p("tokio", "/Users/me/oss/tokio"),
|
|
p("notes", "/Volumes/data/acme-notes"),
|
|
];
|
|
|
|
describe("filterRecent", () => {
|
|
it("returns everything in recency order for an empty query", () => {
|
|
const all = filterRecent(projects, " ", "/Users/me");
|
|
expect(all.map((m) => m.project.name)).toEqual(["acme-api", "openidea-ide", "tokio", "notes"]);
|
|
expect(all[0]!.displayPath).toBe("~/dev/acme");
|
|
expect(all[3]!.displayPath).toBe("/Volumes/data/acme-notes");
|
|
});
|
|
|
|
it("puts name matches first, then path matches, with highlight indices", () => {
|
|
const res = filterRecent(projects, "acme", "/Users/me");
|
|
expect(res.map((m) => m.project.name)).toEqual(["acme-api", "notes"]);
|
|
expect(res[0]!.nameMatch).toEqual([0, 1, 2, 3]);
|
|
expect(res[1]!.nameMatch).toBeNull();
|
|
expect(res[1]!.pathMatch).toEqual([14, 15, 16, 17]);
|
|
});
|
|
|
|
it("matches camel-hump / word starts and is case-insensitive", () => {
|
|
expect(filterRecent(projects, "EIde").map((m) => m.project.name)).toEqual(["openidea-ide"]);
|
|
expect(filterRecent(projects, "TOK").map((m) => m.project.name)).toEqual(["tokio"]);
|
|
expect(filterRecent(projects, "zzz")).toEqual([]);
|
|
});
|
|
|
|
it("ignores scattered path subsequences", () => {
|
|
expect(filterRecent([p("x", "/a/bcdefghijklmnopqrstuvwxyz/o/z")], "az")).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("helpers", () => {
|
|
it("abbreviates the home directory", () => {
|
|
expect(tildify("/Users/me/dev", "/Users/me")).toBe("~/dev");
|
|
expect(tildify("/Users/meow", "/Users/me")).toBe("/Users/meow");
|
|
expect(tildify("/x", null)).toBe("/x");
|
|
});
|
|
});
|
|
|
|
describe("session restore", () => {
|
|
afterEach(() => {
|
|
useProject.setState({ project: null });
|
|
localStorage.clear();
|
|
});
|
|
|
|
it("prefers the settings key over the local preference", () => {
|
|
expect(reopenEnabled(null, true)).toBe(true);
|
|
expect(reopenEnabled({}, false)).toBe(false);
|
|
expect(reopenEnabled({ general: { reopenLastProject: false } } as never, true)).toBe(false);
|
|
expect(reopenEnabled({ general: { reopenLastProject: true } } as never, false)).toBe(true);
|
|
});
|
|
|
|
it("remembers the open project and forgets it on explicit close", () => {
|
|
const stop = trackSession();
|
|
useProject.setState({ project: { root: "/w/a", name: "a", trusted: true, isGit: false, settings: {} } });
|
|
expect(lastSessionProject()).toBe("/w/a");
|
|
useProject.setState({ project: { root: "/w/b", name: "b", trusted: true, isGit: false, settings: {} } });
|
|
expect(lastSessionProject()).toBe("/w/b");
|
|
useProject.setState({ project: null });
|
|
expect(lastSessionProject()).toBeNull();
|
|
stop();
|
|
});
|
|
});
|