fix focus test breakage after zotero@6859614 (#3977)

- edits to zoteroPaneTest.js focus tests to expect the updated focus
sequence: selected tab -> tabs menu -> sync button -> collectionTree
toolbar -> collectionTree -> tags selector -> itemTree toolbar
- updated tags selector keydown handling to explicitly handle all
tab/shift-tab events using moveFocus. It is more readable and explicit
focus handling for all components is required for programmic tab/shiftTab
events dispatched in tests to actually move focus
Fixes: #3975
This commit is contained in:
abaevbog 2024-04-14 02:16:16 -04:00 committed by GitHub
parent 2e8073ab9d
commit c7c3784413
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 73 additions and 49 deletions

View file

@ -92,6 +92,7 @@ class Search extends React.PureComponent {
onChange={this.handleChange} onChange={this.handleChange}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
value={this.state.immediateValue} value={this.state.immediateValue}
className="search-input"
{...pick(this.props, p => p.startsWith('data-') || p.startsWith('aria-'))} {...pick(this.props, p => p.startsWith('data-') || p.startsWith('aria-'))}
/> />
{this.state.immediateValue !== '' {this.state.immediateValue !== ''

View file

@ -414,7 +414,7 @@ var ZoteroPane = new function()
} }
// If tag selector is collapsed, go to "New item" button, otherwise // If tag selector is collapsed, go to "New item" button, otherwise
// default to focusing on tag selector // default to focusing on tag selector
return false; return tagContainer.querySelector(".tag-selector-list");
}, },
Escape: clearCollectionSearch Escape: clearCollectionSearch
} }
@ -432,30 +432,37 @@ var ZoteroPane = new function()
}); });
tagSelector.addEventListener("keydown", (e) => { tagSelector.addEventListener("keydown", (e) => {
// Tab from the scrollable tag list or Shift-Tab from the input field focuses the first let actionsMap = {
// non-disabled tag. If there are none, the tags are skipped 'search-input': {
if ((e.target.classList.contains("tag-selector-list") && e.key == "Tab" && !e.shiftKey) Tab: () => tagSelector.querySelector('.tag-selector-actions'),
|| e.target.tagName == "input" && e.key == "Tab" && e.shiftKey) { ShiftTab: () => {
let firstNonDisabledTag = document.querySelector('.tag-selector-item:not(.disabled)'); let firstNonDisabledTag = tagSelector.querySelector('.tag-selector-item:not(.disabled)');
if (firstNonDisabledTag) { if (firstNonDisabledTag) {
firstNonDisabledTag.focus(); return firstNonDisabledTag;
} }
else if (e.target.classList.contains("tag-selector-list")) { return document.getElementById("collection-tree");
tagSelector.querySelector("input").focus(); },
},
'tag-selector-item': {
Tab: () => tagSelector.querySelector(".search-input"),
ShiftTab: () => tagSelector.querySelector(".tag-selector-list"),
},
'tag-selector-actions': {
Tab: () => document.getElementById('zotero-tb-add'),
ShiftTab: () => tagSelector.querySelector(".search-input")
},
'tag-selector-list': {
Tab: () => {
let firstNonDisabledTag = tagSelector.querySelector('.tag-selector-item:not(.disabled)');
if (firstNonDisabledTag) {
return firstNonDisabledTag;
} }
else { return tagSelector.querySelector(".search-input");
tagSelector.querySelector(".tag-selector-list").focus(); },
} ShiftTab: () => document.getElementById("collection-tree"),
e.preventDefault();
e.stopPropagation();
}
// Special treatment for tag selector button because it has no id
if (e.target.tagName == "button" && e.key == "Tab" && !e.shiftKey) {
document.getElementById('zotero-tb-add').focus();
e.preventDefault();
e.stopPropagation();
} }
};
moveFocus(actionsMap, e);
}); });
} }

View file

