import { afterEach, describe, expect, it, vi } from "vitest"; import type { VirtualDocument } from "@/platform/ipc"; const JDT = "jdt://contents/lib-1.0.jar/org.example/Lib.class?=demo/%5C/m2%5C/lib-1.0.jar%3Corg.example%28Lib.class"; const TEXT = "// Source code is unavailable, and was generated by the Fernflower decompiler.\npackage org.example;\n\npublic class Lib {\n void run() {}\n}\n"; vi.mock("@/platform/virtualDocs", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, virtualDocument: vi.fn( async (uri: string): Promise => { if (uri !== JDT) throw new Error(`no contents for ${uri}`); return { uri, text: TEXT, languageId: "java", title: "Lib.class", description: "/m2/lib-1.0.jar", decompiled: true, }; }, ), }; }); const { EditorController, isTextInput, useTabs, virtualInput } = await import("./controller"); const { createMemoryBackend } = await import("./backend"); const { initialTabsState } = await import("./model/tabs"); const { serializeSession } = await import("./model/session"); const { useDocMeta } = await import("./model/document"); let ctrl: InstanceType | null = null; afterEach(() => { ctrl?.stop(); ctrl = null; useTabs.setState(initialTabsState(), true); }); const activeTab = () => { const s = useTabs.getState(); const pane = s.panes[s.activePane]!; return pane.tabs.find((t) => t.id === pane.active)!; }; describe("virtual editor input", () => { it("describes a library class as a text input keyed by its URI", () => { const input = virtualInput({ uri: JDT, title: "Lib.class", description: "/m2/lib-1.0.jar", decompiled: true }); expect(input).toMatchObject({ kind: "virtual", key: `virtual:${JDT}`, path: JDT }); expect(input.data).toEqual({ title: "Lib.class", description: "/m2/lib-1.0.jar", decompiled: true }); expect(isTextInput(input)).toBe(true); expect(isTextInput({ kind: "diff", key: "d" })).toBe(false); }); it("opens a URI read-only at the target and keeps it out of the saved session", async () => { ctrl = new EditorController(createMemoryBackend({ "/p/App.java": "class App {}\n", [JDT]: TEXT })); ctrl.start(); await ctrl.openFile("/p/App.java", { focus: false }); await ctrl.openFile(JDT, { selection: { line: 3, col: 13, endLine: 3, endCol: 16 }, focus: false }); const tab = activeTab(); expect(tab.input.kind).toBe("virtual"); expect(tab.input.key).toBe(`virtual:${JDT}`); const slot = ctrl.slotFor(tab.id)!; expect(slot.model.virtual).toBe(true); expect(slot.model.ctx()).toMatchObject({ path: JDT, language: "java", readOnly: true, virtual: true }); expect(useDocMeta.getState()[JDT]).toMatchObject({ readOnly: true, banner: null }); const sel = slot.currentState().selection.main; expect(slot.currentState().sliceDoc(sel.from, sel.to)).toBe("Lib"); // The same URI reuses its tab. await ctrl.openFile(JDT, { focus: false }); expect(Object.values(useTabs.getState().panes).flatMap((p) => p.tabs)).toHaveLength(2); // Navigation history goes back to the file and forward into the library class. await ctrl.back(); expect(activeTab().input.path).toBe("/p/App.java"); await ctrl.forward(); expect(activeTab().input.path).toBe(JDT); const session = serializeSession(useTabs.getState(), {}); expect(JSON.stringify(session)).not.toContain("jdt:"); expect(ctrl.tabMenu(useTabs.getState().activePane, activeTab()).some((i) => "label" in i && i.label === "Reveal in Project")).toBe(false); }); it("does not open a tab when the server has no contents", async () => { ctrl = new EditorController(createMemoryBackend()); ctrl.start(); await ctrl.openFile("jdt://contents/missing.jar/p/X.class", { focus: false }); expect(Object.values(useTabs.getState().panes).flatMap((p) => p.tabs)).toHaveLength(0); }); });