From 2b1b67f611d165983605653d67ededf2ea525e86 Mon Sep 17 00:00:00 2001 From: guerler Date: Sat, 8 Nov 2025 11:07:16 +0300 Subject: [PATCH] Add more tests, add link to success message --- .../components/Dataset/DatasetCopy.test.js | 89 ++++++++++++++----- client/src/components/Dataset/DatasetCopy.vue | 4 +- client/src/utils/navigation/navigation.yml | 6 +- .../selenium/test_history_copy_elements.py | 7 +- 4 files changed, 80 insertions(+), 26 deletions(-) diff --git a/client/src/components/Dataset/DatasetCopy.test.js b/client/src/components/Dataset/DatasetCopy.test.js index 16b0df13643..7893dc08a54 100644 --- a/client/src/components/Dataset/DatasetCopy.test.js +++ b/client/src/components/Dataset/DatasetCopy.test.js @@ -27,9 +27,20 @@ function mountComponent() { return mount(DatasetCopy, { localVue, directives: { localize: () => {} }, + stubs: { RouterLink: { template: "" } }, }); } +async function setupBase(histories, contents) { + GalaxyApi().GET.mockResolvedValueOnce({ data: histories, error: null }); + GalaxyApi().GET.mockResolvedValueOnce({ data: contents, error: null }); + const wrapper = mountComponent(); + await flushPromises(); + const checkbox = wrapper.find("input[type='checkbox']"); + await checkbox.setChecked(true); + return wrapper; +} + it("loads histories and contents on mount", async () => { GalaxyApi().GET.mockResolvedValueOnce({ data: [ @@ -83,16 +94,10 @@ it("copies selected items and shows success", async () => { const checkbox = wrapper.find("input[type='checkbox']"); await checkbox.setChecked(true); - const target = wrapper.findAll("span").wrappers.find((n) => n.text() === "H1"); - if (target) { - await target.trigger("click"); - } - await wrapper.find("button.btn-primary").trigger("click"); await flushPromises(); - expect(wrapper.text()).toContain("1 item copied to 1 history"); - + expect(wrapper.text()).toMatch(/1 item[s]? copied/); const payload = GalaxyApi().POST.mock.calls[0][1].body; expect(payload.source_history).toBe("h1"); expect(payload.source_content).toEqual([{ id: "d1", type: "dataset" }]); @@ -140,11 +145,6 @@ it("handles API error from copy call", async () => { const checkbox = wrapper.find("input[type='checkbox']"); await checkbox.setChecked(true); - const target = wrapper.findAll("span").wrappers.find((n) => n.text() === "H1"); - if (target) { - await target.trigger("click"); - } - await wrapper.find("button.btn-primary").trigger("click"); await flushPromises(); @@ -156,7 +156,6 @@ it("toggleAll selects and unselects all", async () => { data: [{ id: "h1", name: "H1" }], error: null, }); - GalaxyApi().GET.mockResolvedValueOnce({ data: [ { id: "d1", name: "X", hid: 1, history_content_type: "dataset" }, @@ -167,7 +166,6 @@ it("toggleAll selects and unselects all", async () => { const wrapper = mountComponent(); await flushPromises(); - await wrapper.vm.$nextTick(); const buttons = wrapper.findAll("button.btn-outline-primary"); @@ -175,15 +173,66 @@ it("toggleAll selects and unselects all", async () => { await flushPromises(); await wrapper.vm.$nextTick(); - const afterSelectAll = wrapper.vm.$data.sourceContentSelection || wrapper.vm.sourceContentSelection || {}; - expect(afterSelectAll["dataset|d1"]).toBe(true); - expect(afterSelectAll["dataset|d2"]).toBe(true); + let sel = wrapper.vm.sourceContentSelection?.value; + if (!sel) { + sel = wrapper.vm.sourceContentSelection; + } // handle non-ref binding + expect(sel["dataset|d1"]).toBe(true); + expect(sel["dataset|d2"]).toBe(true); await buttons.at(1).trigger("click"); await flushPromises(); await wrapper.vm.$nextTick(); - const afterUnselectAll = wrapper.vm.$data.sourceContentSelection || wrapper.vm.sourceContentSelection || {}; - expect(afterUnselectAll["dataset|d1"]).toBe(false); - expect(afterUnselectAll["dataset|d2"]).toBe(false); + sel = wrapper.vm.sourceContentSelection?.value || wrapper.vm.sourceContentSelection; + expect(sel["dataset|d1"]).toBe(false); + expect(sel["dataset|d2"]).toBe(false); +}); + +it("shows success for single existing target", async () => { + const histories = [{ id: "h1", name: "H1" }]; + const contents = [{ id: "d1", name: "X", hid: 1, history_content_type: "dataset" }]; + GalaxyApi().POST.mockResolvedValueOnce({ data: { history_ids: ["h1"] }, error: null }); + GalaxyApi().GET.mockResolvedValueOnce({ data: contents, error: null }); + + const wrapper = await setupBase(histories, contents); + await wrapper.find("button.btn-primary").trigger("click"); + await flushPromises(); + + expect(wrapper.text()).toMatch(/1 item[s]? copied to/); + expect(wrapper.text()).toContain("1 history"); +}); + +it("shows success for multiple target histories", async () => { + const histories = [ + { id: "h1", name: "H1" }, + { id: "h2", name: "H2" }, + ]; + const contents = [{ id: "d1", name: "X", hid: 1, history_content_type: "dataset" }]; + GalaxyApi().POST.mockResolvedValueOnce({ data: { history_ids: ["h1", "h2"] }, error: null }); + GalaxyApi().GET.mockResolvedValueOnce({ data: contents, error: null }); + + const wrapper = await setupBase(histories, contents); + wrapper.vm.targetMultiSelections = { h1: true, h2: true }; + await wrapper.vm.$nextTick(); + + await wrapper.find("button.btn-primary").trigger("click"); + await flushPromises(); + + expect(wrapper.text()).toContain("2 histories"); +}); + +it("shows success for new history creation", async () => { + const histories = [{ id: "h1", name: "H1" }]; + const contents = [{ id: "d1", name: "X", hid: 1, history_content_type: "dataset" }]; + GalaxyApi().POST.mockResolvedValueOnce({ data: { history_ids: ["h2"] }, error: null }); + GalaxyApi().GET.mockResolvedValueOnce({ data: contents, error: null }); + + const wrapper = await setupBase(histories, contents); + await wrapper.find("input[data-description='copy history name']").setValue("New History"); + await wrapper.find("button.btn-primary").trigger("click"); + await flushPromises(); + + expect(wrapper.text()).toContain("1 item copied to"); + expect(wrapper.text()).toContain("New History"); }); diff --git a/client/src/components/Dataset/DatasetCopy.vue b/client/src/components/Dataset/DatasetCopy.vue index e2c1d5f35f7..65edc200087 100644 --- a/client/src/components/Dataset/DatasetCopy.vue +++ b/client/src/components/Dataset/DatasetCopy.vue @@ -55,6 +55,8 @@ const selectedTargets = computed(() => { }); async function loadHistories() { + loading.value = true; + errorMessage.value = ""; const { data, error } = await GalaxyApi().GET("/api/histories"); if (error) { errorMessage.value = error.err_msg; @@ -125,7 +127,7 @@ async function onCopy() { } else if (data) { successItemCount.value = selectedContent.value.length; successHistoryName.value = newHistoryName.value; - successTargetIds.value = data.history_ids; + successTargetIds.value = data.history_ids || []; await loadContents(); } loading.value = false; diff --git a/client/src/utils/navigation/navigation.yml b/client/src/utils/navigation/navigation.yml index 3666080e22a..b317ed1a94e 100644 --- a/client/src/utils/navigation/navigation.yml +++ b/client/src/utils/navigation/navigation.yml @@ -473,8 +473,12 @@ history_copy_elements: dataset_checkbox: '[data-description="copy dataset|${id}"] + label' collection_checkbox: '[data-description="copy dataset_collection|${id}"] + label' new_history_name: '[data-description="copy history name"]' + new_history_link: '[data-description="copy switch history"]' copy_button: '[data-description="copy button"]' - done_link: '.donemessage a' + +history_view: + selectors: + switch_to_history: '[data-description="switch to history button"]' collection_builders: selectors: diff --git a/lib/galaxy_test/selenium/test_history_copy_elements.py b/lib/galaxy_test/selenium/test_history_copy_elements.py index e097b04b604..05f1c9225ff 100644 --- a/lib/galaxy_test/selenium/test_history_copy_elements.py +++ b/lib/galaxy_test/selenium/test_history_copy_elements.py @@ -3,6 +3,7 @@ from .framework import ( SeleniumTestCase, ) + class TestHistoryCopyElements(SeleniumTestCase): ensure_registered = True @@ -29,11 +30,9 @@ class TestHistoryCopyElements(SeleniumTestCase): text_element.send_keys("new_history_to_copy") self.components.history_copy_elements.copy_button.wait_for_and_click() - self.sleep_for(self.wait_types.UX_TRANSITION) - self.components.history_copy_elements.done_link.wait_for_and_click() + self.components.history_copy_elements.new_history_link.wait_for_and_click() + self.components.history_view.switch_to_history.wait_for_and_click() - # I don't know why this sleep is necessary but it seems to be - self.sleep_for(self.wait_types.UX_RENDER) # Okay copied first self.history_panel_wait_for_hid_state(5, "ok") # Then 4 datasets and then the failed collection (this was six when coming from the original history)