From 206ab311db79b6d5507d1acee8414a28d94dd800 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Tue, 16 Jun 2026 12:32:36 -0400 Subject: [PATCH] Clear stale collection-tree selection when filtering Filtering rebuilds the rows, but the selected indices from before pointed at different (or out-of-range) rows afterward, so a multi-selection rendered the wrong rows as selected. Clear the selection after the rebuild and re-select the focused row, collapsing a multi-selection to that row while filtering. --- chrome/content/zotero/collectionTree.jsx | 4 ++++ test/tests/collectionTreeTest.js | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/chrome/content/zotero/collectionTree.jsx b/chrome/content/zotero/collectionTree.jsx index ed66fde62e..25b7041f0f 100644 --- a/chrome/content/zotero/collectionTree.jsx +++ b/chrome/content/zotero/collectionTree.jsx @@ -2721,6 +2721,10 @@ var CollectionTree = class CollectionTree extends LibraryTree { this.selection.clearSelection(); } await this.reload(); + // reload() rebuilds the rows, so any selected indices from before now point at + // different (or out-of-range) rows. Clear them and re-select just the current + // row below, collapsing a multi-selection to the focused row while filtering. + this.selection.clearSelection(); if (currentRow) { // Special treatment for when there are no filter matches // Otherwise, selection.focused does not get updated by selectByID, which breaks ZoteroPane. diff --git a/test/tests/collectionTreeTest.js b/test/tests/collectionTreeTest.js index 80143205a0..cf09904995 100644 --- a/test/tests/collectionTreeTest.js +++ b/test/tests/collectionTreeTest.js @@ -755,6 +755,25 @@ describe("Zotero.CollectionTree", function () { assert.equal(cv.selection.count, 1); assert.isTrue(cv.selection.isSelected(cv.getRowIndexByID("C" + c1.id))); }); + + it("should not leave a stale multi-selection after filtering", async function () { + let cA = await createDataObject('collection', { name: 'filterAAA' }); + let cB = await createDataObject('collection', { name: 'filterBBB' }); + let cC = await createDataObject('collection', { name: 'filterCCC' }); + + // Select all three, focusing the one that will still match the filter + await cv.selectByID("C" + cB.id); + toggleRow(cv.getRowIndexByID("C" + cC.id)); + toggleRow(cv.getRowIndexByID("C" + cA.id)); + assert.equal(cv.selection.count, 3); + + await cv.setFilter("filterAAA"); + + // Only the still-matching focused row is selected -- no stale indices + assert.deepEqual(cv.getSelectedRows().map(r => r.id), ["C" + cA.id]); + + await cv.setFilter(""); + }); }); describe("#onDrop()", function () {