Файли
openidea/src/features/run/serviceOrder.test.ts
T
wangxgaтаwangxga a3cd99db1f feat(services): reorder services within a run configuration type (#345)
Services followed configuration detection order, so frequently used services could not be kept together. Services can now be dragged before or after a sibling, with an insertion indicator and edge scrolling, or moved with Move Up/Down from the toolbar and context menu.

The order is stored per project location and type, separately from configuration definitions. New configurations append, windows stay in sync, and a failed save keeps the order for the session with a warning. Sorting in Tree is opt-in, so other trees are unchanged.

Co-authored-by: wangxga <[email protected]>
2026-10-31 01:28:41 +08:00

116 рядки
4.3 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { act, renderHook } from "@testing-library/react";
import { moveServiceOrder, orderServices, readServiceOrder, saveServiceOrder, useServiceOrder } from "./serviceOrder";
beforeEach(() => localStorage.clear());
describe("service display order", () => {
it("orders known IDs, appends new ones and leaves configurations untouched", () => {
const entries = [{ id: "gateway" }, { id: "portal" }, { id: "new" }];
expect(orderServices(entries, ["gone", "portal", "gateway"]).map((x) => x.id)).toEqual([
"portal",
"gateway",
"new",
]);
expect(entries.map((x) => x.id)).toEqual(["gateway", "portal", "new"]);
});
it("moves before or after the target while retaining temporarily hidden IDs", () => {
expect(moveServiceOrder(["a", "hidden", "b"], ["a", "b", "new"], "b", "a", "before")).toEqual([
"b",
"a",
"hidden",
"new",
]);
expect(moveServiceOrder([], ["a", "b", "c"], "a", "b", "after")).toEqual(["b", "a", "c"]);
});
it("ignores missing, self and stale drop targets", () => {
for (const [source, target] of [
["a", "a"],
["gone", "b"],
["a", "gone"],
]) {
expect(moveServiceOrder(["b", "a"], ["a", "b"], source!, target!, "before")).toEqual(["b", "a"]);
}
});
});
describe("service order storage", () => {
it("isolates project locations and normalizes malformed records and duplicate IDs", () => {
localStorage.setItem(
"openidea.services.runOrder:/app",
JSON.stringify({ npm: ["b", "b", 4, "", "a"], python: null }),
);
expect(readServiceOrder("/app")).toEqual({ npm: ["b", "a"] });
expect(readServiceOrder("ssh://other/app")).toEqual({});
localStorage.setItem("openidea.services.runOrder:/app", "broken");
expect(readServiceOrder("/app")).toEqual({});
localStorage.setItem("openidea.services.runOrder:/app", "[]");
expect(readServiceOrder("/app")).toEqual({});
});
it("continues with default order when storage cannot be read", () => {
const read = vi.spyOn(Storage.prototype, "getItem").mockImplementation(() => {
throw new Error("denied");
});
try {
expect(readServiceOrder("/denied")).toEqual({});
} finally {
read.mockRestore();
}
});
it("updates mounted views, survives remounts, and reads changes from other windows", () => {
const { result, unmount } = renderHook(() => useServiceOrder("/persist"));
act(() => {
saveServiceOrder("/persist", { npm: ["b", "a"] });
});
expect(result.current).toEqual({ npm: ["b", "a"] });
unmount();
const next = renderHook(() => useServiceOrder("/persist"));
expect(next.result.current).toEqual({ npm: ["b", "a"] });
act(() => {
localStorage.setItem("openidea.services.runOrder:/persist", '{"npm":["a","b"]}');
window.dispatchEvent(
new StorageEvent("storage", { key: "openidea.services.runOrder:/persist", storageArea: localStorage }),
);
});
expect(next.result.current).toEqual({ npm: ["a", "b"] });
act(() => {
localStorage.clear();
window.dispatchEvent(new StorageEvent("storage", { key: null, storageArea: localStorage }));
});
expect(next.result.current).toEqual({});
});
it("switches projects without leaking the previous project's order", () => {
saveServiceOrder("/one", { npm: ["b", "a"] });
saveServiceOrder("/two", { python: ["y", "x"] });
const { result, rerender } = renderHook(({ location }) => useServiceOrder(location), {
initialProps: { location: "/one" },
});
expect(result.current).toEqual({ npm: ["b", "a"] });
rerender({ location: "/two" });
expect(result.current).toEqual({ python: ["y", "x"] });
});
it("keeps the in-memory order when writing storage fails", () => {
const { result, unmount } = renderHook(() => useServiceOrder("/unavailable"));
const write = vi.spyOn(Storage.prototype, "setItem").mockImplementation(() => {
throw new Error("quota");
});
try {
act(() => {
expect(saveServiceOrder("/unavailable", { npm: ["b", "a"] })).toBe(false);
});
expect(result.current).toEqual({ npm: ["b", "a"] });
unmount();
const next = renderHook(() => useServiceOrder("/unavailable"));
expect(next.result.current).toEqual({ npm: ["b", "a"] });
} finally {
write.mockRestore();
}
});
});