diff --git a/client/src/components/SelectionDialog/SelectionDialog.test.js b/client/src/components/SelectionDialog/SelectionDialog.test.js index 4671460953b..3e3a0e420ce 100644 --- a/client/src/components/SelectionDialog/SelectionDialog.test.js +++ b/client/src/components/SelectionDialog/SelectionDialog.test.js @@ -87,4 +87,36 @@ describe("SelectionDialog.vue", () => { expect(rowCheckbox.checked).toBe(false); expect(rowCheckbox.indeterminate).toBe(true); }); + + it("emits onClick for the row when its checkbox is toggled", async () => { + await wrapper.setProps({ + optionsShow: true, + selectable: true, + items: [ + { id: "1", label: "folder1", isLeaf: false, selectionState: SELECTION_STATES.MIXED }, + { id: "2", label: "file2", isLeaf: true, selectionState: SELECTION_STATES.UNSELECTED }, + ], + }); + + const rowCheckbox = wrapper.find("tbody tr[aria-rowindex='1'] .g-table-select-column input"); + await rowCheckbox.trigger("change"); + + expect(wrapper.emitted().onClick).toBeTruthy(); + expect(wrapper.emitted().onClick[0][0].id).toBe("1"); + }); + + it("emits onClick exactly once when a selectable row is clicked", async () => { + await wrapper.setProps({ + optionsShow: true, + selectable: true, + items: [{ id: "1", label: "file1", isLeaf: true, selectionState: SELECTION_STATES.UNSELECTED }], + }); + + // GTable emits both "row-select" and "row-click" for a selectable row; + // SelectionDialog must not toggle selection twice. + await wrapper.find("tbody tr[aria-rowindex='1']").trigger("click"); + + expect(wrapper.emitted().onClick).toBeTruthy(); + expect(wrapper.emitted().onClick.length).toBe(1); + }); }); diff --git a/client/src/components/SelectionDialog/SelectionDialog.vue b/client/src/components/SelectionDialog/SelectionDialog.vue index c42affb338d..57ef4deedf7 100644 --- a/client/src/components/SelectionDialog/SelectionDialog.vue +++ b/client/src/components/SelectionDialog/SelectionDialog.vue @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import { BAlert, BButton, BLink, BPagination } from "bootstrap-vue"; import { computed, ref, watch } from "vue"; -import type { RowClickEvent, TableField } from "@/components/Common/GTable.types"; +import type { RowClickEvent, RowSelectEvent, TableField } from "@/components/Common/GTable.types"; import { type ItemsProvider, SELECTION_STATES } from "@/components/SelectionDialog/selectionTypes"; import type Filtering from "@/utils/filtering"; @@ -160,6 +160,17 @@ function onSortChanged(newSortBy: string, newSortDesc: boolean) { } function onRowClick(event: RowClickEvent) { + // For a selectable table GTable also emits "row-select" on a row click + // (handled by onRowSelect), so emitting here too would toggle selection + // twice. Only emit for non-selectable dialogs. + if (!props.selectable) { + emit("onClick", event.item); + } +} + +// Selection for a selectable table: a row click and a checkbox toggle both +// arrive here as a single "row-select", so the checkbox behaves like the row. +function onRowSelect(event: RowSelectEvent) { emit("onClick", event.item); } @@ -307,6 +318,7 @@ defineExpose({ :selected-items="selectedItems" :show-select-all="props.selectable" @row-click="onRowClick" + @row-select="onRowSelect" @select-all="onSelectAll" @sort-changed="onSortChanged">