diff --git a/chrome/content/zotero/collectionTree.jsx b/chrome/content/zotero/collectionTree.jsx index 25b7041f0f..0f413514e8 100644 --- a/chrome/content/zotero/collectionTree.jsx +++ b/chrome/content/zotero/collectionTree.jsx @@ -316,6 +316,8 @@ var CollectionTree = class CollectionTree extends LibraryTree { div.classList.toggle('selected', selection.isSelected(index)); div.classList.toggle('first-selected', selection.isFirstRowOfSelectionBlock(index)); div.classList.toggle('last-selected', selection.isLastRowOfSelectionBlock(index)); + // Focus ring on the focused-but-unselected row (e.g., macOS Cmd-arrow navigation) + div.classList.toggle('focused', selection.focused == index); div.classList.toggle('highlighted', this._highlightedRows.has(treeRow.id)); div.classList.toggle('drop', this._dropRow == index); div.classList.toggle('flashing', this._flashingRow == index); diff --git a/test/tests/collectionTreeTest.js b/test/tests/collectionTreeTest.js index cf09904995..c3f9e03ee3 100644 --- a/test/tests/collectionTreeTest.js +++ b/test/tests/collectionTreeTest.js @@ -756,6 +756,28 @@ describe("Zotero.CollectionTree", function () { assert.isTrue(cv.selection.isSelected(cv.getRowIndexByID("C" + c1.id))); }); + it("should mark the focused-but-unselected row for the focus ring", async function () { + let c1 = await createDataObject('collection'); + let c2 = await createDataObject('collection'); + await cv.selectByID("C" + c1.id); + let r2 = cv.getRowIndexByID("C" + c2.id); + + // Move focus to c2 without selecting it (macOS Cmd-arrow style) + cv.tree._onSelection(r2, false, false, true); + assert.equal(cv.selection.focused, r2); + assert.isFalse(cv.selection.isSelected(r2)); + + // The focused (unselected) row gets the 'focused' class that drives the + // dotted focus ring (rendering is async, so wait for it) + await waitForCallback(() => { + let n = win.document.getElementById(`${cv.id}-row-${r2}`); + return n && n.classList.contains('focused'); + }, 50, 20); + let node = win.document.getElementById(`${cv.id}-row-${r2}`); + assert.isTrue(node.classList.contains('focused')); + assert.isFalse(node.classList.contains('selected')); + }); + 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' });