Skip to content

Commit 6269698

Browse files
authored
Improve search history by attaching change listener (#9794)
1 parent 5bc359f commit 6269698

File tree

4 files changed

+124
-3
lines changed

4 files changed

+124
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve
6868
- We fixed an issue where it was no longer possible to connect to a shared mysql database due to an exception [#9761](https://github.com/JabRef/jabref/issues/9761)
6969
- We fixed the citation key generation for (`[authors]`, `[authshort]`, `[authorsAlpha]`, `authIniN`, `authEtAl`, `auth.etal`)[https://docs.jabref.org/setup/citationkeypatterns#special-field-markers] to handle `and others` properly. [koppor#626](https://github.com/koppor/jabref/issues/626)
7070
- We fixed the Save/save as file type shows BIBTEX_DB instead of "Bibtex library" [#9372](https://github.com/JabRef/jabref/issues/9372)
71+
- We fixed an issue regarding recording redundant prefixes in search history. [#9685](https://github.com/JabRef/jabref/issues/9685)
7172

7273
### Removed
7374

src/main/java/org/jabref/gui/search/GlobalSearchBar.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242

4343
import org.jabref.gui.ClipBoardManager;
4444
import org.jabref.gui.DialogService;
45-
import org.jabref.gui.Globals;
4645
import org.jabref.gui.JabRefFrame;
4746
import org.jabref.gui.StateManager;
4847
import org.jabref.gui.autocompleter.AppendPersonNamesStrategy;
@@ -127,7 +126,7 @@ public GlobalSearchBar(JabRefFrame frame, StateManager stateManager, Preferences
127126
searchFieldTooltip.setMaxHeight(10);
128127
updateHintVisibility();
129128

130-
KeyBindingRepository keyBindingRepository = Globals.getKeyPrefs();
129+
KeyBindingRepository keyBindingRepository = preferencesService.getKeyBindingRepository();
131130
searchField.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
132131
Optional<KeyBinding> keyBinding = keyBindingRepository.mapToKeyBinding(event);
133132
if (keyBinding.isPresent()) {
@@ -211,6 +210,18 @@ public GlobalSearchBar(JabRefFrame frame, StateManager stateManager, Preferences
211210

212211
this.stateManager.activeSearchQueryProperty().addListener((obs, oldvalue, newValue) -> newValue.ifPresent(this::updateSearchResultsForQuery));
213212
this.stateManager.activeDatabaseProperty().addListener((obs, oldValue, newValue) -> stateManager.activeSearchQueryProperty().get().ifPresent(this::updateSearchResultsForQuery));
213+
/*
214+
* The listener tracks a change on the focus property value.
215+
* This happens, from active (user types a query) to inactive / focus
216+
* lost (e.g., user selects an entry or triggers the search).
217+
* The search history should only be filled, if focus is lost.
218+
*/
219+
searchField.focusedProperty().addListener((obs, oldValue, newValue) -> {
220+
// Focus lost can be derived by checking that there is no newValue (or the text is empty)
221+
if (oldValue && !(newValue || searchField.getText().isBlank())) {
222+
this.stateManager.addSearchHistory(searchField.textProperty().get());
223+
}
224+
});
214225
}
215226

216227
private void updateSearchResultsForQuery(SearchQuery query) {
@@ -307,7 +318,6 @@ public void performSearch() {
307318
informUserAboutInvalidSearchQuery();
308319
return;
309320
}
310-
this.stateManager.addSearchHistory(searchField.textProperty().get());
311321
stateManager.setSearchQuery(searchQuery);
312322
}
313323

src/main/java/org/jabref/gui/search/SearchTextField.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public static CustomTextField create() {
1212
CustomTextField textField = (CustomTextField) TextFields.createClearableTextField();
1313
textField.setPromptText(Localization.lang("Search") + "...");
1414
textField.setLeft(IconTheme.JabRefIcons.SEARCH.getGraphicNode());
15+
textField.setId("searchField");
1516
return textField;
1617
}
1718
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package org.jabref.gui.search;
2+
3+
import java.util.EnumSet;
4+
import java.util.List;
5+
6+
import javafx.scene.Scene;
7+
import javafx.scene.control.TextInputControl;
8+
import javafx.scene.layout.HBox;
9+
import javafx.stage.Stage;
10+
11+
import org.jabref.gui.DialogService;
12+
import org.jabref.gui.JabRefFrame;
13+
import org.jabref.gui.StateManager;
14+
import org.jabref.gui.undo.CountingUndoManager;
15+
import org.jabref.gui.util.DefaultTaskExecutor;
16+
import org.jabref.model.database.BibDatabaseContext;
17+
import org.jabref.model.search.rules.SearchRules;
18+
import org.jabref.preferences.PreferencesService;
19+
import org.jabref.preferences.SearchPreferences;
20+
import org.jabref.testutils.category.GUITest;
21+
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.extension.ExtendWith;
24+
import org.mockito.Answers;
25+
import org.testfx.api.FxRobot;
26+
import org.testfx.framework.junit5.ApplicationExtension;
27+
import org.testfx.framework.junit5.Start;
28+
29+
import static org.junit.jupiter.api.Assertions.assertEquals;
30+
import static org.junit.jupiter.api.Assertions.assertTrue;
31+
import static org.mockito.Mockito.mock;
32+
import static org.mockito.Mockito.when;
33+
34+
@GUITest
35+
@ExtendWith(ApplicationExtension.class)
36+
public class GlobalSearchBarTest {
37+
private Stage stage;
38+
private Scene scene;
39+
private HBox hBox;
40+
41+
private GlobalSearchBar searchBar;
42+
private StateManager stateManager;
43+
44+
@Start
45+
public void onStart(Stage stage) {
46+
SearchPreferences searchPreferences = mock(SearchPreferences.class);
47+
when(searchPreferences.getSearchFlags()).thenReturn(EnumSet.noneOf(SearchRules.SearchFlags.class));
48+
PreferencesService prefs = mock(PreferencesService.class, Answers.RETURNS_DEEP_STUBS);
49+
when(prefs.getSearchPreferences()).thenReturn(searchPreferences);
50+
51+
stateManager = new StateManager();
52+
// Need for active database, otherwise the searchField will be disabled
53+
stateManager.setActiveDatabase(new BibDatabaseContext());
54+
55+
// Instantiate GlobalSearchBar class, so the change listener is registered
56+
searchBar = new GlobalSearchBar(
57+
mock(JabRefFrame.class),
58+
stateManager,
59+
prefs,
60+
mock(CountingUndoManager.class),
61+
mock(DialogService.class)
62+
);
63+
64+
hBox = new HBox(searchBar);
65+
66+
scene = new Scene(hBox, 400, 400);
67+
this.stage = stage;
68+
stage.setScene(scene);
69+
70+
stage.show();
71+
}
72+
73+
@Test
74+
void recordingSearchQueriesOnFocusLostOnly(FxRobot robot) throws InterruptedException {
75+
stateManager.clearSearchHistory();
76+
String searchQuery = "Smith";
77+
// Track the node, that the search query will be typed into
78+
TextInputControl searchField = robot.lookup("#searchField").queryTextInputControl();
79+
80+
// The focus is on searchField node, as we click on the search box
81+
var searchFieldRoboto = robot.clickOn(searchField);
82+
for (char c : searchQuery.toCharArray()) {
83+
searchFieldRoboto.write(String.valueOf(c));
84+
Thread.sleep(401);
85+
assertTrue(stateManager.getWholeSearchHistory().isEmpty());
86+
}
87+
88+
// Set the focus to another node to trigger the listener and finally record the query.
89+
DefaultTaskExecutor.runAndWaitInJavaFXThread(() -> hBox.requestFocus());
90+
List<String> lastSearchHistory = stateManager.getWholeSearchHistory().stream().toList();
91+
92+
assertEquals(List.of("Smith"), lastSearchHistory);
93+
}
94+
95+
@Test
96+
void emptyQueryIsNotRecorded(FxRobot robot) {
97+
stateManager.clearSearchHistory();
98+
String searchQuery = "";
99+
TextInputControl searchField = robot.lookup("#searchField").queryTextInputControl();
100+
101+
var searchFieldRoboto = robot.clickOn(searchField);
102+
searchFieldRoboto.write(searchQuery);
103+
104+
DefaultTaskExecutor.runAndWaitInJavaFXThread(() -> hBox.requestFocus());
105+
List<String> lastSearchHistory = stateManager.getWholeSearchHistory().stream().toList();
106+
107+
assertEquals(List.of(), lastSearchHistory);
108+
}
109+
}

0 commit comments

Comments
 (0)