import { createTestingPinia } from "@pinia/testing"; import { getLocalVue } from "@tests/vitest/helpers"; import { mount } from "@vue/test-utils"; import flushPromises from "flush-promises"; import { describe, expect, it, vi } from "vitest"; import type { HistorySummaryExtended } from "@/api"; import { useServerMock } from "@/api/client/__mocks__"; import { useUserStore } from "@/stores/userStore"; import SwitchToHistoryLink from "./SwitchToHistoryLink.vue"; const localVue = getLocalVue(true); const { server, http } = useServerMock(); const selectors = { historyLink: ".history-link", historyLinkButton: ".history-link-click", tooltip: ".g-tooltip", } as const; // Click action mocks const mockSetCurrentHistory = vi.fn(); const mockApplyFilters = vi.fn(); const mockWindowOpen = vi.fn(() => null); vi.mock("vue-router/composables", () => ({ useRouter: () => ({ resolve: (route: string) => ({ href: `resolved-${route}`, }), }), })); // Mock the history store vi.mock("@/stores/historyStore", async () => { const originalModule = await vi.importActual("@/stores/historyStore"); return { ...originalModule, useHistoryStore: () => ({ ...(originalModule as any).useHistoryStore(), currentHistoryId: "current-history-id", setCurrentHistory: mockSetCurrentHistory, applyFilters: vi.fn().mockImplementation((historyId: string) => { // We mock what the actual method does: set the current history if not current if (historyId !== "current-history-id") { mockSetCurrentHistory(); } mockApplyFilters(); }), }), }; }); // Mock the event store to track ctrlKey presses vi.mock("@/stores/eventStore", () => { return { useEventStore: () => ({ isCtrlKey: vi.fn((event: MouseEvent) => event.ctrlKey), }), }; }); /** Clear up and initialize all mocks for the test. */ function initializeMocks() { mockSetCurrentHistory.mockClear(); mockApplyFilters.mockClear(); mockWindowOpen.mockClear(); const windowSpy = vi.spyOn(window, "open"); windowSpy.mockImplementation(() => mockWindowOpen()); } /** Mock `` component for testing. * @param history - The history to be mocked * @param hasFilters - Whether the component has the `filters` prop (generates sample filters) */ function mountSwitchToHistoryLinkForHistory(history: HistorySummaryExtended, hasFilters = false) { initializeMocks(); const pinia = createTestingPinia({ createSpy: vi.fn }); server.use( http.get("/api/histories/{history_id}", ({ response }) => { return response(200).json(history); }), ); const filters = hasFilters ? { deleted: false, visible: true, hid: "1" } : undefined; const wrapper = mount(SwitchToHistoryLink as object, { propsData: { historyId: history.id, filters, }, localVue, pinia, stubs: { FontAwesomeIcon: true, }, }); const userStore = useUserStore(); userStore.currentUser = { email: "email", id: "user_id", isAnonymous: false, total_disk_usage: 0, nice_total_disk_usage: "0 bytes", purged: false, deleted: false, is_admin: false, username: "user", preferences: {}, quota: "abcdef", }; return wrapper; } /** * This function expects a specific action to be taken based on the provided history's properties. * @param tooltip What tooltip text to expect * @param history The history object to test (with variations like `deleted`, `archived`, etc.) * @param opensInNewTab Whether the history should open in a new tab on click * @param hasFilters Whether the `SwitchToHistoryLink` has a `filters` prop * @param setsCurrentHistory Whether we set the current history on click * @param setsFilters Whether filters are applied to the current history on click */ async function expectActionForHistory( tooltip: | "Switch to this history" | "This is your current history" | "View in new tab" | "Switch to history and view dataset", history: HistorySummaryExtended, opensInNewTab = false, hasFilters = false, setsCurrentHistory = false, setsFilters = false, ) { const wrapper = mountSwitchToHistoryLinkForHistory(history, hasFilters); // Wait for the history to be loaded await flushPromises(); expect(wrapper.find(selectors.tooltip).text()).toEqual(tooltip); expect(wrapper.text()).toContain(history.name); await wrapper.find(selectors.historyLinkButton).trigger("click"); expect(mockSetCurrentHistory).toHaveBeenCalledTimes(setsCurrentHistory ? 1 : 0); expect(mockApplyFilters).toHaveBeenCalledTimes(setsFilters ? 1 : 0); expect(mockWindowOpen).toHaveBeenCalledTimes(opensInNewTab ? 1 : 0); // Click with ctrl key pressed down this time await wrapper.find(selectors.historyLinkButton).trigger("click", { ctrlKey: true }); // None of the other click operations are called (counts remain as is), but we always open the history in a new tab expect(mockSetCurrentHistory).toHaveBeenCalledTimes(setsCurrentHistory ? 1 : 0); expect(mockApplyFilters).toHaveBeenCalledTimes(setsFilters ? 1 : 0); expect(mockWindowOpen).toHaveBeenCalledTimes(opensInNewTab ? 2 : 1); // Ctrl+Click opens in new tab } describe("SwitchToHistoryLink", () => { it("loads the history information from the store", async () => { const history = { id: "history-id-to-load", name: "History Name", deleted: false, archived: false, purged: false, } as HistorySummaryExtended; const wrapper = mountSwitchToHistoryLinkForHistory(history); expect(wrapper.find(selectors.historyLink).exists()).toBe(false); expect(wrapper.html()).toContain("Loading"); // Wait for the history to be loaded await flushPromises(); expect(wrapper.find(selectors.historyLink).exists()).toBe(true); expect(wrapper.text()).toContain(history.name); }); it("sets current history or applies filters if the history can be switched to", async () => { const history = { id: "active-history-id", name: "History Active", deleted: false, purged: false, archived: false, user_id: "user_id", } as HistorySummaryExtended; // We switch to this history await expectActionForHistory("Switch to this history", history, false, false, true, false); // Since history was not current, we switch to it AND apply filters await expectActionForHistory("Switch to history and view dataset", history, false, true, true, true); }); it("only applies filters when the history is the Current history", async () => { const history = { id: "current-history-id", name: "History Current", deleted: false, purged: false, archived: false, user_id: "user_id", } as HistorySummaryExtended; // We do nothing since the history is already current await expectActionForHistory("This is your current history", history); // Since history is already current, we only apply filters await expectActionForHistory("Switch to history and view dataset", history, false, true, false, true); }); it("opens purged history in new tab or applies filters", async () => { const history = { id: "purged-history-id", name: "History Purged", deleted: false, purged: true, archived: false, user_id: "user_id", } as HistorySummaryExtended; // We view the purged history in a new tab await expectActionForHistory("View in new tab", history, true); // We switch to the purged history and apply filters await expectActionForHistory("Switch to history and view dataset", history, false, true, true, true); }); it("opens archived history in new tab or applies filters", async () => { const history = { id: "archived-history-id", name: "History Archived", deleted: false, purged: false, archived: true, user_id: "user_id", } as HistorySummaryExtended; // We view the archived history in a new tab await expectActionForHistory("View in new tab", history, true); // We switch to the archived history and apply filters await expectActionForHistory("Switch to history and view dataset", history, false, true, true, true); }); it("only opens an accessible unowned history in new tab", async () => { const history = { id: "public-history-id", name: "History Published", deleted: false, purged: false, archived: false, published: true, user_id: "other_user_id", } as HistorySummaryExtended; // We view the accessible (but other user's) history in a new tab await expectActionForHistory("View in new tab", history, true); // Since the history isn't owned, we can't switch to it and apply filters; so we just view it in a new tab await expectActionForHistory("View in new tab", history, true); }); // if the history is inaccessible, the HistorySummary would never be fetched in the first place });