unmatchedWordsIterator = unmatchedWords.iterator();
+ while (unmatchedWordsIterator.hasNext()) {
+ String word = unmatchedWordsIterator.next();
+ if (formattedFieldContent.contains(word)) {
+ unmatchedWordsIterator.remove();
+ }
+ }
+
+ if (unmatchedWords.isEmpty()) {
+ return true;
+ }
+ }
+
+ return getFulltextResults(query, bibEntry).numSearchResults() > 0; // Didn't match all words.
+ }
+
+}
diff --git a/src/main/java/org/jabref/model/search/rules/FullTextSearchRule.java b/src/main/java/org/jabref/model/search/rules/FullTextSearchRule.java
new file mode 100644
index 00000000000..97084748a6b
--- /dev/null
+++ b/src/main/java/org/jabref/model/search/rules/FullTextSearchRule.java
@@ -0,0 +1,70 @@
+package org.jabref.model.search.rules;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.jabref.gui.Globals;
+import org.jabref.logic.pdf.search.retrieval.PdfSearcher;
+import org.jabref.model.database.BibDatabaseContext;
+import org.jabref.model.entry.BibEntry;
+import org.jabref.model.pdf.search.PdfSearchResults;
+import org.jabref.model.pdf.search.SearchResult;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * All classes providing full text search results inherit from this class.
+ *
+ * Some kind of caching of the full text search results is implemented.
+ */
+public abstract class FullTextSearchRule implements SearchRule {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(FullTextSearchRule.class);
+
+ protected final EnumSet searchFlags;
+
+ protected String lastQuery;
+ protected List lastSearchResults;
+
+ private final BibDatabaseContext databaseContext;
+
+ public FullTextSearchRule(EnumSet searchFlags) {
+ this.searchFlags = searchFlags;
+ this.lastQuery = "";
+ lastSearchResults = Collections.emptyList();
+
+ databaseContext = Globals.stateManager.getActiveDatabase().orElse(null);
+ }
+
+ public EnumSet getSearchFlags() {
+ return searchFlags;
+ }
+
+ @Override
+ public PdfSearchResults getFulltextResults(String query, BibEntry bibEntry) {
+ if (!searchFlags.contains(SearchRules.SearchFlags.FULLTEXT) || databaseContext == null) {
+ return new PdfSearchResults();
+ }
+
+ if (!query.equals(this.lastQuery)) {
+ this.lastQuery = query;
+ lastSearchResults = Collections.emptyList();
+ try {
+ PdfSearcher searcher = PdfSearcher.of(databaseContext);
+ PdfSearchResults results = searcher.search(query, 5);
+ lastSearchResults = results.getSortedByScore();
+ } catch (IOException e) {
+ LOGGER.error("Could not retrieve search results!", e);
+ }
+ }
+
+ return new PdfSearchResults(lastSearchResults.stream()
+ .filter(searchResult -> searchResult.isResultFor(bibEntry))
+ .collect(Collectors.toList()));
+ }
+
+}
diff --git a/src/main/java/org/jabref/model/search/rules/LuceneBasedSearchRule.java b/src/main/java/org/jabref/model/search/rules/LuceneBasedSearchRule.java
new file mode 100644
index 00000000000..90f838c2214
--- /dev/null
+++ b/src/main/java/org/jabref/model/search/rules/LuceneBasedSearchRule.java
@@ -0,0 +1,77 @@
+package org.jabref.model.search.rules;
+
+import java.util.EnumSet;
+import java.util.Locale;
+import java.util.concurrent.ExecutionException;
+
+import org.jabref.architecture.AllowedToUseLogic;
+import org.jabref.model.entry.BibEntry;
+import org.jabref.model.search.rules.SearchRules.SearchFlags;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import org.apache.lucene.queryparser.flexible.core.QueryNodeException;
+import org.apache.lucene.queryparser.flexible.core.nodes.QueryNode;
+import org.apache.lucene.queryparser.flexible.core.parser.SyntaxParser;
+import org.apache.lucene.queryparser.flexible.standard.parser.StandardSyntaxParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Search rule for a search based on String.contains()
+ */
+@AllowedToUseLogic("Because access to the lucene index is needed")
+public class LuceneBasedSearchRule extends FullTextSearchRule {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(LuceneBasedSearchRule.class);
+
+ // We use SyntaxParser to be consistent with org.jabref.logic.importer
+ // We do not use "PrecedenceQueryParser", because this parser keeps the term "AND"
+ private SyntaxParser parser = new StandardSyntaxParser();
+ private LoadingCache cache = CacheBuilder.newBuilder()
+ .maximumSize(100)
+ .build(
+ new CacheLoader<>() {
+ @Override
+ public QueryNode load(String query) throws Exception {
+ return parser.parse(query, "");
+ }
+ }
+ );
+
+ public LuceneBasedSearchRule(EnumSet searchFlags) {
+ super(searchFlags);
+ }
+
+ @Override
+ public boolean validateSearchStrings(String query) {
+ // Using the Lucene parser and checking for an exception is the only way to check whether the query is valid
+ try {
+ parser.parse(query, "");
+ } catch (QueryNodeException e) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean applyRule(String queryString, BibEntry bibEntry) {
+ String searchString = queryString;
+ QueryNode query;
+ try {
+ query = cache.get(searchString);
+ } catch (ExecutionException e) {
+ LOGGER.error("Could not parse query {}", queryString, e);
+ return false;
+ }
+
+ BibQueryVisitor bibQueryVisitor = new BibQueryVisitor(bibEntry, searchFlags);
+ if (bibQueryVisitor.matchFound(query)) {
+ return true;
+ }
+
+ return getFulltextResults(queryString, bibEntry).numSearchResults() > 0; // Didn't match all words.
+ }
+
+}
diff --git a/src/main/java/org/jabref/model/search/rules/RegexBasedSearchRule.java b/src/main/java/org/jabref/model/search/rules/RegexBasedSearchRule.java
index 0cc4bcb1f0a..456cded97be 100644
--- a/src/main/java/org/jabref/model/search/rules/RegexBasedSearchRule.java
+++ b/src/main/java/org/jabref/model/search/rules/RegexBasedSearchRule.java
@@ -1,23 +1,14 @@
package org.jabref.model.search.rules;
-import java.io.IOException;
import java.util.EnumSet;
-import java.util.List;
-import java.util.Locale;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
-import java.util.stream.Collectors;
import org.jabref.architecture.AllowedToUseLogic;
-import org.jabref.gui.Globals;
-import org.jabref.logic.pdf.search.retrieval.PdfSearcher;
-import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.field.Field;
-import org.jabref.model.pdf.search.PdfSearchResults;
-import org.jabref.model.pdf.search.SearchResult;
import org.jabref.model.search.rules.SearchRules.SearchFlags;
import org.slf4j.Logger;
@@ -27,36 +18,18 @@
* Search rule for regex-based search.
*/
@AllowedToUseLogic("Because access to the lucene index is needed")
-public class RegexBasedSearchRule implements SearchRule {
+public class RegexBasedSearchRule extends FullTextSearchRule {
- private static final Logger LOGGER = LoggerFactory.getLogger(GrammarBasedSearchRule.class);
-
- private final EnumSet searchFlags;
-
- private String lastQuery;
- private List lastSearchResults;
-
- private final BibDatabaseContext databaseContext;
+ private static final Logger LOGGER = LoggerFactory.getLogger(RegexBasedSearchRule.class);
public RegexBasedSearchRule(EnumSet searchFlags) {
- this.searchFlags = searchFlags;
-
- databaseContext = Globals.stateManager.getActiveDatabase().orElse(null);
- }
-
- public EnumSet getSearchFlags() {
- return searchFlags;
+ super(searchFlags);
}
@Override
public boolean validateSearchStrings(String query) {
- String searchString = query;
- if (!searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE)) {
- searchString = searchString.toLowerCase(Locale.ROOT);
- }
-
try {
- Pattern.compile(searchString, searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE) ? 0 : Pattern.CASE_INSENSITIVE);
+ Pattern.compile(query, searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE) ? 0 : Pattern.CASE_INSENSITIVE);
} catch (PatternSyntaxException ex) {
return false;
}
@@ -66,10 +39,10 @@ public boolean validateSearchStrings(String query) {
@Override
public boolean applyRule(String query, BibEntry bibEntry) {
Pattern pattern;
-
try {
pattern = Pattern.compile(query, searchFlags.contains(SearchRules.SearchFlags.CASE_SENSITIVE) ? 0 : Pattern.CASE_INSENSITIVE);
} catch (PatternSyntaxException ex) {
+ LOGGER.debug("Could not compile regex {}", query, ex);
return false;
}
@@ -86,24 +59,4 @@ public boolean applyRule(String query, BibEntry bibEntry) {
return getFulltextResults(query, bibEntry).numSearchResults() > 0;
}
- @Override
- public PdfSearchResults getFulltextResults(String query, BibEntry bibEntry) {
-
- if (!searchFlags.contains(SearchRules.SearchFlags.FULLTEXT) || databaseContext == null) {
- return new PdfSearchResults(List.of());
- }
-
- if (!query.equals(this.lastQuery)) {
- this.lastQuery = query;
- lastSearchResults = List.of();
- try {
- PdfSearcher searcher = PdfSearcher.of(databaseContext);
- PdfSearchResults results = searcher.search(query, 5);
- lastSearchResults = results.getSortedByScore();
- } catch (IOException e) {
- LOGGER.error("Could not retrieve search results!", e);
- }
- }
- return new PdfSearchResults(lastSearchResults.stream().filter(searchResult -> searchResult.isResultFor(bibEntry)).collect(Collectors.toList()));
- }
}
diff --git a/src/main/java/org/jabref/model/search/rules/SearchRules.java b/src/main/java/org/jabref/model/search/rules/SearchRules.java
index 614c353f834..c949f4af1ee 100644
--- a/src/main/java/org/jabref/model/search/rules/SearchRules.java
+++ b/src/main/java/org/jabref/model/search/rules/SearchRules.java
@@ -3,9 +3,16 @@
import java.util.EnumSet;
import java.util.regex.Pattern;
+/**
+ * This is a factory to instantiate the matching SearchRule implementation matching a given query
+ */
public class SearchRules {
- private static final Pattern SIMPLE_EXPRESSION = Pattern.compile("[^\\p{Punct}]*");
+ // In case Lucene keywords are contained, it is not a simple expression any more
+ private static final Pattern SIMPLE_EXPRESSION = Pattern.compile("^((?! AND | OR )[^\\p{Punct}])*$");
+
+ // used for checking the syntax of the query
+ private static LuceneBasedSearchRule luceneBasedSearchRule = new LuceneBasedSearchRule(EnumSet.noneOf(SearchFlags.class));
private SearchRules() {
}
@@ -15,7 +22,11 @@ private SearchRules() {
*/
public static SearchRule getSearchRuleByQuery(String query, EnumSet searchFlags) {
if (isSimpleQuery(query)) {
- return new ContainBasedSearchRule(searchFlags);
+ return new ContainsBasedSearchRule(searchFlags);
+ }
+
+ if (luceneBasedSearchRule.validateSearchStrings(query)) {
+ return new LuceneBasedSearchRule(searchFlags);
}
// this searches specified fields if specified,
@@ -36,7 +47,7 @@ static SearchRule getSearchRule(EnumSet searchFlags) {
if (searchFlags.contains(SearchFlags.REGULAR_EXPRESSION)) {
return new RegexBasedSearchRule(searchFlags);
} else {
- return new ContainBasedSearchRule(searchFlags);
+ return new ContainsBasedSearchRule(searchFlags);
}
}
diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties
index 3cffb6fc69f..3c9eec2b060 100644
--- a/src/main/resources/l10n/JabRef_en.properties
+++ b/src/main/resources/l10n/JabRef_en.properties
@@ -2068,7 +2068,7 @@ Keyword\ delimiter=Keyword delimiter
Hierarchical\ keyword\ delimiter=Hierarchical keyword delimiter
Escape\ ampersands=Escape ampersands
-Hint\:\n\nTo\ search\ all\ fields\ for\ Smith,\ enter\:\nsmith\n\nTo\ search\ the\ field\ author\ for\ Smith\ and\ the\ field\ title\ for\ electrical,\ enter\:\nauthor\=Smith\ and\ title\=electrical=Hint:\n\nTo search all fields for Smith, enter:\nsmith\n\nTo search the field author for Smith and the field title for electrical, enter:\nauthor=Smith and title=electrical
+Hint\:\n\nTo\ search\ all\ fields\ for\ Smith,\ enter\:\nsmith\n\nTo\ search\ the\ field\ author\ for\ Smith\ and\ the\ field\ title\ for\ electrical,\ enter\:\nauthor\:Smith\ and\ title\:electrical=Hint:\n\nTo search all fields for Smith, enter:\nsmith\n\nTo search the field author for Smith and the field title for electrical, enter:\nauthor:Smith and title:electrical
Copied\ '%0'\ to\ clipboard.=Copied '%0' to clipboard.
This\ operation\ requires\ an\ open\ library.=This operation requires an open library.
diff --git a/src/test/java/org/jabref/gui/autocompleter/AutoCompletionTest.java b/src/test/java/org/jabref/gui/autocompleter/AutoCompletionTest.java
new file mode 100644
index 00000000000..292bdde0c5f
--- /dev/null
+++ b/src/test/java/org/jabref/gui/autocompleter/AutoCompletionTest.java
@@ -0,0 +1,82 @@
+package org.jabref.gui.autocompleter;
+
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.StackPane;
+import javafx.stage.Stage;
+
+import org.controlsfx.control.PopOver;
+import org.controlsfx.control.textfield.CustomTextField;
+import org.jabref.gui.search.SearchTextField;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.testfx.api.FxRobot;
+import org.testfx.framework.junit5.ApplicationExtension;
+import org.testfx.framework.junit5.Start;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.jabref.model.database.BibDatabase;
+import org.jabref.model.entry.BibEntry;
+import org.jabref.model.entry.field.StandardField;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import static org.jabref.gui.autocompleter.AutoCompleterUtil.getRequest;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@Tag("dropdown")
+public class AutoCompletionTest {
+
+ private WordSuggestionProvider autoCompleter;
+ private BibDatabase database;
+ BibEntry entry;
+ private PopOver searchbarDropDown;
+ private CustomTextField searchField;
+
+ @Start
+ public void start(Stage stage) {
+ this.searchbarDropDown = new PopOver();
+ this.searchField = SearchTextField.create();
+ stage.setScene(new Scene(new StackPane(new HBox(searchField)), 500, 500));
+ stage.show();
+ }
+
+ @BeforeEach
+ void setUp() throws Exception {
+ database = new BibDatabase();
+ autoCompleter = new WordSuggestionProvider(StandardField.TITLE, database);
+ }
+
+ @Test//in progress
+ void completeSearchbarInDropdown() { //tests autocomplete word in searchbar
+ // assertThrows(.class, () -> new WordSuggestionProvider(null, database));
+ }
+
+ @Test//in progress
+ void completeWithoutAddingAnythingReturnsSomething() { //tests recommended words from past
+ Collection result = autoCompleter.provideSuggestions(getRequest(("test")));
+ assertEquals(Collections.emptyList(), result);
+
+ }
+
+ @Test//in progress
+ void completeReturnsMultipleResultsInDropdown() { //tests list of recommended words below searchbar
+ entry = new BibEntry();
+ entry.setField(StandardField.TITLE, "testValueOne");
+ database.insertEntry(entry);
+ searchField.appendText("testValueOne");
+
+ Collection result = autoCompleter.provideSuggestions(getRequest(("testValue")));
+ assertEquals(Arrays.asList("testValueOne", "testValueTwo"), result);
+ }
+}
diff --git a/src/test/java/org/jabref/gui/search/DropDownTest.java b/src/test/java/org/jabref/gui/search/DropDownTest.java
new file mode 100644
index 00000000000..12511b3ddf6
--- /dev/null
+++ b/src/test/java/org/jabref/gui/search/DropDownTest.java
@@ -0,0 +1,72 @@
+package org.jabref.gui.search;
+
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.StackPane;
+import javafx.stage.Stage;
+
+import org.controlsfx.control.PopOver;
+import org.controlsfx.control.textfield.CustomTextField;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.testfx.api.FxRobot;
+import org.testfx.framework.junit5.ApplicationExtension;
+import org.testfx.framework.junit5.Start;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ExtendWith(ApplicationExtension.class)
+@Tag("dropdown")
+class DropDownTest {
+
+ private PopOver searchbarDropDown;
+ private Button button;
+ private Button popOverNotShowing;
+ private Button buttonText;
+ private CustomTextField searchField;
+
+
+ @Start
+ public void start(Stage stage) {
+ this.searchbarDropDown = new PopOver();
+ this.button = new Button("button");
+ this.popOverNotShowing = new Button("popOverNotShowing");
+ this.buttonText = new Button("WriteSomeText");
+ this.searchField = SearchTextField.create();
+ button.setOnAction(actionEvent -> {
+ searchbarDropDown.show(button);
+ });
+ popOverNotShowing.setOnAction(actionEvent -> {
+ popOverNotShowing.setText("popOverGone");
+ searchbarDropDown.hide();
+ });
+ buttonText.setOnAction(actionEvent -> {
+ searchField.setText("hello");
+ });
+ stage.setScene(new Scene(new StackPane(new HBox(button, popOverNotShowing, buttonText)), 500, 500));
+ stage.show();
+ }
+
+ @Test
+ void testButtonWorking(FxRobot robot) {
+ robot.clickOn(buttonText); //enter Text into searchField
+ assertEquals("hello", searchField.getText());
+ }
+
+ @Test
+ void testDropDownShowing(FxRobot robot) {
+ robot.clickOn(button); //popOver is showing
+ assertTrue(searchbarDropDown.isShowing());
+ }
+
+ @Test
+ void dropDownNotShowing(FxRobot robot) {
+ robot.clickOn(button); //popOver is showing
+ robot.clickOn(popOverNotShowing); //popover hides
+ assertFalse(searchbarDropDown.isShowing());
+ }
+}
diff --git a/src/test/java/org/jabref/gui/search/SearchFieldSynchronizerAndRecentSearchTest.java b/src/test/java/org/jabref/gui/search/SearchFieldSynchronizerAndRecentSearchTest.java
new file mode 100644
index 00000000000..78e9ad27658
--- /dev/null
+++ b/src/test/java/org/jabref/gui/search/SearchFieldSynchronizerAndRecentSearchTest.java
@@ -0,0 +1,178 @@
+package org.jabref.gui.search;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.embed.swing.JFXPanel;
+import javafx.scene.control.ListView;
+import javafx.stage.Stage;
+
+import org.jabref.gui.Globals;
+import org.jabref.gui.JabRefFrame;
+import org.jabref.gui.StateManager;
+import org.jabref.gui.undo.CountingUndoManager;
+import org.jabref.preferences.PreferencesService;
+
+import org.controlsfx.control.textfield.CustomTextField;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@Tag("fast")
+public class SearchFieldSynchronizerAndRecentSearchTest {
+ CustomTextField searchField;
+ SearchFieldSynchronizer searchFieldSynchronizer = new SearchFieldSynchronizer(searchField);
+
+ @Test
+ void searchStringBuilderBuildsMixedStringCorrectly() {
+ JFXPanel fxPanel = new JFXPanel();
+ searchField = new CustomTextField();
+ searchFieldSynchronizer = new SearchFieldSynchronizer(searchField);
+ SearchItem item1 = new SearchItem("attribute","author:");
+ SearchItem item2 = new SearchItem("query","julian");
+ SearchItem item3 = new SearchItem("logical","OR");
+ SearchItem item4 = new SearchItem("attribute","title:");
+ SearchItem item5 = new SearchItem("query","Algebra");
+
+ searchFieldSynchronizer.addSearchItem(item1);
+ searchFieldSynchronizer.addSearchItem(item2);
+ searchFieldSynchronizer.addSearchItem(item3);
+ searchFieldSynchronizer.addSearchItem(item4);
+ searchFieldSynchronizer.addSearchItem(item5);
+
+ String searchString = searchFieldSynchronizer.searchStringBuilder();
+ String trueSearchString = "author:julian OR title:Algebra";
+ System.out.println(searchString);
+ assertEquals(trueSearchString, searchString);
+ }
+
+ @Test
+ void textFieldToListTest() {
+ JFXPanel fxPanel = new JFXPanel();
+ searchField = new CustomTextField();
+ searchFieldSynchronizer = new SearchFieldSynchronizer(searchField);
+ searchField.setText("author:Jon OR title:\"Software Engineering\"");
+ ArrayList list = searchFieldSynchronizer.textFieldToList();
+
+ String[] expectedList = new String[5];
+ expectedList[0] = "author:";
+ expectedList[1] = "Jon";
+ expectedList[2] = "OR";
+ expectedList[3] = "title:";
+ expectedList[4] = "\"Software Engineering\"";
+ assertEquals(new ArrayList(Arrays.stream(expectedList).toList()), list);
+
+ }
+
+ @Test
+ void bracketsBalancedTest() {
+ searchFieldSynchronizer = new SearchFieldSynchronizer(searchField);
+ SearchItem item1 = new SearchItem("bracket", "(");
+ SearchItem item2 = new SearchItem("bracket", "(");
+ SearchItem item3 = new SearchItem("bracket", "(");
+ SearchItem item4 = new SearchItem("bracket", ")");
+ SearchItem item5 = new SearchItem("bracket", ")");
+ SearchItem item6 = new SearchItem("bracket", ")");
+
+ ObservableList searchItemList = FXCollections.observableList(new ArrayList());
+ searchItemList.add(item1);
+ searchItemList.add(item2);
+ searchItemList.add(item3);
+ searchItemList.add(item4);
+ searchItemList.add(item5);
+ searchItemList.add(item6);
+
+ assertTrue(searchFieldSynchronizer.bracketsBalanced(searchItemList));
+
+ SearchItem item7 = new SearchItem("bracket", ")");
+ searchItemList.add(item7);
+
+ assertFalse(searchFieldSynchronizer.bracketsBalanced(searchItemList));
+ }
+
+ @Test
+ void RecentSearchRemovesDuplicates() {
+ Stage mainStage = new Stage();
+ JabRefFrame frame = new JabRefFrame(mainStage);
+ StateManager stateManager = new StateManager();
+ PreferencesService preferencesService = Globals.prefs;
+ CountingUndoManager undoManager = new CountingUndoManager();
+ GlobalSearchBar globalSearchBar = new GlobalSearchBar(frame, stateManager, preferencesService, undoManager);
+ RecentSearch recentSearch = new RecentSearch(globalSearchBar);
+
+ recentSearch.add("author:John");
+ recentSearch.add("Software Engineering");
+ recentSearch.add("title:programming");
+ recentSearch.add("author:John");
+ recentSearch.add("Software Engineering");
+
+
+ ListView RecentSearches = new ListView<>();
+ RecentSearches.getItems().add("author:John");
+ RecentSearches.getItems().add("Software Engineering");
+ RecentSearches.getItems().add("title:programming");
+
+ assertEquals(recentSearch.getList().getItems().toString(), RecentSearches.getItems().toString());
+ }
+
+ @Test
+ void SearchBarHighlightingWorks() {
+ JFXPanel fxPanel = new JFXPanel();
+ searchField = new CustomTextField();
+ searchFieldSynchronizer = new SearchFieldSynchronizer(searchField);
+ searchField.clear();
+ searchField.setStyle("-fx-border-color: blue");
+
+ // correct syntax
+ searchField.setText("author:testauthor AND title:TestTitle");
+
+ searchFieldSynchronizer.updateSearchItemList(searchFieldSynchronizer.textFieldToList());
+ searchFieldSynchronizer.syntaxHighlighting();
+ assertEquals("-fx-border-color: green", searchField.getStyle());
+
+ // wrong syntax
+ searchField.setText("AND author:test");
+
+ searchFieldSynchronizer.updateSearchItemList(searchFieldSynchronizer.textFieldToList());
+ searchFieldSynchronizer.syntaxHighlighting();
+ assertEquals("-fx-border-color: red", searchField.getStyle());
+ }
+
+ @Test
+ void addItemDoesNotCreateInvalidSearch() {
+ JFXPanel fxPanel = new JFXPanel();
+ searchField = new CustomTextField();
+ searchFieldSynchronizer = new SearchFieldSynchronizer(searchField);
+
+ SearchItem item1 = new SearchItem("logical","AND");
+ SearchItem item2 = new SearchItem("logical", "OR");
+
+ searchFieldSynchronizer.addSearchItem(item1);
+ searchFieldSynchronizer.addSearchItem(item2);
+
+ assertTrue(searchFieldSynchronizer.searchItemList.isEmpty());
+
+ }
+
+ @Test
+ void returnLatestReallyReturnsLatest() {
+ JFXPanel fxPanel = new JFXPanel();
+ searchField = new CustomTextField();
+ searchFieldSynchronizer = new SearchFieldSynchronizer(searchField);
+
+ searchFieldSynchronizer.addSearchItem("query","one");
+ searchFieldSynchronizer.addSearchItem("query","two");
+
+ SearchItem Three = new SearchItem("query", "three");
+ searchFieldSynchronizer.addSearchItem(Three);
+
+ assertEquals(Three, searchFieldSynchronizer.returnLatest(searchFieldSynchronizer.searchItemList));
+
+ }
+}
+
diff --git a/src/test/java/org/jabref/gui/search/SearchFieldSynchronizerTest.java b/src/test/java/org/jabref/gui/search/SearchFieldSynchronizerTest.java
new file mode 100644
index 00000000000..2723d5353a7
--- /dev/null
+++ b/src/test/java/org/jabref/gui/search/SearchFieldSynchronizerTest.java
@@ -0,0 +1,149 @@
+package org.jabref.gui.search;
+
+import java.util.ArrayList;
+
+import org.controlsfx.control.textfield.CustomTextField;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@Tag("dropdown")
+class SearchFieldSynchronizerTest {
+ private CustomTextField searchField;
+
+ @Test
+ void testGetSearchString() {
+ // tests whether the actual text in the search field matches the one being recalled
+ searchField = SearchTextField.create();
+ searchField.setText("author:Ruh AND year:2015 OR title:Corona");
+ SearchFieldSynchronizer sync = new SearchFieldSynchronizer(searchField);
+ assertEquals(sync.getSearchString(), searchField.getText());
+ }
+
+ @Test
+ void testSearchStringBuilder() {
+ // tests whether the searchStringBuilder() method parses the correct string from searchItemList
+ // TODO: currently AND/OR are stored in itemType, hence the test fails
+ searchField = SearchTextField.create();
+ searchField.setText("");
+ SearchFieldSynchronizer sync = new SearchFieldSynchronizer(searchField);
+ sync.searchItemList.add(new SearchItem("author:", "Ruh"));
+ sync.searchItemList.add(new SearchItem("logicalOperator", "AND"));
+ sync.searchItemList.add(new SearchItem("year:", "2015"));
+ sync.searchItemList.add(new SearchItem("logicalOperator", "OR"));
+ sync.searchItemList.add(new SearchItem("title:", "Corona"));
+ assertEquals(sync.searchStringBuilder(), "author:Ruh AND year:2015 OR title:Corona");
+ }
+
+ @Test
+ void testSearchItemList() {
+ // tests whether the searchItemList is constructed correctly TODO: overhaul itemType
+ // TODO: currently AND/OR are stored in itemType, hence the test fails
+ searchField = SearchTextField.create();
+ searchField.setText("");
+ SearchFieldSynchronizer sync = new SearchFieldSynchronizer(searchField);
+ sync.searchItemList.add(new SearchItem("author:", "Ruh"));
+ sync.searchItemList.add(new SearchItem("logicalOperator", "AND"));
+ sync.searchItemList.add(new SearchItem("year:", "2015"));
+ sync.searchItemList.add(new SearchItem("logicalOperator", "OR"));
+ sync.searchItemList.add(new SearchItem("title:", "Corona"));
+ assertEquals(5, sync.searchItemList.size());
+ assertEquals("author:", sync.searchItemList.get(0).getItemType());
+ assertEquals("Ruh", sync.searchItemList.get(0).getItem());
+ assertEquals("logicalOperator", sync.searchItemList.get(1).getItemType());
+ assertEquals("AND", sync.searchItemList.get(1).getItem());
+ assertEquals("year:", sync.searchItemList.get(2).getItemType());
+ assertEquals("2015", sync.searchItemList.get(2).getItem());
+ assertEquals("logicalOperator", sync.searchItemList.get(3).getItemType());
+ assertEquals("OR", sync.searchItemList.get(3).getItem());
+ assertEquals("title:", sync.searchItemList.get(4).getItemType());
+ assertEquals("Corona", sync.searchItemList.get(4).getItem());
+ }
+
+ @Test
+ void testUpdateSearchItemList() {
+ // tests whether the method can parse the text from the search field correctly onto the searchItemList
+ searchField = SearchTextField.create();
+ searchField.setText("author:Ruh AND year:2015 OR title:Corona");
+ SearchFieldSynchronizer sync = new SearchFieldSynchronizer(searchField);
+
+ String str = searchField.getText();
+ String[]words = str.split("(?<=:)|\\ ");
+ ArrayList list = new ArrayList<>();
+
+ for (int i = 0; i < words.length; i++) {
+ if (words[i].startsWith("\"")) {
+ boolean isWordAfterwards = i + 1 < words.length;
+ if (isWordAfterwards && words[i + 1].endsWith("\"") && !words[i].endsWith(":")) {
+ String str2 = words[i] + " " + words[i + 1];
+ list.add(str2);
+ i++;
+ } else {
+ list.add(words[i]);
+ }
+ } else {
+ list.add(words[i]);
+ }
+ }
+
+ sync.updateSearchItemList(list);
+
+ assertEquals("author:", sync.searchItemList.get(0).getItemType());
+ assertEquals("Ruh", sync.searchItemList.get(0).getItem());
+ assertEquals("logicalOperator", sync.searchItemList.get(1).getItemType());
+ assertEquals("AND", sync.searchItemList.get(1).getItem());
+ assertEquals("year:", sync.searchItemList.get(2).getItemType());
+ assertEquals("2015", sync.searchItemList.get(2).getItem());
+ assertEquals("logicalOperator", sync.searchItemList.get(1).getItemType());
+ assertEquals("OR", sync.searchItemList.get(1).getItem());
+ assertEquals("title:", sync.searchItemList.get(2).getItemType());
+ assertEquals("Corona", sync.searchItemList.get(2).getItem());
+ }
+
+ @Deprecated
+ @Test
+ void testIsPrevAttribute() {
+ // deprecated
+ searchField = SearchTextField.create();
+ searchField.setText("");
+ SearchFieldSynchronizer sync = new SearchFieldSynchronizer(searchField);
+ sync.searchItemList.add(new SearchItem("author:", "Ruh"));
+ assertEquals(true, sync.isPrevAttribute());
+
+ sync.searchItemList.add(new SearchItem("logicalOperator", "AND"));
+ assertEquals(false, sync.isPrevAttribute());
+
+ sync.searchItemList.add(new SearchItem("year:", "2015"));
+ assertEquals(true, sync.isPrevAttribute());
+
+ sync.searchItemList.add(new SearchItem("logicalOperator", "OR"));
+ assertEquals(false, sync.isPrevAttribute());
+
+ sync.searchItemList.add(new SearchItem("title:", "Corona"));
+ assertEquals(true, sync.isPrevAttribute());
+ }
+
+ @Deprecated
+ @Test
+ void testIsPrevOperator() {
+ // deprecated
+ searchField = SearchTextField.create();
+ searchField.setText("");
+ SearchFieldSynchronizer sync = new SearchFieldSynchronizer(searchField);
+ sync.searchItemList.add(new SearchItem("author:", "Ruh"));
+ assertEquals(true, sync.isPrevOperator());
+
+ sync.searchItemList.add(new SearchItem("logicalOperator", "AND"));
+ assertEquals(false, sync.isPrevOperator());
+
+ sync.searchItemList.add(new SearchItem("year:", "2015"));
+ assertEquals(true, sync.isPrevOperator());
+
+ sync.searchItemList.add(new SearchItem("logicalOperator", "OR"));
+ assertEquals(false, sync.isPrevOperator());
+
+ sync.searchItemList.add(new SearchItem("title:", "Corona"));
+ assertEquals(true, sync.isPrevOperator());
+ }
+}
diff --git a/src/test/java/org/jabref/logic/l10n/LocalizationTest.java b/src/test/java/org/jabref/logic/l10n/LocalizationTest.java
index 596139f8b5d..69d7dd00f4a 100644
--- a/src/test/java/org/jabref/logic/l10n/LocalizationTest.java
+++ b/src/test/java/org/jabref/logic/l10n/LocalizationTest.java
@@ -51,7 +51,7 @@ void testKnownGermanTranslation() {
@Test
void newLineIsAvailableAndKeptUnescaped() {
Localization.setLanguage(Language.ENGLISH);
- assertEquals("Hint: To search specific fields only, enter for example:\nauthor=smith and title=electrical", Localization.lang("Hint: To search specific fields only, enter for example:\nauthor=smith and title=electrical"));
+ assertEquals("Hint: To search specific fields only, enter for example:\nauthor:smith and title:electrical", Localization.lang("Hint: To search specific fields only, enter for example:\nauthor:ssmith and title:electrical"));
}
@Test
diff --git a/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java b/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java
deleted file mode 100644
index 337263b0dbc..00000000000
--- a/src/test/java/org/jabref/model/search/rules/ContainBasedSearchRuleTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.jabref.model.search.rules;
-
-import java.util.EnumSet;
-
-import org.jabref.model.entry.BibEntry;
-import org.jabref.model.entry.field.StandardField;
-import org.jabref.model.entry.types.StandardEntryType;
-
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * Test case for ContainBasedSearchRule.
- */
-public class ContainBasedSearchRuleTest {
-
- @Test
- public void testBasicSearchParsing() {
- BibEntry be = makeBibtexEntry();
- ContainBasedSearchRule bsCaseSensitive = new ContainBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION));
- ContainBasedSearchRule bsCaseInsensitive = new ContainBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.REGULAR_EXPRESSION));
- RegexBasedSearchRule bsCaseSensitiveRegexp = new RegexBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION));
- RegexBasedSearchRule bsCaseInsensitiveRegexp = new RegexBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.REGULAR_EXPRESSION));
-
- String query = "marine 2001 shields";
-
- assertFalse(bsCaseSensitive.applyRule(query, be));
- assertTrue(bsCaseInsensitive.applyRule(query, be));
- assertFalse(bsCaseSensitiveRegexp.applyRule(query, be));
- assertFalse(bsCaseInsensitiveRegexp.applyRule(query, be));
-
- query = "\"marine larviculture\"";
-
- assertFalse(bsCaseSensitive.applyRule(query, be));
- assertFalse(bsCaseInsensitive.applyRule(query, be));
- assertFalse(bsCaseSensitiveRegexp.applyRule(query, be));
- assertFalse(bsCaseInsensitiveRegexp.applyRule(query, be));
-
- query = "marine [A-Za-z]* larviculture";
-
- assertFalse(bsCaseSensitive.applyRule(query, be));
- assertFalse(bsCaseInsensitive.applyRule(query, be));
- assertFalse(bsCaseSensitiveRegexp.applyRule(query, be));
- assertTrue(bsCaseInsensitiveRegexp.applyRule(query, be));
- }
-
- public BibEntry makeBibtexEntry() {
- return new BibEntry(StandardEntryType.InCollection)
- .withCitationKey("shields01")
- .withField(StandardField.TITLE, "Marine finfish larviculture in Europe")
- .withField(StandardField.YEAR, "2001")
- .withField(StandardField.AUTHOR, "Kevin Shields");
- }
-}
diff --git a/src/test/java/org/jabref/model/search/rules/ContainsBasedSearchRuleTest.java b/src/test/java/org/jabref/model/search/rules/ContainsBasedSearchRuleTest.java
new file mode 100644
index 00000000000..ed77afd40be
--- /dev/null
+++ b/src/test/java/org/jabref/model/search/rules/ContainsBasedSearchRuleTest.java
@@ -0,0 +1,58 @@
+package org.jabref.model.search.rules;
+
+import java.util.EnumSet;
+
+import org.jabref.model.entry.BibEntry;
+import org.jabref.model.entry.field.StandardField;
+import org.jabref.model.entry.types.StandardEntryType;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Test case for ContainBasedSearchRule.
+ */
+public class ContainsBasedSearchRuleTest {
+
+ private final BibEntry be = new BibEntry(StandardEntryType.InCollection)
+ .withCitationKey("shields01")
+ .withField(StandardField.TITLE, "Marine finfish larviculture in Europe")
+ .withField(StandardField.YEAR, "2001")
+ .withField(StandardField.AUTHOR, "Kevin Shields");
+ private final ContainsBasedSearchRule bsCaseSensitive = new ContainsBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION));
+ private final ContainsBasedSearchRule bsCaseInsensitive = new ContainsBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.REGULAR_EXPRESSION));
+ private final RegexBasedSearchRule bsCaseSensitiveRegexp = new RegexBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE, SearchRules.SearchFlags.REGULAR_EXPRESSION));
+ private final RegexBasedSearchRule bsCaseInsensitiveRegexp = new RegexBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.REGULAR_EXPRESSION));
+
+ @Test
+ public void testContentOfSingleField() {
+ String query = "\"marine larviculture\"";
+
+ assertFalse(bsCaseSensitive.applyRule(query, be));
+ assertFalse(bsCaseInsensitive.applyRule(query, be));
+ assertFalse(bsCaseSensitiveRegexp.applyRule(query, be));
+ assertFalse(bsCaseInsensitiveRegexp.applyRule(query, be));
+ }
+
+ @Test
+ public void testContentDistributedOnMultipleFields() {
+ String query = "marine 2001 shields";
+
+ assertFalse(bsCaseSensitive.applyRule(query, be));
+ assertTrue(bsCaseInsensitive.applyRule(query, be));
+ assertFalse(bsCaseSensitiveRegexp.applyRule(query, be));
+ assertFalse(bsCaseInsensitiveRegexp.applyRule(query, be));
+ }
+
+ @Test
+ public void testRegularExpressionMatch() {
+ String query = "marine [A-Za-z]* larviculture";
+
+ assertFalse(bsCaseSensitive.applyRule(query, be));
+ assertFalse(bsCaseInsensitive.applyRule(query, be));
+ assertFalse(bsCaseSensitiveRegexp.applyRule(query, be));
+ assertTrue(bsCaseInsensitiveRegexp.applyRule(query, be));
+ }
+}
diff --git a/src/test/java/org/jabref/model/search/rules/LuceneBasedSearchRuleTest.java b/src/test/java/org/jabref/model/search/rules/LuceneBasedSearchRuleTest.java
new file mode 100644
index 00000000000..a4b804c3e4f
--- /dev/null
+++ b/src/test/java/org/jabref/model/search/rules/LuceneBasedSearchRuleTest.java
@@ -0,0 +1,109 @@
+package org.jabref.model.search.rules;
+
+import java.util.EnumSet;
+
+import org.jabref.model.entry.BibEntry;
+import org.jabref.model.entry.field.StandardField;
+import org.jabref.model.entry.types.StandardEntryType;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class LuceneBasedSearchRuleTest {
+
+ private final BibEntry bibEntry = new BibEntry(StandardEntryType.InCollection)
+ .withCitationKey("shields01")
+ .withField(StandardField.TITLE, "Marine finfish larviculture in Europe")
+ .withField(StandardField.YEAR, "2001")
+ .withField(StandardField.AUTHOR, "Kevin Shields")
+ .withField(StandardField.GROUPS, "included");
+
+ private final LuceneBasedSearchRule luceneBasedSearchRuleCaseSensitive = new LuceneBasedSearchRule(EnumSet.of(SearchRules.SearchFlags.CASE_SENSITIVE));
+ private final LuceneBasedSearchRule luceneBasedSearchRuleCaseInsensitive = new LuceneBasedSearchRule(EnumSet.noneOf(SearchRules.SearchFlags.class));
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "year:2001",
+ "year=2001",
+
+ // Current JabRef special feature: sub strings are also matched
+ "title:Marine",
+ "title=Marine",
+
+ // TODO glob patterns: "title:Marine*"
+
+ "year:2001 title:Marine",
+ "year=2001 title=Marine",
+ "year=2001 title:Marine",
+
+ "year:2001 AND title:Marine",
+
+ "year:2001 OR title:Marine",
+
+ "Marine",
+
+ // RegEx syntax of Lucene
+ "/M[a-z]+e/"
+ })
+ public void findsCaseSensitive(String query) {
+ assertTrue(luceneBasedSearchRuleCaseSensitive.validateSearchStrings(query));
+ assertTrue(luceneBasedSearchRuleCaseSensitive.applyRule(query, bibEntry));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "year:2002",
+ "year=2002",
+
+ // Current JabRef special feature: sub strings are also matched
+ "title:Marie",
+ "title=Marie",
+
+ // TODO glob patterns: "title:Marine*"
+
+ "year:2002 title:Marine",
+ "year=2002 title=Marine",
+ "year=2002 title:Marine",
+
+ "year:2002 AND title:Marine",
+
+ "year:2002 OR title:Marie",
+
+ "Marie",
+
+ "/M[0-9]+e/",
+
+ // this tests for grouping (indicated the brackets)
+ "(groups=excluded)",
+
+ // this tests for the NOT operator, grouping and Boolean AND
+ "NOT(groups=excluded) AND NOT(groups=included)"
+ })
+ public void notFindsCaseSensitive(String query) {
+ assertTrue(luceneBasedSearchRuleCaseSensitive.validateSearchStrings(query));
+ assertFalse(luceneBasedSearchRuleCaseSensitive.applyRule(query, bibEntry));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "year:2001",
+
+ "title:Marine",
+
+ "year:2001 title:Marine",
+
+ "year:2001 AND title:Marine",
+
+ "year:2001 OR title:Marine",
+
+ "Marine"
+ })
+ public void findsCaseInSensitive(String query) {
+ assertTrue(luceneBasedSearchRuleCaseInsensitive.validateSearchStrings(query));
+ assertTrue(luceneBasedSearchRuleCaseInsensitive.applyRule(query, bibEntry));
+ }
+
+}
diff --git a/src/test/java/org/jabref/model/search/rules/SearchRulesTest.java b/src/test/java/org/jabref/model/search/rules/SearchRulesTest.java
new file mode 100644
index 00000000000..5558794adc1
--- /dev/null
+++ b/src/test/java/org/jabref/model/search/rules/SearchRulesTest.java
@@ -0,0 +1,29 @@
+package org.jabref.model.search.rules;
+
+import java.util.EnumSet;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+class SearchRulesTest {
+
+ @Test
+ void simpleLuceneQueryReturnsContainsBasedSearchRule() {
+ SearchRule searchRule = SearchRules.getSearchRuleByQuery("test", EnumSet.noneOf(SearchRules.SearchFlags.class));
+ assertInstanceOf(ContainsBasedSearchRule.class, searchRule);
+ }
+
+ @Test
+ void andLuceneQueryReturnsLuceneBasedSearchRule() {
+ SearchRule searchRule = SearchRules.getSearchRuleByQuery("test AND lucene", EnumSet.noneOf(SearchRules.SearchFlags.class));
+ assertInstanceOf(LuceneBasedSearchRule.class, searchRule);
+ }
+
+ @Test
+ void simpleFieldedLuceneQueryReturnsLuceneBasedSearchRule() {
+ SearchRule searchRule = SearchRules.getSearchRuleByQuery("title:test", EnumSet.noneOf(SearchRules.SearchFlags.class));
+ assertInstanceOf(LuceneBasedSearchRule.class, searchRule);
+ }
+
+}