import { createTestingPinia } from "@pinia/testing"; import { getLocalVue } from "@tests/vitest/helpers"; import { mount } from "@vue/test-utils"; import flushPromises from "flush-promises"; import { setActivePinia } from "pinia"; import { beforeEach, describe, expect, it, vi } from "vitest"; import VueRouter from "vue-router"; import { useServerMock } from "@/api/client/__mocks__"; import { testDatatypesMapper } from "@/components/Datatypes/test_fixtures"; import DatasetView from "./DatasetView.vue"; const { server, http } = useServerMock(); // Mock the datatypeVisualizationsStore vi.mock("@/stores/datatypeVisualizationsStore", () => ({ useDatatypeVisualizationsStore: vi.fn(() => ({ getPreferredVisualizationForDatatype: vi.fn().mockImplementation((datatype) => { // Only return a preferred visualization for a specific test datatype if (datatype === "h5") { return Promise.resolve({ datatype: "h5", visualization: "h5web", }); } return Promise.resolve(null); }), })), })); const DATASET_ID = "dataset_id"; const localVue = getLocalVue(); localVue.use(VueRouter); // Mock dataset const mockDataset = { id: DATASET_ID, name: "Test Dataset", state: "ok", file_ext: "txt", genome_build: "hg38", misc_blurb: "100 lines", misc_info: "Additional info", peek: "Needs a peek", }; const errorDataset = { ...mockDataset, state: "error" }; const failedMetadataDataset = { ...mockDataset, state: "failed_metadata" }; // Additional states to test const uploadingDataset = { ...mockDataset, state: "upload" }; const runningDataset = { ...mockDataset, state: "running" }; const pausedDataset = { ...mockDataset, state: "paused" }; // Dataset with preferred visualization const h5Dataset = { ...mockDataset, file_ext: "h5" }; function setupPinia(datasetStore) { const pinia = createTestingPinia({ createSpy: vi.fn, initialState: { datasetStore: datasetStore, datatypeStore: { datatypeDetails: { txt: { id: "txt", name: "Text", display_type: "txt", }, }, }, datatypesMapperStore: { datatypesMapper: testDatatypesMapper, }, }, stubActions: false, }); setActivePinia(pinia); return pinia; } /** * Mount the DatasetView component with the specified tab and dataset options */ async function mountDatasetView(tab = "preview", options = {}) { const datasetStore = { storedDatasets: { [DATASET_ID]: options.dataset || mockDataset, }, }; const pinia = setupPinia(datasetStore); const router = new VueRouter(); router.push = vi.fn(); router.replace = vi.fn(); const wrapper = mount(DatasetView, { propsData: { datasetId: DATASET_ID, tab: tab, }, localVue, pinia, router, attachTo: document.createElement("div"), stubs: { // Only shallow stub certain components FontAwesomeIcon: true, Heading: { template: "
", props: ["h1", "separator"], }, BLink: { template: "", props: ["to"], }, GTabs: { template: '
', props: ["pills", "card", "lazy", "value"], }, GTab: { template: '
', props: ["title"], }, DatasetDetails: true, VisualizationsList: true, DatasetAttributes: true, DatasetError: true, // Use a stub for the VisualizationFrame component VisualizationFrame: { template: '
', props: ["datasetId", "visualization", "visualizationParams"], }, }, mocks: { $store: { state: { config: {}, }, }, }, }); await flushPromises(); return wrapper; } /** * Mount the DatasetView component in loading state */ async function mountLoadingDatasetView() { const datasetStore = { storedDatasets: {}, }; const pinia = setupPinia(datasetStore); const router = new VueRouter(); router.push = vi.fn(); router.replace = vi.fn(); const wrapper = mount(DatasetView, { propsData: { datasetId: DATASET_ID, }, localVue, pinia, router, stubs: { Heading: true, BLink: true, GTabs: true, GTab: true, }, mocks: { $store: { state: { config: {}, }, }, }, }); await flushPromises(); return wrapper; } describe("DatasetView", () => { beforeEach(() => { class IO { constructor() {} observe() {} unobserve() {} disconnect() {} } global.IntersectionObserver = IO; global.MutationObserver = IO; server.use( http.get("/api/datasets/:dataset_id", ({ response }) => { return response(200).json(mockDataset); }), http.get("/api/configuration", ({ response }) => response(200).json({})), http.get("/api/plugins", ({ response }) => response(200).json([])), ); }); describe("Component mounting and basic functionality", () => { it("mounts with correct props", async () => { const wrapper = await mountDatasetView(); expect(wrapper.exists()).toBe(true); expect(wrapper.props().datasetId).toBe(DATASET_ID); expect(wrapper.props().tab).toBe("preview"); }); it("shows loading message when dataset is loading", async () => { const wrapper = await mountLoadingDatasetView(); expect(wrapper.find(".loading-message").exists()).toBe(true); expect(wrapper.find(".loading-message").text()).toBe("Loading..."); expect(wrapper.find(".dataset-view").exists()).toBe(true); }); it("renders dataset information", async () => { const wrapper = await mountDatasetView(); // Check that the component mounted successfully with the expected data expect(wrapper.vm.$props.datasetId).toBe(DATASET_ID); expect(wrapper.vm.$props.tab).toBe("preview"); // Make sure we're properly passing the dataset to the component const datasetStore = wrapper.vm.$pinia.state.value.datasetStore; expect(datasetStore.storedDatasets[DATASET_ID]).toBeDefined(); expect(datasetStore.storedDatasets[DATASET_ID].name).toBe("Test Dataset"); }); }); describe("Tab navigation functionality", () => { it("handles different tabs through props", async () => { let wrapper = await mountDatasetView("details"); expect(wrapper.props().tab).toBe("details"); wrapper = await mountDatasetView("visualize"); expect(wrapper.props().tab).toBe("visualize"); wrapper = await mountDatasetView("edit"); expect(wrapper.props().tab).toBe("edit"); wrapper = await mountDatasetView("error", { dataset: errorDataset }); expect(wrapper.props().tab).toBe("error"); }); it("updates when tab prop changes", async () => { const wrapper = await mountDatasetView("details"); expect(wrapper.props().tab).toBe("details"); await wrapper.setProps({ tab: "visualize" }); await flushPromises(); expect(wrapper.props().tab).toBe("visualize"); await wrapper.setProps({ tab: "edit" }); await flushPromises(); expect(wrapper.props().tab).toBe("edit"); }); it("includes preview tab for dataset viewing", async () => { const wrapper = await mountDatasetView("preview"); expect(wrapper.vm.$props.tab).toBe("preview"); const datasetStore = wrapper.vm.$pinia.state.value.datasetStore; const dataset = datasetStore.storedDatasets[DATASET_ID]; expect(dataset).toBeDefined(); }); }); describe("Error state handling", () => { it("shows error tab for datasets with error state", async () => { const wrapper = await mountDatasetView("error", { dataset: errorDataset }); const router = wrapper.vm.$router; expect(router.replace).not.toHaveBeenCalled(); // Check that we're viewing the error tab expect(wrapper.vm.$props.tab).toBe("error"); }); it("shows error tab for datasets with failed_metadata state", async () => { const wrapper = await mountDatasetView("error", { dataset: failedMetadataDataset }); const router = wrapper.vm.$router; expect(router.replace).not.toHaveBeenCalled(); // Check that we're viewing the error tab expect(wrapper.vm.$props.tab).toBe("error"); }); it("handles datasets with different states appropriately", async () => { // Test uploading state, make sure it doesn't blow up. let wrapper = await mountDatasetView("preview", { dataset: uploadingDataset }); expect(wrapper.exists()).toBe(true); expect(wrapper.vm.$props.datasetId).toBe(DATASET_ID); expect(wrapper.vm.$props.tab).toBe("preview"); // Test running state, make sure it doesn't blow up. wrapper = await mountDatasetView("preview", { dataset: runningDataset }); expect(wrapper.exists()).toBe(true); expect(wrapper.vm.$props.datasetId).toBe(DATASET_ID); expect(wrapper.vm.$props.tab).toBe("preview"); // Test paused state, make sure it doesn't blow up. wrapper = await mountDatasetView("preview", { dataset: pausedDataset }); expect(wrapper.exists()).toBe(true); expect(wrapper.vm.$props.datasetId).toBe(DATASET_ID); expect(wrapper.vm.$props.tab).toBe("preview"); }); it.skip("uses preferred visualization for supported datatypes", async () => { const wrapper = await mountDatasetView("preview", { dataset: h5Dataset }); await flushPromises(); // Wait for preferred visualization check // Check that the preferredVisualization was set expect(wrapper.vm.preferredVisualization).toBe("h5web"); // Check that we're using the VisualizationFrame for the preferred visualization expect(wrapper.findComponent({ name: "VisualizationFrame" }).exists()).toBe(true); expect(wrapper.find("iframe").exists()).toBe(false); }); it.skip("falls back to default preview for unsupported datatypes", async () => { const wrapper = await mountDatasetView("preview"); await flushPromises(); // Wait for preferred visualization check // No preferred visualization should be set expect(wrapper.vm.preferredVisualization).toBeNull(); // Check that we're using the default iframe expect(wrapper.findComponent({ name: "VisualizationFrame" }).exists()).toBe(false); expect(wrapper.find("iframe").exists()).toBe(true); expect(wrapper.find("iframe").attributes("src")).toBe(`/datasets/${DATASET_ID}/display/?preview=true`); }); }); describe("URL and routing", () => { it("tests navigation behavior", async () => { const wrapper = await mountDatasetView("preview"); await wrapper.setProps({ tab: "details" }); await flushPromises(); expect(wrapper.vm.$props.tab).toBe("details"); }); it("formats tab URLs correctly", async () => { const previewUrl = `/datasets/${DATASET_ID}`; const detailsUrl = `/datasets/${DATASET_ID}/details`; const visualizeUrl = `/datasets/${DATASET_ID}/visualize`; const editUrl = `/datasets/${DATASET_ID}/edit`; const errorUrl = `/datasets/${DATASET_ID}/error`; expect(previewUrl).toBe(`/datasets/${DATASET_ID}`); expect(detailsUrl).toBe(`/datasets/${DATASET_ID}/details`); expect(visualizeUrl).toBe(`/datasets/${DATASET_ID}/visualize`); expect(editUrl).toBe(`/datasets/${DATASET_ID}/edit`); expect(errorUrl).toBe(`/datasets/${DATASET_ID}/error`); }); }); });