diff --git a/src/main/java/org/jabref/logic/bibtex/comparator/GroupDiff.java b/src/main/java/org/jabref/logic/bibtex/comparator/GroupDiff.java index 009172b5adb..4914ed9642d 100644 --- a/src/main/java/org/jabref/logic/bibtex/comparator/GroupDiff.java +++ b/src/main/java/org/jabref/logic/bibtex/comparator/GroupDiff.java @@ -9,7 +9,7 @@ public class GroupDiff { private final GroupTreeNode originalGroupRoot; private final GroupTreeNode newGroupRoot; - private GroupDiff(GroupTreeNode originalGroupRoot, GroupTreeNode newGroupRoot) { + GroupDiff(GroupTreeNode originalGroupRoot, GroupTreeNode newGroupRoot) { this.originalGroupRoot = originalGroupRoot; this.newGroupRoot = newGroupRoot; } diff --git a/src/test/java/org/jabref/gui/duplicationFinder/DuplicateSearchTest.java b/src/test/java/org/jabref/gui/duplicationFinder/DuplicateSearchTest.java new file mode 100644 index 00000000000..fb82989a8ff --- /dev/null +++ b/src/test/java/org/jabref/gui/duplicationFinder/DuplicateSearchTest.java @@ -0,0 +1,103 @@ +package org.jabref.gui.duplicationFinder; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; + +import org.jabref.gui.DialogService; +import org.jabref.gui.JabRefFrame; +import org.jabref.gui.LibraryTab; +import org.jabref.gui.StateManager; +import org.jabref.gui.undo.CountingUndoManager; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.util.OptionalObjectProperty; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.database.BibDatabaseMode; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.testutils.category.GUITest; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testfx.framework.junit5.ApplicationExtension; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@GUITest +@ExtendWith(ApplicationExtension.class) +public class DuplicateSearchTest { + + private final DialogService dialogService = spy(DialogService.class); + private final StateManager stateManager = mock(StateManager.class); + private final JabRefFrame jabRefFrame = mock(JabRefFrame.class); + private final LibraryTab libraryTab = mock(LibraryTab.class); + private final BibDatabaseContext bibDatabaseContext = mock(BibDatabaseContext.class); + private final CountingUndoManager undoManager = mock(CountingUndoManager.class); + + private DuplicateSearch duplicateSearch; + private BibEntry entry1; + + @BeforeEach + void setupDuplicateSearchInstance() { + entry1 = new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "Souti Chattopadhyay and Nicholas Nelson and Audrey Au and Natalia Morales and Christopher Sanchez and Rahul Pandita and Anita Sarma") + .withField(StandardField.TITLE, "A tale from the trenches") + .withField(StandardField.YEAR, "2020") + .withField(StandardField.DOI, "10.1145/3377811.3380330") + .withField(StandardField.SUBTITLE, "cognitive biases and software development") + .withCitationKey("Chattopadhyay2020"); + + when(jabRefFrame.getCurrentLibraryTab()).thenReturn(libraryTab); + when(stateManager.activeDatabaseProperty()).thenReturn(OptionalObjectProperty.empty()); + duplicateSearch = new DuplicateSearch(jabRefFrame, dialogService, stateManager); + } + + @Test + public void executeWithNoEntries() { + when(stateManager.getActiveDatabase()).thenReturn(Optional.of(bibDatabaseContext)); + when(bibDatabaseContext.getEntries()).thenReturn(Collections.emptyList()); + + duplicateSearch.execute(); + verify(dialogService, times(1)).notify(Localization.lang("Searching for duplicates...")); + } + + @Test + public void executeWithOneEntry() { + when(stateManager.getActiveDatabase()).thenReturn(Optional.of(bibDatabaseContext)); + when(bibDatabaseContext.getEntries()).thenReturn(Collections.singletonList(entry1)); + + duplicateSearch.execute(); + verify(dialogService, times(1)).notify(Localization.lang("Searching for duplicates...")); + } + + @Test + public void executeWithNoDuplicates() { + BibEntry entry2 = new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.AUTHOR, "Tale S Sastad and Karl Thomas Hjelmervik") + .withField(StandardField.TITLE, "Synthesizing Realistic, High-Resolution Anti-Submarine Sonar Data\n") + .withField(StandardField.YEAR, "2018") + .withField(StandardField.DOI, "10.1109/OCEANSKOBE.2018.8558837") + .withCitationKey("Sastad2018"); + + when(stateManager.getActiveDatabase()).thenReturn(Optional.of(bibDatabaseContext)); + when(bibDatabaseContext.getEntries()).thenReturn(Arrays.asList(entry1, entry2)); + when(bibDatabaseContext.getMode()).thenReturn(BibDatabaseMode.BIBTEX); + when(libraryTab.getBibDatabaseContext()).thenReturn(bibDatabaseContext); + when(libraryTab.getDatabase()).thenReturn(mock(BibDatabase.class)); + when(libraryTab.getUndoManager()).thenReturn(undoManager); + when(undoManager.addEdit(mock(NamedCompound.class))).thenReturn(true); + + duplicateSearch.execute(); + verify(dialogService, times(1)).notify(Localization.lang("Searching for duplicates...")); + verify(dialogService, times(1)).notify(Localization.lang("Duplicates found") + ": " + String.valueOf(0) + ' ' + + Localization.lang("pairs processed") + ": " + String.valueOf(0)); + } +} diff --git a/src/test/java/org/jabref/gui/edit/ManageKeywordsViewModelTest.java b/src/test/java/org/jabref/gui/edit/ManageKeywordsViewModelTest.java new file mode 100644 index 00000000000..980ad54aa7a --- /dev/null +++ b/src/test/java/org/jabref/gui/edit/ManageKeywordsViewModelTest.java @@ -0,0 +1,83 @@ +package org.jabref.gui.edit; + +import java.util.Arrays; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.preferences.PreferencesService; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ManageKeywordsViewModelTest { + + private final PreferencesService preferences = mock(PreferencesService.class); + private ManageKeywordsViewModel keywordsViewModel; + + @BeforeEach + void setUp() { + BibEntry entryOne = new BibEntry(StandardEntryType.Article) + .withField(StandardField.AUTHOR, "Prakhar Srivastava and Nishant Singh") + .withField(StandardField.YEAR, "2020") + .withField(StandardField.DOI, "10.1109/PARC49193.2020.236624") + .withField(StandardField.ISBN, "978-1-7281-6575-2") + .withField(StandardField.JOURNALTITLE, "2020 International Conference on Power Electronics & IoT Applications in Renewable Energy and its Control (PARC)") + .withField(StandardField.PAGES, "351--354") + .withField(StandardField.PUBLISHER, "IEEE") + .withField(StandardField.TITLE, "Automatized Medical Chatbot (Medibot)") + .withField(StandardField.KEYWORDS, "Human-machine interaction, Chatbot, Medical Chatbot, Natural Language Processing, Machine Learning, Bot"); + + BibEntry entryTwo = new BibEntry(StandardEntryType.Article) + .withField(StandardField.AUTHOR, "Mladjan Jovanovic and Marcos Baez and Fabio Casati") + .withField(StandardField.DATE, "November 2020") + .withField(StandardField.YEAR, "2020") + .withField(StandardField.DOI, "10.1109/MIC.2020.3037151") + .withField(StandardField.ISSN, "1941-0131") + .withField(StandardField.JOURNALTITLE, "IEEE Internet Computing") + .withField(StandardField.PAGES, "1--1") + .withField(StandardField.PUBLISHER, "IEEE") + .withField(StandardField.TITLE, "Chatbots as conversational healthcare services") + .withField(StandardField.KEYWORDS, "Chatbot, Medical services, Internet, Data collection, Medical diagnostic imaging, Automation, Vocabulary"); + + List entries = List.of(entryOne, entryTwo); + + char delimiter = ','; + when(preferences.getKeywordDelimiter()).thenReturn(delimiter); + + keywordsViewModel = new ManageKeywordsViewModel(preferences, entries); + } + + @Test + void keywordsFilledInCorrectly() { + ObservableList addedKeywords = keywordsViewModel.getKeywords(); + List expectedKeywordsList = Arrays.asList("Human-machine interaction", "Chatbot", "Medical Chatbot", + "Natural Language Processing", "Machine Learning", "Bot", "Chatbot", "Medical services", "Internet", + "Data collection", "Medical diagnostic imaging", "Automation", "Vocabulary"); + + assertEquals(FXCollections.observableList(expectedKeywordsList), addedKeywords); + } + + @Test + void removedKeywordNotIncludedInKeywordsList() { + ObservableList modifiedKeywords = keywordsViewModel.getKeywords(); + List originalKeywordsList = Arrays.asList("Human-machine interaction", "Chatbot", "Medical Chatbot", + "Natural Language Processing", "Machine Learning", "Bot", "Chatbot", "Medical services", "Internet", + "Data collection", "Medical diagnostic imaging", "Automation", "Vocabulary"); + + assertEquals(FXCollections.observableList(originalKeywordsList), modifiedKeywords, "compared lists are not identical"); + + keywordsViewModel.removeKeyword("Human-machine interaction"); + + assertNotEquals(FXCollections.observableList(originalKeywordsList), modifiedKeywords, "compared lists are identical"); + } +} diff --git a/src/test/java/org/jabref/logic/bibtex/comparator/GroupDiffTest.java b/src/test/java/org/jabref/logic/bibtex/comparator/GroupDiffTest.java new file mode 100644 index 00000000000..002a71dec46 --- /dev/null +++ b/src/test/java/org/jabref/logic/bibtex/comparator/GroupDiffTest.java @@ -0,0 +1,65 @@ +package org.jabref.logic.bibtex.comparator; + +import java.util.Optional; + +import org.jabref.model.groups.AllEntriesGroup; +import org.jabref.model.groups.ExplicitGroup; +import org.jabref.model.groups.GroupHierarchyType; +import org.jabref.model.groups.GroupTreeNode; +import org.jabref.model.metadata.MetaData; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class GroupDiffTest { + + private final MetaData originalMetaData = mock(MetaData.class); + private final MetaData newMetaData = mock(MetaData.class); + private GroupTreeNode rootOriginal; + + @BeforeEach + void setup() { + rootOriginal = GroupTreeNode.fromGroup(new AllEntriesGroup("All entries")); + rootOriginal.addSubgroup(new ExplicitGroup("ExplicitA", GroupHierarchyType.INCLUDING, ',')); + GroupTreeNode parent = rootOriginal + .addSubgroup(new ExplicitGroup("ExplicitParent", GroupHierarchyType.INDEPENDENT, ',')); + parent.addSubgroup(new ExplicitGroup("ExplicitNode", GroupHierarchyType.REFINING, ',')); + } + + @Test + void compareEmptyGroups() { + when(originalMetaData.getGroups()).thenReturn(Optional.empty()); + when(newMetaData.getGroups()).thenReturn(Optional.empty()); + + assertEquals(Optional.empty(), GroupDiff.compare(originalMetaData, newMetaData)); + } + + @Test + void compareGroupWithItself() { + when(originalMetaData.getGroups()).thenReturn(Optional.of(rootOriginal)); + when(newMetaData.getGroups()).thenReturn(Optional.of(rootOriginal)); + + assertEquals(Optional.empty(), GroupDiff.compare(originalMetaData, newMetaData)); + } + + @Test + void compareWithChangedGroup() { + GroupTreeNode rootModified = GroupTreeNode.fromGroup(new AllEntriesGroup("All entries")); + rootModified.addSubgroup(new ExplicitGroup("ExplicitA", GroupHierarchyType.INCLUDING, ',')); + + when(originalMetaData.getGroups()).thenReturn(Optional.of(rootOriginal)); + when(newMetaData.getGroups()).thenReturn(Optional.of(rootModified)); + + Optional groupDiff = GroupDiff.compare(originalMetaData, newMetaData); + + Optional expectedGroupDiff = Optional.of(new GroupDiff(newMetaData.getGroups().get(), originalMetaData.getGroups().get())); + + assertEquals(expectedGroupDiff.get().getNewGroupRoot(), groupDiff.get().getNewGroupRoot()); + assertEquals(expectedGroupDiff.get().getOriginalGroupRoot(), groupDiff.get().getOriginalGroupRoot()); + } + +} diff --git a/src/test/java/org/jabref/model/groups/GroupTreeNodeTest.java b/src/test/java/org/jabref/model/groups/GroupTreeNodeTest.java index 05886e41c76..faa4d9d3d56 100644 --- a/src/test/java/org/jabref/model/groups/GroupTreeNodeTest.java +++ b/src/test/java/org/jabref/model/groups/GroupTreeNodeTest.java @@ -1,10 +1,12 @@ package org.jabref.model.groups; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; +import org.jabref.model.FieldChange; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.StandardField; import org.jabref.model.search.matchers.AndMatcher; @@ -109,7 +111,7 @@ public static GroupTreeNode getRoot() { } @BeforeEach - void setUp() throws Exception { + void setUp() { entries.clear(); entry = new BibEntry(); entries.add(entry); @@ -160,12 +162,12 @@ void getSearchRuleForIncludingGroupReturnsGroupOrSubgroupAsMatcher() { } @Test - void findMatchesReturnsEmptyForEmptyList() throws Exception { + void findMatchesReturnsEmptyForEmptyList() { assertEquals(Collections.emptyList(), getNodeInSimpleTree().findMatches(Collections.emptyList())); } @Test - void findMatchesOneEntry() throws Exception { + void findMatchesOneEntry() { GroupTreeNode parent = getNodeInSimpleTree(); GroupTreeNode node = parent.addSubgroup( new WordKeywordGroup("node", GroupHierarchyType.INDEPENDENT, StandardField.AUTHOR, "author2", true, ',', false)); @@ -173,7 +175,7 @@ void findMatchesOneEntry() throws Exception { } @Test - void findMatchesMultipleEntries() throws Exception { + void findMatchesMultipleEntries() { GroupTreeNode parent = getNodeInSimpleTree(); GroupTreeNode node = parent.addSubgroup( new WordKeywordGroup("node", GroupHierarchyType.INDEPENDENT, StandardField.AUTHOR, "author1", true, ',', false)); @@ -181,7 +183,7 @@ void findMatchesMultipleEntries() throws Exception { } @Test - void findMatchesWorksForRefiningGroups() throws Exception { + void findMatchesWorksForRefiningGroups() { GroupTreeNode grandParent = getNodeInSimpleTree(); GroupTreeNode parent = grandParent.addSubgroup( new WordKeywordGroup("node", GroupHierarchyType.INDEPENDENT, StandardField.AUTHOR, "author2", true, ',', false)); @@ -191,7 +193,7 @@ void findMatchesWorksForRefiningGroups() throws Exception { } @Test - void findMatchesWorksForHierarchyOfIndependentGroups() throws Exception { + void findMatchesWorksForHierarchyOfIndependentGroups() { GroupTreeNode grandParent = getNodeInSimpleTree(); GroupTreeNode parent = grandParent.addSubgroup( new WordKeywordGroup("node", GroupHierarchyType.INDEPENDENT, StandardField.AUTHOR, "author2", true, ',', false)); @@ -201,7 +203,7 @@ void findMatchesWorksForHierarchyOfIndependentGroups() throws Exception { } @Test - void setGroupChangesUnderlyingGroup() throws Exception { + void setGroupChangesUnderlyingGroup() { GroupTreeNode node = getNodeInSimpleTree(); AbstractGroup newGroup = new ExplicitGroup("NewGroup", GroupHierarchyType.INDEPENDENT, ','); @@ -211,7 +213,7 @@ void setGroupChangesUnderlyingGroup() throws Exception { } @Test - void setGroupAddsPreviousAssignmentsExplicitToExplicit() throws Exception { + void setGroupAddsPreviousAssignmentsExplicitToExplicit() { ExplicitGroup oldGroup = new ExplicitGroup("OldGroup", GroupHierarchyType.INDEPENDENT, ','); oldGroup.add(entry); GroupTreeNode node = GroupTreeNode.fromGroup(oldGroup); @@ -223,7 +225,7 @@ void setGroupAddsPreviousAssignmentsExplicitToExplicit() throws Exception { } @Test - void setGroupWithFalseDoesNotAddsPreviousAssignments() throws Exception { + void setGroupWithFalseDoesNotAddsPreviousAssignments() { ExplicitGroup oldGroup = new ExplicitGroup("OldGroup", GroupHierarchyType.INDEPENDENT, ','); oldGroup.add(entry); GroupTreeNode node = GroupTreeNode.fromGroup(oldGroup); @@ -235,7 +237,7 @@ void setGroupWithFalseDoesNotAddsPreviousAssignments() throws Exception { } @Test - void setGroupAddsOnlyPreviousAssignments() throws Exception { + void setGroupAddsOnlyPreviousAssignments() { ExplicitGroup oldGroup = new ExplicitGroup("OldGroup", GroupHierarchyType.INDEPENDENT, ','); assertFalse(oldGroup.isMatch(entry)); GroupTreeNode node = GroupTreeNode.fromGroup(oldGroup); @@ -247,7 +249,7 @@ void setGroupAddsOnlyPreviousAssignments() throws Exception { } @Test - void setGroupExplicitToSearchDoesNotKeepPreviousAssignments() throws Exception { + void setGroupExplicitToSearchDoesNotKeepPreviousAssignments() { ExplicitGroup oldGroup = new ExplicitGroup("OldGroup", GroupHierarchyType.INDEPENDENT, ','); oldGroup.add(entry); GroupTreeNode node = GroupTreeNode.fromGroup(oldGroup); @@ -259,7 +261,7 @@ void setGroupExplicitToSearchDoesNotKeepPreviousAssignments() throws Exception { } @Test - void setGroupExplicitToExplicitIsRenameAndSoRemovesPreviousAssignment() throws Exception { + void setGroupExplicitToExplicitIsRenameAndSoRemovesPreviousAssignment() { ExplicitGroup oldGroup = new ExplicitGroup("OldGroup", GroupHierarchyType.INDEPENDENT, ','); oldGroup.add(entry); GroupTreeNode node = GroupTreeNode.fromGroup(oldGroup); @@ -271,7 +273,7 @@ void setGroupExplicitToExplicitIsRenameAndSoRemovesPreviousAssignment() throws E } @Test - void getChildByPathFindsCorrectChildInSecondLevel() throws Exception { + void getChildByPathFindsCorrectChildInSecondLevel() { GroupTreeNode root = getRoot(); GroupTreeNode child = getNodeInSimpleTree(root); @@ -279,9 +281,67 @@ void getChildByPathFindsCorrectChildInSecondLevel() throws Exception { } @Test - void getPathSimpleTree() throws Exception { + void getChildByPathDoesNotFindChildWhenInvalidPath() { + GroupTreeNode root = getRoot(); + + // use side effect of method, which builds the group tree + getNodeInSimpleTree(root); + + assertEquals(Optional.empty(), root.getChildByPath("ExplicitParent > ExplicitChildNode")); + } + + @Test + void getPathSimpleTree() { GroupTreeNode node = getNodeInSimpleTree(); assertEquals("ExplicitParent > ExplicitNode", node.getPath()); } + + @Test + void onlyRootAndChildNodeContainAtLeastOneEntry() { + GroupTreeNode rootNode = getRoot(); + rootNode.addSubgroup(new ExplicitGroup("ExplicitA", GroupHierarchyType.INCLUDING, ',')); + GroupTreeNode parent = rootNode + .addSubgroup(new ExplicitGroup("ExplicitParent", GroupHierarchyType.INDEPENDENT, ',')); + GroupTreeNode child = parent.addSubgroup(new ExplicitGroup("ExplicitNode", GroupHierarchyType.REFINING, ',')); + + BibEntry newEntry = new BibEntry().withField(StandardField.AUTHOR, "Stephen King"); + child.addEntriesToGroup(Collections.singletonList(newEntry)); + entries.add(newEntry); + + assertEquals(rootNode.getContainingGroups(entries, false), Arrays.asList(rootNode, child)); + } + + @Test + void onlySubgroupsContainAllEntries() { + GroupTreeNode rootNode = getRoot(); + rootNode.addSubgroup(new ExplicitGroup("ExplicitA", GroupHierarchyType.INCLUDING, ',')); + GroupTreeNode parent = rootNode + .addSubgroup(new ExplicitGroup("ExplicitParent", GroupHierarchyType.INDEPENDENT, ',')); + GroupTreeNode firstChild = parent.addSubgroup(new ExplicitGroup("ExplicitNode", GroupHierarchyType.REFINING, ',')); + GroupTreeNode secondChild = parent.addSubgroup(new ExplicitGroup("ExplicitSecondNode", GroupHierarchyType.REFINING, ',')); + GroupTreeNode grandChild = secondChild.addSubgroup(new ExplicitGroup("ExplicitNodeThirdLevel", GroupHierarchyType.REFINING, ',')); + + parent.addEntriesToGroup(Collections.singletonList(entry)); + firstChild.addEntriesToGroup(entries); + secondChild.addEntriesToGroup(entries); + grandChild.addEntriesToGroup(entries); + assertEquals(parent.getContainingGroups(entries, true), Arrays.asList(firstChild, secondChild, grandChild)); + } + + @Test + void addEntriesToGroupWorksNotForGroupsNotSupportingExplicitAddingOfEntries() { + GroupTreeNode searchGroup = new GroupTreeNode(new SearchGroup("Search A", GroupHierarchyType.INCLUDING, "searchExpression", true, false)); + List fieldChanges = searchGroup.addEntriesToGroup(entries); + + assertEquals(Collections.emptyList(), fieldChanges); + } + + @Test + void removeEntriesFromGroupWorksNotForGroupsNotSupportingExplicitRemovalOfEntries() { + GroupTreeNode searchGroup = new GroupTreeNode(new SearchGroup("Search A", GroupHierarchyType.INCLUDING, "searchExpression", true, false)); + List fieldChanges = searchGroup.removeEntriesFromGroup(entries); + + assertEquals(Collections.emptyList(), fieldChanges); + } }