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)