import { createTestingPinia } from "@pinia/testing"; import { getLocalVue } from "@tests/vitest/helpers"; import { mount } from "@vue/test-utils"; import flushPromises from "flush-promises"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { HttpResponse, useServerMock } from "@/api/client/__mocks__"; import MountTarget from "./MarkdownGalaxy.vue"; const { server, http } = useServerMock(); // mock routes vi.mock("@/utils/redirect", () => ({ withPrefix: vi.fn((url) => url), })); vi.mock("@/composables/config", () => ({ useConfig: vi.fn(() => ({ config: { version_major: "test_version", }, isConfigLoaded: true, })), })); vi.mock("@/stores/invocationStore", () => ({ useInvocationStore: vi.fn(() => ({ getInvocationById: () => null, getInvocationLoadError: () => null, isLoadingInvocation: () => false, })), })); vi.mock("@/stores/workflowStore", () => ({ useWorkflowStore: vi.fn(() => ({ fetchWorkflowForInstanceIdCached: vi.fn(() => Promise.resolve()), getStoredWorkflowIdByInstanceId: () => null, })), })); const localVue = getLocalVue(); const pinia = createTestingPinia({ createSpy: vi.fn, stubActions: false }); let postRequests = []; beforeEach(() => { postRequests = []; }); function mountComponent(propsData = {}, options = {}) { const handlers = [ http.get("/api/histories/test_history_id", ({ response }) => response(200).json({ id: "test_history_id", name: "history_name" }), ), ]; if (options.enableHistoryPost) { handlers.push( http.untyped.post("/api/histories", async ({ request }) => { const data = await request.json(); postRequests.push({ url: request.url, data }); return HttpResponse.json({}); }), ); } server.use(...handlers); return mount(MountTarget, { localVue, pinia, propsData, stubs: { FontAwesomeIcon: true, }, }); } describe("MarkdownContainer", () => { it("Renders version", async () => { const version = "test_version"; const wrapper = mountComponent({ content: "generate_galaxy_version()", }); const versionEl = wrapper.find(".galaxy-version"); expect(versionEl.exists()).toBe(true); expect(versionEl.text()).toContain(version); // test collapsing const nolink = wrapper.find("a"); expect(nolink.exists()).toBe(false); const collapse = "Click here to expand/collapse"; await wrapper.setProps({ content: `generate_galaxy_version(collapse="${collapse}")` }); const link = wrapper.find("a"); expect(link.text()).toBe(collapse); const container = wrapper.find(".collapse"); expect(container.attributes("style")).toBe("display: none;"); await link.trigger("click"); // After click, style attribute is removed expect(container.attributes("style")).toBeFalsy(); }); it("Renders time stamp", async () => { const time = new Date(); vi.useFakeTimers(); vi.setSystemTime(time); const wrapper = mountComponent({ content: "generate_time()", }); const version = wrapper.find(".galaxy-time"); expect(version.exists()).toBe(true); expect(version.text()).toBe(time.toUTCString()); vi.useRealTimers(); }); it("Renders history link", async () => { const wrapper = mountComponent( { content: "history_link(history_id=test_history_id)", }, { enableHistoryPost: true, }, ); expect(wrapper.find("a").text()).toBe("Click to Import History: ..."); await flushPromises(); const link = wrapper.find("a"); expect(link.text()).toBe("Click to Import History: history_name"); await link.trigger("click"); await flushPromises(); expect(postRequests.length).toBe(1); expect(postRequests[0].data.history_id).toBe("test_history_id"); const error = wrapper.find(".text-success"); const message = error.find("span"); expect(message.text()).toBe("Successfully Imported History: history_name!"); }); it("Renders history link (with failing import error message)", async () => { const wrapper = mountComponent({ content: "history_link(history_id=test_history_id)", }); await wrapper.find("a").trigger("click"); await flushPromises(); const error = wrapper.find(".text-danger"); const message = error.find("span"); expect(message.text()).toBe("Failed to handle History: history_name!"); }); it("Renders error for invalid directive syntax", async () => { const wrapper = mountComponent({ content: "not_valid_content(", }); const alert = wrapper.find(".alert-danger"); expect(alert.exists()).toBe(true); expect(alert.text()).toContain("The directive provided below is invalid"); }); it("Renders error for invalid component type", async () => { const wrapper = mountComponent({ content: "unknown_component()", }); const alert = wrapper.find(".alert-danger"); expect(alert.text()).toContain("Invalid component type"); }); it("Renders error for missing required label", async () => { const wrapper = mountComponent({ content: "tool_a(input=foo)", labels: [{ type: "input", label: "NotFoo" }], }); const alert = wrapper.find(".alert-danger"); expect(alert.text()).toContain("Invalid component type tool_a"); }); it("Renders info alert if labels exist but no invocation_id is present", async () => { const wrapper = mountComponent({ content: "history_dataset_display(input=foo)", labels: [ { type: "input", label: "foo" }, { type: "output", label: "bar" }, ], }); await flushPromises(); const alert = wrapper.find(".alert-info"); expect(alert.text()).toContain("Data for rendering not yet available for"); }); it("Renders danger alert if more than one label exists", async () => { const wrapper = mountComponent({ content: "history_dataset_display(input=foo, output=bar)", labels: [ { type: "input", label: "foo" }, { type: "output", label: "bar" }, ], }); await flushPromises(); const alert = wrapper.find(".alert-danger"); expect(alert.text()).toMatch(/Invalid or missing label for\s*history_dataset_display/); }); it("Renders loading span while invocation is loading", async () => { const { useInvocationStore } = await import("@/stores/invocationStore"); vi.mocked(useInvocationStore).mockReturnValueOnce({ getInvocationById: () => null, getInvocationLoadError: () => null, isLoadingInvocation: vi.fn(() => true), }); const wrapper = mountComponent({ content: "history_dataset_display(invocation_id=123, input=foo)", labels: [ { type: "input", label: "foo" }, { type: "output", label: "bar" }, ], }); await flushPromises(); expect(wrapper.findComponent({ name: "LoadingSpan" }).exists()).toBe(true); }); it("Handles invocation fetching and workflow ID resolution", async () => { const invocation = { workflow_id: "wf123", inputs: {}, outputs: {} }; const { useInvocationStore } = await import("@/stores/invocationStore"); const { useWorkflowStore } = await import("@/stores/workflowStore"); const fetchWorkflowMock = vi.fn(() => Promise.resolve()); vi.mocked(useInvocationStore).mockReturnValueOnce({ getInvocationById: () => invocation, getInvocationLoadError: () => null, isLoadingInvocation: () => false, }); vi.mocked(useWorkflowStore).mockReturnValueOnce({ fetchWorkflowForInstanceIdCached: fetchWorkflowMock, getStoredWorkflowIdByInstanceId: () => "wf123", }); mountComponent({ content: "tool_a(invocation_id=123, input=foo, output=bar)", labels: [ { type: "input", label: "foo" }, { type: "output", label: "bar" }, ], }); await flushPromises(); expect(fetchWorkflowMock).toHaveBeenCalledWith("wf123"); }); });