@ -1495,7 +1495,15 @@ describe("ZoteroPane", function() {
var collection = new Zotero.Collection; var collection = new Zotero.Collection;
collection.name = "Focus Test"; collection.name = "Focus Test";
await collection.saveTx(); await collection.saveTx();
// Make sure there is a tag
var item = new Zotero.Item('newspaperArticle');
item.setCollections([collection.id]);
await item.setTags(["Tag"]);
await item.saveTx({
skipSelect: true
});
await waitForItemsLoad(win); await waitForItemsLoad(win);
await zp.collectionsView.selectLibrary(userLibraryID);
}); });
var tab = new KeyboardEvent('keydown', { var tab = new KeyboardEvent('keydown', {
@ -1519,19 +1527,23 @@ describe("ZoteroPane", function() {
bubbles: true bubbles: true
}); });
// TEMP: https://github.com/zotero/zotero/issues/3975 // Focus sequence for Zotero Pane
it.skip("should shift-tab through the toolbar to item-tree", async function () {
let searchBox = doc.getElementById('zotero-tb-search-textbox');
searchBox.focus();
let sequence = [ let sequence = [
"zotero-tb-search-dropmarker", "zotero-tb-search-dropmarker",
"zotero-tb-add", "zotero-tb-add",
"tag-selector-actions",
"search-input",
"tag-selector-item",
"tag-selector-list",
"collection-tree",
"zotero-collections-search", "zotero-collections-search",
"zotero-tb-collection-add", "zotero-tb-collection-add",
"zotero-tb-sync", "zotero-tb-sync",
"zotero-tb-tabs-menu" "zotero-tb-tabs-menu"
]; ];
it("should shift-tab across the zotero pane", async function () {
let searchBox = doc.getElementById('zotero-tb-search-textbox');
searchBox.focus();
for (let id of sequence) { for (let id of sequence) {
doc.activeElement.dispatchEvent(shiftTab); doc.activeElement.dispatchEvent(shiftTab);
@ -1539,7 +1551,14 @@ describe("ZoteroPane", function() {
if (id === "zotero-collections-search") { if (id === "zotero-collections-search") {
await Zotero.Promise.delay(250); await Zotero.Promise.delay(250);
} }
// Some elements don't have id, so use classes to verify they're focused
if (doc.activeElement.id) {
assert.equal(doc.activeElement.id, id); assert.equal(doc.activeElement.id, id);
}
else {
let clases = [...doc.activeElement.classList];
assert.include(clases, id);
}
// Wait for collection search to be hidden for subsequent tests // Wait for collection search to be hidden for subsequent tests
if (id === "zotero-tb-collection-add") { if (id === "zotero-tb-collection-add") {
await Zotero.Promise.delay(50); await Zotero.Promise.delay(50);
@ -1552,27 +1571,24 @@ describe("ZoteroPane", function() {
assert.equal(doc.activeElement.id, "item-tree-main-default"); assert.equal(doc.activeElement.id, "item-tree-main-default");
}); });
// TEMP: https://github.com/zotero/zotero/issues/3975 it("should tab across the zotero pane", async function () {
it.skip("should tab through the toolbar to collection-tree", async function () {
win.Zotero_Tabs.moveFocus("current"); win.Zotero_Tabs.moveFocus("current");
let sequence = [ sequence.reverse();
"zotero-tb-tabs-menu",
"zotero-tb-sync",
"zotero-tb-collection-add",
"zotero-collections-search",
"zotero-tb-add",
"zotero-tb-search-dropmarker",
'zotero-tb-search-textbox',
'collection-tree',
];
for (let id of sequence) { for (let id of sequence) {
doc.activeElement.dispatchEvent(tab); doc.activeElement.dispatchEvent(tab);
// Wait for collection search to be revealed // Wait for collection search to be revealed
if (id === "zotero-collections-search") { if (id === "zotero-collections-search") {
await Zotero.Promise.delay(250); await Zotero.Promise.delay(250);
} }
// Some elements don't have id, so use classes to verify they're focused
if (doc.activeElement.id) {
assert.equal(doc.activeElement.id, id); assert.equal(doc.activeElement.id, id);
} }
else {
let clases = [...doc.activeElement.classList];
assert.include(clases, id);
}
}
}); });
it("should navigate toolbarbuttons with arrows", async function () { it("should navigate toolbarbuttons with arrows", async function () {