Файли
openidea/src/features/run/services.test.ts
T
arookieofcтаa-rookie-of-C-language d277fc1522 fix(services): keep non-Spring-Boot run configurations in the Services tree (#443)
Services listed run configurations only while their session was alive (or when their whole type was added), so after a restart only Spring Boot configurations were back. Configurations that ran are now remembered per project and listed under their type, dimmed until they run again; ids of deleted configurations are ignored.

Fixes #388

Co-authored-by: a-rookie-of-C-language <[email protected]>
2026-10-30 19:29:35 +08:00

154 рядки
6.2 KiB
TypeScript

import { beforeEach, describe, expect, it } from "vitest";
import type { RunConfig } from "@/platform/ipc";
import { DEFAULT_TYPES, readRan, readTypes, runServiceItems, sessionDetail, sessionStatus } from "./services";
import { startedPort } from "./serverPort";
import type { RunSession } from "./store";
const config = (id: string, kind: string): RunConfig => ({
id,
name: id,
kind,
command: kind,
args: [],
cwd: null,
env: {},
detected: true,
isTest: false,
debugAdapter: null,
});
const session = (
id: string,
c: RunConfig,
status: RunSession["status"],
startedAt: number,
extra: Partial<RunSession> = {},
): RunSession => ({
id,
config: c,
runId: null,
status,
exitCode: null,
startedAt,
endedAt: null,
stdinClosed: false,
...extra,
});
describe("run configurations in Services", () => {
const build = config("build", "cargo");
const test = config("test", "cargo");
const dev = config("dev", "npm");
const scratch = config("context:file", "python");
it("lists added types fully and other types only with their runs", () => {
const sessions = [
session("s1", dev, "finished", 1),
session("s2", dev, "running", 2),
session("s3", scratch, "failed", 3, { exitCode: 2 }),
];
const items = runServiceItems([build, test, dev], sessions, ["cargo"]);
expect(items.map((i) => [i.key, i.label, i.detail])).toEqual([
["run:type:cargo", "Cargo", undefined],
["run:type:npm", "npm", "1 running"],
["run:type:python", "Python", undefined],
]);
expect(items[0]!.children!.map((c) => [c.key, c.detail, c.dim])).toEqual([
["run:config:build", undefined, true],
["run:config:test", undefined, true],
]);
// The newest session of a configuration decides its state.
expect(items[1]!.children!.map((c) => [c.label, c.detail, c.dim, c.icon])).toEqual([
["dev", "Running", false, "run"],
]);
expect(items[2]!.children!.map((c) => [c.key, c.detail])).toEqual([["run:config:context:file", "Failed (exit 2)"]]);
});
it("has no groups without added types or sessions", () => {
expect(runServiceItems([build], [], [])).toEqual([]);
});
it("describes session states", () => {
expect(sessionStatus(undefined)).toBeUndefined();
expect(sessionStatus(session("a", build, "starting", 0))).toBe("Starting");
expect(sessionStatus(session("a", build, "failed", 0, { stopped: true }))).toBe("Stopped");
expect(sessionStatus(session("a", build, "finished", 0))).toBe("Finished");
});
it("shows Spring Boot by default, hidden while the project has none", () => {
const app = config("spring:app", "spring-boot");
expect(runServiceItems([build], [], DEFAULT_TYPES)).toEqual([]);
const items = runServiceItems([build, app], [], DEFAULT_TYPES);
expect(items.map((i) => [i.key, i.label, i.expanded])).toEqual([["run:type:spring-boot", "Spring Boot", true]]);
expect(items[0]!.children!.map((c) => [c.label, c.detail, c.dim])).toEqual([["spring:app", undefined, true]]);
});
it("shows the HTTP port of a running application instead of its state", () => {
const app = config("spring:app", "spring-boot");
const running = session("s1", app, "running", 1, { httpPort: 8080 });
expect(sessionDetail(running)).toBe(":8080");
expect(sessionDetail(session("s1", app, "running", 1))).toBe("Running");
expect(sessionDetail(session("s1", app, "finished", 1, { httpPort: 8080, stopped: true }))).toBe("Stopped");
expect(runServiceItems([app], [running], DEFAULT_TYPES)[0]!.children![0]!.detail).toBe(":8080");
});
});
describe("types added to Services", () => {
beforeEach(() => localStorage.clear());
it("defaults to Spring Boot and persists the user's list per project", () => {
expect(readTypes("/p")).toEqual(["spring-boot"]);
localStorage.setItem("openidea.services.runTypes:/p", JSON.stringify(["npm"]));
expect(readTypes("/p")).toEqual(["npm"]);
localStorage.setItem("openidea.services.runTypes:/p", "[]");
expect(readTypes("/p")).toEqual([]);
expect(readTypes("/q")).toEqual(["spring-boot"]);
});
});
describe("configurations that ran", () => {
const dev = config("dev", "npm");
const scratch = config("context:file", "python");
beforeEach(() => localStorage.clear());
it("persists the ran configuration ids per project", () => {
expect(readRan("/p")).toEqual([]);
localStorage.setItem("openidea.services.ran:/p", JSON.stringify(["dev"]));
expect(readRan("/p")).toEqual(["dev"]);
expect(readRan("/q")).toEqual([]);
});
it("lists previously run configurations without a session", () => {
const items = runServiceItems([dev, scratch], [], [], ["dev", "context:file"]);
expect(items.map((i) => [i.key, i.label])).toEqual([
["run:type:npm", "npm"],
["run:type:python", "Python"],
]);
expect(items[0]!.children!.map((c) => [c.label, c.dim])).toEqual([["dev", true]]);
expect(items[1]!.children!.map((c) => c.key)).toEqual(["run:config:context:file"]);
});
it("ignores ran ids whose configuration is gone and merges sessions", () => {
const gone = runServiceItems([dev], [], [], ["missing"]);
expect(gone).toEqual([]);
const running = session("s1", dev, "running", 1);
const items = runServiceItems([dev], [running], [], ["dev"]);
expect(items).toHaveLength(1);
expect(items[0]!.children!.map((c) => [c.label, c.detail])).toEqual([["dev", "Running"]]);
});
});
describe("embedded web server port", () => {
it("reads the port from the started line", () => {
expect(startedPort("TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'")).toBe(8080);
expect(startedPort("Tomcat started on port(s): 8081 (http) with context path ''")).toBe(8081);
expect(startedPort("o.s.b.web.embedded.netty.NettyWebServer : Netty started on port 9090")).toBe(9090);
expect(startedPort("Netty started on port(s): 9091")).toBe(9091);
expect(startedPort("Undertow started on port(s) 8082 (http)")).toBe(8082);
expect(startedPort("Jetty started on port 8083 (http/1.1) with context path '/'")).toBe(8083);
expect(startedPort("Tomcat initialized with port 8080 (http)")).toBeNull();
expect(startedPort("Started DemoApplication in 1.2 seconds")).toBeNull();
expect(startedPort("Tomcat started on port 99999")).toBeNull();
});
});