Merge branch 'next' of calcium:git/Sone into next
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 22 Feb 2019 21:28:35 +0000 (22:28 +0100)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Fri, 22 Feb 2019 21:28:35 +0000 (22:28 +0100)
src/main/java/net/pterodactylus/sone/core/Core.java
src/main/java/net/pterodactylus/sone/core/Preferences.java
src/main/java/net/pterodactylus/sone/core/SoneChangeDetector.java [deleted file]
src/main/kotlin/net/pterodactylus/sone/core/SoneChangeCollector.kt [deleted file]
src/main/kotlin/net/pterodactylus/sone/core/SoneComparison.kt [new file with mode: 0644]
src/main/kotlin/net/pterodactylus/sone/core/UpdatedSoneProcessor.kt
src/test/java/net/pterodactylus/sone/core/PreferencesTest.java [deleted file]
src/test/java/net/pterodactylus/sone/core/SoneChangeDetectorTest.java [deleted file]
src/test/kotlin/net/pterodactylus/sone/core/PreferencesTest.kt [new file with mode: 0644]
src/test/kotlin/net/pterodactylus/sone/core/SoneChangeCollectorTest.kt [deleted file]
src/test/kotlin/net/pterodactylus/sone/core/SoneComparisonTest.kt [new file with mode: 0644]

index 4319d8b..8e20176 100644 (file)
@@ -48,8 +48,6 @@ import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidImageFound;
 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidParentAlbumFound;
 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostFound;
 import net.pterodactylus.sone.core.ConfigurationSoneParser.InvalidPostReplyFound;
-import net.pterodactylus.sone.core.SoneChangeDetector.PostProcessor;
-import net.pterodactylus.sone.core.SoneChangeDetector.PostReplyProcessor;
 import net.pterodactylus.sone.core.event.ImageInsertFinishedEvent;
 import net.pterodactylus.sone.core.event.InsertionDelayChangedEvent;
 import net.pterodactylus.sone.core.event.MarkPostKnownEvent;
@@ -851,48 +849,33 @@ public class Core extends AbstractService implements SoneProvider, PostProvider,
                }
        }
 
-       private List<Object> collectEventsForChangesInSone(Sone oldSone,
-                       final Sone newSone) {
-               final List<Object> events = new ArrayList<Object>();
-               SoneChangeDetector soneChangeDetector = new SoneChangeDetector(
-                               oldSone);
-               soneChangeDetector.onNewPosts(new PostProcessor() {
-                       @Override
-                       public void processPost(Post post) {
-                               if (post.getSone().equals(newSone)) {
-                                       post.setKnown(true);
-                               } else if (post.getTime() < database.getFollowingTime(newSone.getId())) {
-                                       post.setKnown(true);
-                               } else if (!post.isKnown()) {
-                                       events.add(new NewPostFoundEvent(post));
-                               }
-                       }
-               });
-               soneChangeDetector.onRemovedPosts(new PostProcessor() {
-                       @Override
-                       public void processPost(Post post) {
-                               events.add(new PostRemovedEvent(post));
-                       }
-               });
-               soneChangeDetector.onNewPostReplies(new PostReplyProcessor() {
-                       @Override
-                       public void processPostReply(PostReply postReply) {
-                               if (postReply.getSone().equals(newSone)) {
-                                       postReply.setKnown(true);
-                               } else if (postReply.getTime() < database.getFollowingTime(newSone.getId())) {
-                                       postReply.setKnown(true);
-                               } else if (!postReply.isKnown()) {
-                                       events.add(new NewPostReplyFoundEvent(postReply));
-                               }
+       private List<Object> collectEventsForChangesInSone(Sone oldSone, Sone newSone) {
+               List<Object> events = new ArrayList<>();
+               SoneComparison soneComparison = new SoneComparison(oldSone, newSone);
+               for (Post newPost : soneComparison.getNewPosts()) {
+                       if (newPost.getSone().equals(newSone)) {
+                               newPost.setKnown(true);
+                       } else if (newPost.getTime() < database.getFollowingTime(newSone.getId())) {
+                               newPost.setKnown(true);
+                       } else if (!newPost.isKnown()) {
+                               events.add(new NewPostFoundEvent(newPost));
                        }
-               });
-               soneChangeDetector.onRemovedPostReplies(new PostReplyProcessor() {
-                       @Override
-                       public void processPostReply(PostReply postReply) {
-                               events.add(new PostReplyRemovedEvent(postReply));
+               }
+               for (Post post : soneComparison.getRemovedPosts()) {
+                       events.add(new PostRemovedEvent(post));
+               }
+               for (PostReply postReply : soneComparison.getNewPostReplies()) {
+                       if (postReply.getSone().equals(newSone)) {
+                               postReply.setKnown(true);
+                       } else if (postReply.getTime() < database.getFollowingTime(newSone.getId())) {
+                               postReply.setKnown(true);
+                       } else if (!postReply.isKnown()) {
+                               events.add(new NewPostReplyFoundEvent(postReply));
                        }
-               });
-               soneChangeDetector.detectChanges(newSone);
+               }
+               for (PostReply postReply : soneComparison.getRemovedPostReplies()) {
+                       events.add(new PostReplyRemovedEvent(postReply));
+               }
                return events;
        }
 
index ce56e8b..73d964c 100644 (file)
@@ -137,6 +137,7 @@ public class Preferences {
         */
        public Preferences setPostsPerPage(Integer postsPerPage) {
                this.postsPerPage.set(postsPerPage);
+               eventBus.post(new PreferenceChangedEvent("PostsPerPage", getPostsPerPage()));
                return this;
        }
 
diff --git a/src/main/java/net/pterodactylus/sone/core/SoneChangeDetector.java b/src/main/java/net/pterodactylus/sone/core/SoneChangeDetector.java
deleted file mode 100644 (file)
index 7dd882d..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-package net.pterodactylus.sone.core;
-
-import static com.google.common.base.Optional.absent;
-import static com.google.common.base.Optional.fromNullable;
-import static com.google.common.collect.FluentIterable.from;
-
-import java.util.Collection;
-
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.data.Sone;
-
-import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
-import com.google.common.collect.FluentIterable;
-
-/**
- * Compares the contents of two {@link Sone}s and fires events for new and
- * removed elements.
- */
-public class SoneChangeDetector {
-
-       private final Sone oldSone;
-       private Optional<PostProcessor> newPostProcessor = absent();
-       private Optional<PostProcessor> removedPostProcessor = absent();
-       private Optional<PostReplyProcessor> newPostReplyProcessor = absent();
-       private Optional<PostReplyProcessor> removedPostReplyProcessor = absent();
-
-       public SoneChangeDetector(Sone oldSone) {
-               this.oldSone = oldSone;
-       }
-
-       public void onNewPosts(PostProcessor newPostProcessor) {
-               this.newPostProcessor = fromNullable(newPostProcessor);
-       }
-
-       public void onRemovedPosts(PostProcessor removedPostProcessor) {
-               this.removedPostProcessor = fromNullable(removedPostProcessor);
-       }
-
-       public void onNewPostReplies(PostReplyProcessor newPostReplyProcessor) {
-               this.newPostReplyProcessor = fromNullable(newPostReplyProcessor);
-       }
-
-       public void onRemovedPostReplies(
-                       PostReplyProcessor removedPostReplyProcessor) {
-               this.removedPostReplyProcessor = fromNullable(removedPostReplyProcessor);
-       }
-
-       public void detectChanges(Sone newSone) {
-               processPosts(from(newSone.getPosts()).filter(
-                               notContainedIn(oldSone.getPosts())), newPostProcessor);
-               processPosts(from(oldSone.getPosts()).filter(
-                               notContainedIn(newSone.getPosts())), removedPostProcessor);
-               processPostReplies(from(newSone.getReplies()).filter(
-                               notContainedIn(oldSone.getReplies())), newPostReplyProcessor);
-               processPostReplies(from(oldSone.getReplies()).filter(
-                               notContainedIn(newSone.getReplies())), removedPostReplyProcessor);
-       }
-
-       private void processPostReplies(FluentIterable<PostReply> postReplies,
-                       Optional<PostReplyProcessor> postReplyProcessor) {
-               for (PostReply postReply : postReplies) {
-                       notifyPostReplyProcessor(postReplyProcessor, postReply);
-               }
-       }
-
-       private void notifyPostReplyProcessor(
-                       Optional<PostReplyProcessor> postReplyProcessor,
-                       PostReply postReply) {
-               if (postReplyProcessor.isPresent()) {
-                       postReplyProcessor.get()
-                                       .processPostReply(postReply);
-               }
-       }
-
-       private void processPosts(FluentIterable<Post> posts,
-                       Optional<PostProcessor> newPostProcessor) {
-               for (Post post : posts) {
-                       notifyPostProcessor(newPostProcessor, post);
-               }
-       }
-
-       private void notifyPostProcessor(Optional<PostProcessor> postProcessor,
-                       Post newPost) {
-               if (postProcessor.isPresent()) {
-                       postProcessor.get().processPost(newPost);
-               }
-       }
-
-       private <T> Predicate<T> notContainedIn(final Collection<T> posts) {
-               return new Predicate<T>() {
-                       @Override
-                       public boolean apply(T element) {
-                               return !posts.contains(element);
-                       }
-               };
-       }
-
-       public interface PostProcessor {
-
-               void processPost(Post post);
-
-       }
-
-       public interface PostReplyProcessor {
-
-               void processPostReply(PostReply postReply);
-
-       }
-
-}
diff --git a/src/main/kotlin/net/pterodactylus/sone/core/SoneChangeCollector.kt b/src/main/kotlin/net/pterodactylus/sone/core/SoneChangeCollector.kt
deleted file mode 100644 (file)
index 609a55f..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-package net.pterodactylus.sone.core
-
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.data.Sone
-
-/**
- * Wrapper around a [SoneChangeDetector] that can turn changed elements into
- * different elements which are then being returned. This can be used to turn
- * changed elements into events for further processing.
- */
-class SoneChangeCollector(private val oldSone: Sone) {
-
-       private val newPostEventCreators = mutableListOf<(Post) -> Any?>()
-       private val removedPostEventCreators = mutableListOf<(Post) -> Any?>()
-       private val newPostReplyEventCreators = mutableListOf<(PostReply) -> Any?>()
-       private val removedPostReplyEventCreators = mutableListOf<(PostReply) -> Any?>()
-
-       fun onNewPost(postProcessor: (Post) -> Unit) =
-                       newPostEventCreators.add { postProcessor(it).let { null } }.let { this }
-
-       fun newPostEvent(postEventCreator: (Post) -> Any?) =
-                       newPostEventCreators.add(postEventCreator).let { this }
-
-       fun onNewPostReply(postReplyProcessor: (PostReply) -> Unit) =
-                       newPostReplyEventCreators.add { postReplyProcessor(it).let { null } }.let { this }
-
-       fun newPostReplyEvent(postReplyEventCreator: (PostReply) -> Any?) =
-                       newPostReplyEventCreators.add(postReplyEventCreator).let { this }
-
-       fun removedPostEvent(postEventCreator: (Post) -> Any?) =
-                       removedPostEventCreators.add(postEventCreator).let { this }
-
-       fun onRemovedPostReply(postReplyEventCreator: (PostReply) -> Any?) =
-                       removedPostReplyEventCreators.add(postReplyEventCreator).let { this }
-
-       fun detectChanges(newSone: Sone): List<Any> {
-               val events = mutableListOf<Any>()
-               SoneChangeDetector(oldSone).apply {
-                       onNewPosts { post -> newPostEventCreators.mapNotNull { it(post) }.forEach { events.add(it) } }
-                       onRemovedPosts { post -> removedPostEventCreators.mapNotNull { it(post) }.forEach { events.add(it) } }
-                       onNewPostReplies { reply -> newPostReplyEventCreators.mapNotNull { it(reply) }.forEach { events.add(it) } }
-                       onRemovedPostReplies { reply -> removedPostReplyEventCreators.mapNotNull { it(reply) }.forEach { events.add(it) } }
-                       detectChanges(newSone)
-               }
-               return events
-       }
-
-}
diff --git a/src/main/kotlin/net/pterodactylus/sone/core/SoneComparison.kt b/src/main/kotlin/net/pterodactylus/sone/core/SoneComparison.kt
new file mode 100644 (file)
index 0000000..9a67f80
--- /dev/null
@@ -0,0 +1,12 @@
+package net.pterodactylus.sone.core
+
+import net.pterodactylus.sone.data.*
+
+class SoneComparison(private val oldSone: Sone, private val newSone: Sone) {
+
+       val newPosts: Collection<Post> get() = newSone.posts - oldSone.posts
+       val removedPosts: Collection<Post> get() = oldSone.posts - newSone.posts
+       val newPostReplies: Collection<PostReply> get() = newSone.replies - oldSone.replies
+       val removedPostReplies: Collection<PostReply> get() = oldSone.replies - newSone.replies
+
+}
index 580f227..ebaa3c8 100644 (file)
@@ -1,16 +1,13 @@
 package net.pterodactylus.sone.core
 
-import com.google.common.eventbus.EventBus
-import com.google.inject.ImplementedBy
-import net.pterodactylus.sone.core.event.NewPostFoundEvent
-import net.pterodactylus.sone.core.event.NewPostReplyFoundEvent
-import net.pterodactylus.sone.core.event.PostRemovedEvent
-import net.pterodactylus.sone.core.event.PostReplyRemovedEvent
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.data.Sone.SoneStatus
-import net.pterodactylus.sone.database.Database
-import net.pterodactylus.sone.utils.ifFalse
-import net.pterodactylus.util.logging.Logging
+import com.google.common.eventbus.*
+import com.google.inject.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.data.Sone.*
+import net.pterodactylus.sone.database.*
+import net.pterodactylus.sone.utils.*
+import net.pterodactylus.util.logging.*
 import javax.inject.Inject
 
 /**
@@ -35,9 +32,23 @@ abstract class BasicUpdateSoneProcessor(private val database: Database, private
                        logger.fine("Downloaded Sone $sone can not update stored Sone $storedSone.")
                        return
                }
-               collectEventsForChanges(storedSone, sone)
-                               .also { database.storeSone(sone) }
-                               .forEach(eventBus::post)
+
+               SoneComparison(storedSone, sone).apply {
+                       newPosts
+                                       .onEach { post -> if (post.time <= sone.followingTime) post.isKnown = true }
+                                       .mapNotNull { post -> post.isKnown.ifFalse { NewPostFoundEvent(post) } }
+                                       .forEach(eventBus::post)
+                       removedPosts
+                                       .map { PostRemovedEvent(it) }
+                                       .forEach(eventBus::post)
+                       newPostReplies
+                                       .onEach { postReply -> if (postReply.time <= sone.followingTime) postReply.isKnown = true }
+                                       .mapNotNull { postReply -> postReply.isKnown.ifFalse { NewPostReplyFoundEvent(postReply) } }
+                                       .forEach(eventBus::post)
+                       removedPostReplies
+                                       .map { PostReplyRemovedEvent(it) }
+                                       .forEach(eventBus::post)
+               }
                sone.options = storedSone.options
                sone.isKnown = storedSone.isKnown
                sone.status = if (sone.time != 0L) SoneStatus.idle else SoneStatus.unknown
@@ -47,16 +58,6 @@ abstract class BasicUpdateSoneProcessor(private val database: Database, private
 
        private val Sone.followingTime get() = database.getFollowingTime(id) ?: 0
 
-       private fun collectEventsForChanges(oldSone: Sone, newSone: Sone): List<Any> =
-                       SoneChangeCollector(oldSone)
-                                       .onNewPost { post -> if (post.time <= newSone.followingTime) post.isKnown = true }
-                                       .newPostEvent { post -> post.isKnown.ifFalse { NewPostFoundEvent(post) } }
-                                       .removedPostEvent { PostRemovedEvent(it) }
-                                       .onNewPostReply { postReply -> if (postReply.time <= newSone.followingTime) postReply.isKnown = true }
-                                       .newPostReplyEvent { postReply -> postReply.isKnown.ifFalse { NewPostReplyFoundEvent(postReply) } }
-                                       .onRemovedPostReply { PostReplyRemovedEvent(it) }
-                                       .detectChanges(newSone)
-
 }
 
 class DefaultUpdateSoneProcessor @Inject constructor(database: Database, eventBus: EventBus) :
diff --git a/src/test/java/net/pterodactylus/sone/core/PreferencesTest.java b/src/test/java/net/pterodactylus/sone/core/PreferencesTest.java
deleted file mode 100644 (file)
index fdae222..0000000
+++ /dev/null
@@ -1,319 +0,0 @@
-package net.pterodactylus.sone.core;
-
-import static net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.ALWAYS;
-import static net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.NO;
-import static net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.WRITING;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.hasItem;
-import static org.hamcrest.Matchers.is;
-import static org.mockito.ArgumentCaptor.forClass;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-import net.pterodactylus.sone.core.event.InsertionDelayChangedEvent;
-import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired;
-import net.pterodactylus.sone.fcp.event.FcpInterfaceActivatedEvent;
-import net.pterodactylus.sone.fcp.event.FcpInterfaceDeactivatedEvent;
-import net.pterodactylus.sone.fcp.event.FullAccessRequiredChanged;
-
-import com.google.common.eventbus.EventBus;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-
-/**
- * Unit test for {@link Preferences}.
- */
-public class PreferencesTest {
-
-       private final EventBus eventBus = mock(EventBus.class);
-       private final Preferences preferences = new Preferences(eventBus);
-
-       @Test
-       public void preferencesRetainInsertionDelay() {
-               preferences.setInsertionDelay(15);
-               assertThat(preferences.getInsertionDelay(), is(15));
-       }
-
-       @Test
-       public void preferencesSendsEventOnSettingInsertionDelay() {
-               preferences.setInsertionDelay(15);
-               ArgumentCaptor<Object> eventsCaptor = forClass(Object.class);
-               verify(eventBus, atLeastOnce()).post(eventsCaptor.capture());
-               assertThat(eventsCaptor.getAllValues(), hasItem(new InsertionDelayChangedEvent(15)));
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void invalidInsertionDelayIsRejected() {
-               preferences.setInsertionDelay(-15);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenInsertionDelayIsSetToNull() {
-               preferences.setInsertionDelay(null);
-               assertThat(preferences.getInsertionDelay(), is(60));
-       }
-
-       @Test
-       public void preferencesStartWithInsertionDelayDefaultValue() {
-               assertThat(preferences.getInsertionDelay(), is(60));
-       }
-
-       @Test
-       public void preferencesRetainPostsPerPage() {
-               preferences.setPostsPerPage(15);
-               assertThat(preferences.getPostsPerPage(), is(15));
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void invalidPostsPerPageIsRejected() {
-               preferences.setPostsPerPage(-15);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenPostsPerPageIsSetToNull() {
-               preferences.setPostsPerPage(null);
-               assertThat(preferences.getPostsPerPage(), is(10));
-       }
-
-       @Test
-       public void preferencesStartWithPostsPerPageDefaultValue() {
-               assertThat(preferences.getPostsPerPage(), is(10));
-       }
-
-       @Test
-       public void preferencesRetainImagesPerPage() {
-               preferences.setImagesPerPage(15);
-               assertThat(preferences.getImagesPerPage(), is(15));
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void invalidImagesPerPageIsRejected() {
-               preferences.setImagesPerPage(-15);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenImagesPerPageIsSetToNull() {
-               preferences.setImagesPerPage(null);
-               assertThat(preferences.getImagesPerPage(), is(9));
-       }
-
-       @Test
-       public void preferencesStartWithImagesPerPageDefaultValue() {
-               assertThat(preferences.getImagesPerPage(), is(9));
-       }
-
-       @Test
-       public void preferencesRetainCharactersPerPost() {
-               preferences.setCharactersPerPost(150);
-               assertThat(preferences.getCharactersPerPost(), is(150));
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void invalidCharactersPerPostIsRejected() {
-               preferences.setCharactersPerPost(-15);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenCharactersPerPostIsSetToNull() {
-               preferences.setCharactersPerPost(null);
-               assertThat(preferences.getCharactersPerPost(), is(400));
-       }
-
-       @Test
-       public void preferencesStartWithCharactersPerPostDefaultValue() {
-               assertThat(preferences.getCharactersPerPost(), is(400));
-       }
-
-       @Test
-       public void preferencesRetainPostCutOffLength() {
-               preferences.setPostCutOffLength(150);
-               assertThat(preferences.getPostCutOffLength(), is(150));
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void invalidPostCutOffLengthIsRejected() {
-               preferences.setPostCutOffLength(-15);
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void cutOffLengthOfMinusOneIsNotAllowed() {
-               preferences.setPostCutOffLength(-1);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenPostCutOffLengthIsSetToNull() {
-               preferences.setPostCutOffLength(null);
-               assertThat(preferences.getPostCutOffLength(), is(200));
-       }
-
-       @Test
-       public void preferencesStartWithPostCutOffLengthDefaultValue() {
-               assertThat(preferences.getPostCutOffLength(), is(200));
-       }
-
-       @Test
-       public void preferencesRetainRequireFullAccessOfTrue() {
-               preferences.setRequireFullAccess(true);
-               assertThat(preferences.isRequireFullAccess(), is(true));
-       }
-
-       @Test
-       public void preferencesRetainRequireFullAccessOfFalse() {
-               preferences.setRequireFullAccess(false);
-               assertThat(preferences.isRequireFullAccess(), is(false));
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenRequireFullAccessIsSetToNull() {
-               preferences.setRequireFullAccess(null);
-               assertThat(preferences.isRequireFullAccess(), is(false));
-       }
-
-       @Test
-       public void preferencesStartWithRequireFullAccessDefaultValue() {
-               assertThat(preferences.isRequireFullAccess(), is(false));
-       }
-
-       @Test
-       public void preferencesRetainPositiveTrust() {
-               preferences.setPositiveTrust(15);
-               assertThat(preferences.getPositiveTrust(), is(15));
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void invalidPositiveTrustIsRejected() {
-               preferences.setPositiveTrust(-15);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenPositiveTrustIsSetToNull() {
-               preferences.setPositiveTrust(null);
-               assertThat(preferences.getPositiveTrust(), is(75));
-       }
-
-       @Test
-       public void preferencesStartWithPositiveTrustDefaultValue() {
-               assertThat(preferences.getPositiveTrust(), is(75));
-       }
-
-       @Test
-       public void preferencesRetainNegativeTrust() {
-               preferences.setNegativeTrust(-15);
-               assertThat(preferences.getNegativeTrust(), is(-15));
-       }
-
-       @Test(expected = IllegalArgumentException.class)
-       public void invalidNegativeTrustIsRejected() {
-               preferences.setNegativeTrust(150);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenNegativeTrustIsSetToNull() {
-               preferences.setNegativeTrust(null);
-               assertThat(preferences.getNegativeTrust(), is(-25));
-       }
-
-       @Test
-       public void preferencesStartWithNegativeTrustDefaultValue() {
-               assertThat(preferences.getNegativeTrust(), is(-25));
-       }
-
-       @Test
-       public void preferencesRetainTrustComment() {
-               preferences.setTrustComment("Trust");
-               assertThat(preferences.getTrustComment(), is("Trust"));
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenTrustCommentIsSetToNull() {
-               preferences.setTrustComment(null);
-               assertThat(preferences.getTrustComment(),
-                               is("Set from Sone Web Interface"));
-       }
-
-       @Test
-       public void preferencesStartWithTrustCommentDefaultValue() {
-               assertThat(preferences.getTrustComment(),
-                               is("Set from Sone Web Interface"));
-       }
-
-       @Test
-       public void preferencesRetainFcpInterfaceActiveOfTrue() {
-               preferences.setFcpInterfaceActive(true);
-               assertThat(preferences.isFcpInterfaceActive(), is(true));
-               verify(eventBus).post(any(FcpInterfaceActivatedEvent.class));
-       }
-
-       @Test
-       public void preferencesRetainFcpInterfaceActiveOfFalse() {
-               preferences.setFcpInterfaceActive(false);
-               assertThat(preferences.isFcpInterfaceActive(), is(false));
-               verify(eventBus).post(any(FcpInterfaceDeactivatedEvent.class));
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenFcpInterfaceActiveIsSetToNull() {
-               preferences.setFcpInterfaceActive(null);
-               assertThat(preferences.isFcpInterfaceActive(), is(false));
-               verify(eventBus).post(any(FcpInterfaceDeactivatedEvent.class));
-       }
-
-       @Test
-       public void preferencesStartWithFcpInterfaceActiveDefaultValue() {
-               assertThat(preferences.isFcpInterfaceActive(), is(false));
-       }
-
-       @Test
-       public void preferencesRetainFcpFullAccessRequiredOfNo() {
-               preferences.setFcpFullAccessRequired(NO);
-               assertThat(preferences.getFcpFullAccessRequired(), is(NO));
-               verifyFullAccessRequiredChangedEvent(NO);
-       }
-
-       private void verifyFullAccessRequiredChangedEvent(
-                       FullAccessRequired fullAccessRequired) {
-               ArgumentCaptor<FullAccessRequiredChanged> fullAccessRequiredCaptor =
-                               forClass(FullAccessRequiredChanged.class);
-               verify(eventBus).post(fullAccessRequiredCaptor.capture());
-               assertThat(
-                               fullAccessRequiredCaptor.getValue().getFullAccessRequired(),
-                               is(fullAccessRequired));
-       }
-
-       @Test
-       public void preferencesRetainFcpFullAccessRequiredOfWriting() {
-               preferences.setFcpFullAccessRequired(WRITING);
-               assertThat(preferences.getFcpFullAccessRequired(), is(WRITING));
-               verifyFullAccessRequiredChangedEvent(WRITING);
-       }
-
-       @Test
-       public void preferencesRetainFcpFullAccessRequiredOfAlways() {
-               preferences.setFcpFullAccessRequired(ALWAYS);
-               assertThat(preferences.getFcpFullAccessRequired(), is(ALWAYS));
-               verifyFullAccessRequiredChangedEvent(ALWAYS);
-       }
-
-       @Test
-       public void preferencesReturnDefaultValueWhenFcpFullAccessRequiredIsSetToNull() {
-               preferences.setFcpFullAccessRequired(null);
-               assertThat(preferences.getFcpFullAccessRequired(), is(ALWAYS));
-               verifyFullAccessRequiredChangedEvent(ALWAYS);
-       }
-
-       @Test
-       public void preferencesStartWithFcpFullAccessRequiredDefaultValue() {
-               assertThat(preferences.getFcpFullAccessRequired(), is(ALWAYS));
-       }
-
-       @Test
-       public void settingInsertionDelayToValidValueSendsChangeEvent() {
-               preferences.setInsertionDelay(30);
-               ArgumentCaptor<Object> eventsCaptor = forClass(Object.class);
-               verify(eventBus, atLeastOnce()).post(eventsCaptor.capture());
-               assertThat(eventsCaptor.getAllValues(), hasItem(new PreferenceChangedEvent("InsertionDelay", 30)));
-       }
-
-}
diff --git a/src/test/java/net/pterodactylus/sone/core/SoneChangeDetectorTest.java b/src/test/java/net/pterodactylus/sone/core/SoneChangeDetectorTest.java
deleted file mode 100644 (file)
index eaa9637..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-package net.pterodactylus.sone.core;
-
-import static java.util.Arrays.asList;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.HashSet;
-
-import net.pterodactylus.sone.core.SoneChangeDetector.PostProcessor;
-import net.pterodactylus.sone.core.SoneChangeDetector.PostReplyProcessor;
-import net.pterodactylus.sone.data.Post;
-import net.pterodactylus.sone.data.PostReply;
-import net.pterodactylus.sone.data.Sone;
-
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Unit test for {@link SoneChangeDetector}.
- */
-public class SoneChangeDetectorTest {
-
-       private final Sone oldSone = mock(Sone.class);
-       private final Sone newSone = mock(Sone.class);
-       private final SoneChangeDetector soneChangeDetector =
-                       new SoneChangeDetector(oldSone);
-       private final Post oldPost = mock(Post.class);
-       private final Post removedPost = mock(Post.class);
-       private final Post newPost = mock(Post.class);
-       private final PostProcessor newPostProcessor = mock(PostProcessor.class);
-       private final PostProcessor removedPostProcessor =
-                       mock(PostProcessor.class);
-       private final PostReply oldPostReply = mock(PostReply.class);
-       private final PostReply removedPostReply = mock(PostReply.class);
-       private final PostReply newPostReply = mock(PostReply.class);
-       private final PostReplyProcessor newPostReplyProcessor =
-                       mock(PostReplyProcessor.class);
-       private final PostReplyProcessor removedPostReplyProcessor =
-                       mock(PostReplyProcessor.class);
-
-       @Before
-       public void setupPosts() {
-               when(oldSone.getPosts()).thenReturn(asList(oldPost, removedPost));
-               when(newSone.getPosts()).thenReturn(asList(oldPost, newPost));
-       }
-
-       @Before
-       public void setupPostProcessors() {
-               soneChangeDetector.onNewPosts(newPostProcessor);
-               soneChangeDetector.onRemovedPosts(removedPostProcessor);
-       }
-
-       @Before
-       public void setupPostReplies() {
-               when(oldSone.getReplies()).thenReturn(
-                               new HashSet<PostReply>(
-                                               asList(oldPostReply, removedPostReply)));
-               when(newSone.getReplies()).thenReturn(
-                               new HashSet<PostReply>(asList(oldPostReply, newPostReply)));
-       }
-
-       @Before
-       public void setupPostReplyProcessors() {
-               soneChangeDetector.onNewPostReplies(newPostReplyProcessor);
-               soneChangeDetector.onRemovedPostReplies(removedPostReplyProcessor);
-       }
-
-       @Test
-       public void changeDetectorDetectsChanges() {
-               soneChangeDetector.detectChanges(newSone);
-
-               verify(newPostProcessor).processPost(newPost);
-               verify(newPostProcessor, never()).processPost(oldPost);
-               verify(newPostProcessor, never()).processPost(removedPost);
-               verify(removedPostProcessor).processPost(removedPost);
-               verify(removedPostProcessor, never()).processPost(oldPost);
-               verify(removedPostProcessor, never()).processPost(newPost);
-
-               verify(newPostReplyProcessor).processPostReply(newPostReply);
-               verify(newPostReplyProcessor, never()).processPostReply(oldPostReply);
-               verify(newPostReplyProcessor, never()).processPostReply(
-                               removedPostReply);
-               verify(removedPostReplyProcessor).processPostReply(removedPostReply);
-               verify(removedPostReplyProcessor, never()).processPostReply(
-                               oldPostReply);
-               verify(removedPostReplyProcessor, never()).processPostReply(
-                               newPostReply);
-       }
-
-       @Test
-       public void changeDetectorDoesNotNotifyAnyProcessorIfProcessorsUnset() {
-           soneChangeDetector.onNewPosts(null);
-           soneChangeDetector.onRemovedPosts(null);
-           soneChangeDetector.onNewPostReplies(null);
-           soneChangeDetector.onRemovedPostReplies(null);
-               soneChangeDetector.detectChanges(newSone);
-               verify(newPostProcessor, never()).processPost(any(Post.class));
-               verify(removedPostProcessor, never()).processPost(any(Post.class));
-               verify(newPostReplyProcessor, never()).processPostReply(any(PostReply.class));
-               verify(removedPostReplyProcessor, never()).processPostReply(any(PostReply.class));
-       }
-
-}
diff --git a/src/test/kotlin/net/pterodactylus/sone/core/PreferencesTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/PreferencesTest.kt
new file mode 100644 (file)
index 0000000..f4f4818
--- /dev/null
@@ -0,0 +1,326 @@
+package net.pterodactylus.sone.core
+
+import com.google.common.eventbus.*
+import net.pterodactylus.sone.core.event.*
+import net.pterodactylus.sone.fcp.FcpInterface.*
+import net.pterodactylus.sone.fcp.FcpInterface.FullAccessRequired.*
+import net.pterodactylus.sone.fcp.event.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/**
+ * Unit test for [Preferences].
+ */
+class PreferencesTest {
+
+       private val eventBus = mock<EventBus>()
+       private val preferences = Preferences(eventBus)
+       private val eventsCaptor = capture<Any>()
+
+       @Test
+       fun `preferences retain insertion delay`() {
+               preferences.insertionDelay = 15
+               assertThat(preferences.insertionDelay, equalTo(15))
+       }
+
+       @Test
+       fun `preferences sends event on setting insertion delay`() {
+               preferences.insertionDelay = 15
+               verify(eventBus, atLeastOnce()).post(eventsCaptor.capture())
+               assertThat(eventsCaptor.allValues, hasItem(InsertionDelayChangedEvent(15)))
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `invalid insertion delay is rejected`() {
+               preferences.insertionDelay = -15
+       }
+
+       @Test
+       fun `no event is sent when invalid insertion delay is set`() {
+               try {
+                       preferences.insertionDelay = -15
+               } catch (iae: IllegalArgumentException) {
+                       /* ignore. */
+               }
+
+               verify(eventBus, never()).post(any())
+       }
+
+       @Test
+       fun `preferences return default value when insertion delay is set to null`() {
+               preferences.setInsertionDelay(null)
+               assertThat(preferences.insertionDelay, equalTo(60))
+       }
+
+       @Test
+       fun `preferences start with insertion delay default value`() {
+               assertThat(preferences.insertionDelay, equalTo(60))
+       }
+
+       @Test
+       fun `preferences retain posts per page`() {
+               preferences.postsPerPage = 15
+               assertThat(preferences.postsPerPage, equalTo(15))
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `invalid posts per page is rejected`() {
+               preferences.postsPerPage = -15
+       }
+
+       @Test
+       fun `preferences return default value when posts per page is set to null`() {
+               preferences.setPostsPerPage(null)
+               assertThat(preferences.postsPerPage, equalTo(10))
+       }
+
+       @Test
+       fun `preferences start with posts per page default value`() {
+               assertThat(preferences.postsPerPage, equalTo(10))
+       }
+
+       @Test
+       fun `preferences retain images per page`() {
+               preferences.imagesPerPage = 15
+               assertThat(preferences.imagesPerPage, equalTo(15))
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `invalid images per page is rejected`() {
+               preferences.imagesPerPage = -15
+       }
+
+       @Test
+       fun `preferences return default value when images per page is set to null`() {
+               preferences.setImagesPerPage(null)
+               assertThat(preferences.imagesPerPage, equalTo(9))
+       }
+
+       @Test
+       fun `preferences start with images per page default value`() {
+               assertThat(preferences.imagesPerPage, equalTo(9))
+       }
+
+       @Test
+       fun `preferences retain characters per post`() {
+               preferences.charactersPerPost = 150
+               assertThat(preferences.charactersPerPost, equalTo(150))
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `invalid characters per post is rejected`() {
+               preferences.charactersPerPost = -15
+       }
+
+       @Test
+       fun `preferences return default value when characters per post is set to null`() {
+               preferences.setCharactersPerPost(null)
+               assertThat(preferences.charactersPerPost, equalTo(400))
+       }
+
+       @Test
+       fun `preferences start with characters per post default value`() {
+               assertThat(preferences.charactersPerPost, equalTo(400))
+       }
+
+       @Test
+       fun `preferences retain post cut off length`() {
+               preferences.postCutOffLength = 150
+               assertThat(preferences.postCutOffLength, equalTo(150))
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `invalid post cut off length is rejected`() {
+               preferences.postCutOffLength = -15
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `cut off length of minus one is not allowed`() {
+               preferences.postCutOffLength = -1
+       }
+
+       @Test
+       fun `preferences return default value when post cut off length is set to null`() {
+               preferences.setPostCutOffLength(null)
+               assertThat(preferences.postCutOffLength, equalTo(200))
+       }
+
+       @Test
+       fun `preferences start with post cut off length default value`() {
+               assertThat(preferences.postCutOffLength, equalTo(200))
+       }
+
+       @Test
+       fun `preferences retain require full access of true`() {
+               preferences.isRequireFullAccess = true
+               assertThat(preferences.isRequireFullAccess, equalTo(true))
+       }
+
+       @Test
+       fun `preferences retain require full access of false`() {
+               preferences.isRequireFullAccess = false
+               assertThat(preferences.isRequireFullAccess, equalTo(false))
+       }
+
+       @Test
+       fun `preferences return default value when require full access is set to null`() {
+               preferences.setRequireFullAccess(null)
+               assertThat(preferences.isRequireFullAccess, equalTo(false))
+       }
+
+       @Test
+       fun `preferences start with require full access default value`() {
+               assertThat(preferences.isRequireFullAccess, equalTo(false))
+       }
+
+       @Test
+       fun `preferences retain positive trust`() {
+               preferences.positiveTrust = 15
+               assertThat(preferences.positiveTrust, equalTo(15))
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `invalid positive trust is rejected`() {
+               preferences.positiveTrust = -15
+       }
+
+       @Test
+       fun `preferences return default value when positive trust is set to null`() {
+               preferences.setPositiveTrust(null)
+               assertThat(preferences.positiveTrust, equalTo(75))
+       }
+
+       @Test
+       fun `preferences start with positive trust default value`() {
+               assertThat(preferences.positiveTrust, equalTo(75))
+       }
+
+       @Test
+       fun `preferences retain negative trust`() {
+               preferences.negativeTrust = -15
+               assertThat(preferences.negativeTrust, equalTo(-15))
+       }
+
+       @Test(expected = IllegalArgumentException::class)
+       fun `invalid negative trust is rejected`() {
+               preferences.negativeTrust = 150
+       }
+
+       @Test
+       fun `preferences return default value when negative trust is set to null`() {
+               preferences.setNegativeTrust(null)
+               assertThat(preferences.negativeTrust, equalTo(-25))
+       }
+
+       @Test
+       fun `preferences start with negative trust default value`() {
+               assertThat(preferences.negativeTrust, equalTo(-25))
+       }
+
+       @Test
+       fun `preferences retain trust comment`() {
+               preferences.trustComment = "Trust"
+               assertThat(preferences.trustComment, equalTo("Trust"))
+       }
+
+       @Test
+       fun `preferences return default value when trust comment is set to null`() {
+               preferences.trustComment = null
+               assertThat(preferences.trustComment,
+                               equalTo("Set from Sone Web Interface"))
+       }
+
+       @Test
+       fun `preferences start with trust comment default value`() {
+               assertThat(preferences.trustComment,
+                               equalTo("Set from Sone Web Interface"))
+       }
+
+       @Test
+       fun `preferences retain fcp interface active of true`() {
+               preferences.isFcpInterfaceActive = true
+               assertThat(preferences.isFcpInterfaceActive, equalTo(true))
+               verify(eventBus).post(any(FcpInterfaceActivatedEvent::class.java))
+       }
+
+       @Test
+       fun `preferences retain fcp interface active of false`() {
+               preferences.isFcpInterfaceActive = false
+               assertThat(preferences.isFcpInterfaceActive, equalTo(false))
+               verify(eventBus).post(any(FcpInterfaceDeactivatedEvent::class.java))
+       }
+
+       @Test
+       fun `preferences return default value when fcp interface active is set to null`() {
+               preferences.setFcpInterfaceActive(null)
+               assertThat(preferences.isFcpInterfaceActive, equalTo(false))
+               verify(eventBus).post(any(FcpInterfaceDeactivatedEvent::class.java))
+       }
+
+       @Test
+       fun `preferences start with fcp interface active default value`() {
+               assertThat(preferences.isFcpInterfaceActive, equalTo(false))
+       }
+
+       @Test
+       fun `preferences retain fcp full access required of no`() {
+               preferences.fcpFullAccessRequired = NO
+               assertThat(preferences.fcpFullAccessRequired, equalTo(NO))
+               verifyFullAccessRequiredChangedEvent(NO)
+       }
+
+       private fun verifyFullAccessRequiredChangedEvent(
+                       fullAccessRequired: FullAccessRequired) {
+               verify(eventBus).post(eventsCaptor.capture())
+               assertThat(eventsCaptor.value, instanceOf(FullAccessRequiredChanged::class.java))
+               assertThat((eventsCaptor.value as FullAccessRequiredChanged).fullAccessRequired,
+                               equalTo(fullAccessRequired))
+       }
+
+       @Test
+       fun `preferences retain fcp full access required of writing`() {
+               preferences.fcpFullAccessRequired = WRITING
+               assertThat(preferences.fcpFullAccessRequired, equalTo(WRITING))
+               verifyFullAccessRequiredChangedEvent(WRITING)
+       }
+
+       @Test
+       fun `preferences retain fcp full access required of always`() {
+               preferences.fcpFullAccessRequired = ALWAYS
+               assertThat(preferences.fcpFullAccessRequired, equalTo(ALWAYS))
+               verifyFullAccessRequiredChangedEvent(ALWAYS)
+       }
+
+       @Test
+       fun `preferences return default value when fcp full access required is set to null`() {
+               preferences.fcpFullAccessRequired = null
+               assertThat(preferences.fcpFullAccessRequired, equalTo(ALWAYS))
+               verifyFullAccessRequiredChangedEvent(ALWAYS)
+       }
+
+       @Test
+       fun `preferences start with fcp full access required default value`() {
+               assertThat(preferences.fcpFullAccessRequired, equalTo(ALWAYS))
+       }
+
+       @Test
+       fun `setting insertion delay to valid value sends change event`() {
+               preferences.insertionDelay = 30
+               verify(eventBus, atLeastOnce()).post(eventsCaptor.capture())
+               assertThat(eventsCaptor.allValues, hasItem(PreferenceChangedEvent("InsertionDelay", 30)))
+       }
+
+       @Test
+       fun `setting posts per page to valid value sends change event`() {
+               preferences.postsPerPage = 30
+               verify(eventBus, atLeastOnce()).post(eventsCaptor.capture())
+               assertThat(eventsCaptor.allValues, hasItem(PreferenceChangedEvent("PostsPerPage", 30)))
+       }
+
+}
diff --git a/src/test/kotlin/net/pterodactylus/sone/core/SoneChangeCollectorTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/SoneChangeCollectorTest.kt
deleted file mode 100644 (file)
index 3b82b49..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-package net.pterodactylus.sone.core
-
-import net.pterodactylus.sone.data.Post
-import net.pterodactylus.sone.data.PostReply
-import net.pterodactylus.sone.data.Sone
-import net.pterodactylus.sone.test.mock
-import net.pterodactylus.sone.test.whenever
-import org.hamcrest.MatcherAssert.assertThat
-import org.hamcrest.Matchers.containsInAnyOrder
-import org.hamcrest.Matchers.emptyIterable
-import org.hamcrest.Matchers.equalTo
-import org.junit.Test
-import java.util.concurrent.atomic.AtomicInteger
-
-/**
- * Unit test for [SoneChangeCollectorTest].
- */
-class SoneChangeCollectorTest {
-
-       private val oldSone = mock<Sone>()
-       private val newSone = mock<Sone>()
-       private val changeCollector = SoneChangeCollector(oldSone)
-
-       @Test
-       fun `new posts are correctly turned into events`() {
-               val posts = listOf(mock<Post>(), mock(), mock())
-               whenever(newSone.posts).thenReturn(posts)
-               changeCollector.newPostEvent { it.takeIf { it != posts[1] } }
-               assertThat(changeCollector.detectChanges(newSone), containsInAnyOrder<Any>(posts[0], posts[2]))
-       }
-
-       @Test
-       fun `actions can be performed on new post without being returned`() {
-               val posts = listOf(mock<Post>(), mock(), mock())
-               val counter = AtomicInteger(0)
-               whenever(newSone.posts).thenReturn(posts.slice(0..2))
-               whenever(oldSone.posts).thenReturn(posts.slice(2..2))
-               changeCollector.onNewPost { counter.incrementAndGet() }
-               assertThat(changeCollector.detectChanges(newSone), emptyIterable())
-               assertThat(counter.get(), equalTo(2))
-       }
-
-       @Test
-       fun `removed posts are correctly turned into events`() {
-               val posts = listOf(mock<Post>(), mock(), mock())
-               whenever(oldSone.posts).thenReturn(posts)
-               changeCollector.removedPostEvent { it.takeIf { it != posts[1] } }
-               assertThat(changeCollector.detectChanges(newSone), containsInAnyOrder<Any>(posts[0], posts[2]))
-       }
-
-       @Test
-       fun `new post replies are correctly turned into events`() {
-               val postReplies = listOf(mock<PostReply>(), mock(), mock())
-               whenever(newSone.replies).thenReturn(postReplies.toSet())
-               changeCollector.newPostReplyEvent { it.takeIf { it != postReplies[1] } }
-               assertThat(changeCollector.detectChanges(newSone), containsInAnyOrder<Any>(postReplies[0], postReplies[2]))
-       }
-
-       @Test
-       fun `actions can be performed on new replies without being returned`() {
-               val replies = listOf(mock<PostReply>(), mock(), mock())
-               val counter = AtomicInteger(0)
-               whenever(newSone.replies).thenReturn(replies.slice(0..2).toSet())
-               whenever(oldSone.replies).thenReturn(replies.slice(2..2).toSet())
-               changeCollector.onNewPostReply { counter.incrementAndGet() }
-               assertThat(changeCollector.detectChanges(newSone), emptyIterable())
-               assertThat(counter.get(), equalTo(2))
-       }
-
-       @Test
-       fun `removed post replies are correctly turned into events`() {
-               val postReplies = listOf(mock<PostReply>(), mock(), mock())
-               whenever(oldSone.replies).thenReturn(postReplies.toSet())
-               changeCollector.onRemovedPostReply { it.takeIf { it != postReplies[1] } }
-               assertThat(changeCollector.detectChanges(newSone), containsInAnyOrder<Any>(postReplies[0], postReplies[2]))
-       }
-
-}
diff --git a/src/test/kotlin/net/pterodactylus/sone/core/SoneComparisonTest.kt b/src/test/kotlin/net/pterodactylus/sone/core/SoneComparisonTest.kt
new file mode 100644 (file)
index 0000000..f79d736
--- /dev/null
@@ -0,0 +1,50 @@
+package net.pterodactylus.sone.core
+
+import net.pterodactylus.sone.data.*
+import net.pterodactylus.sone.test.*
+import org.hamcrest.MatcherAssert.*
+import org.hamcrest.Matchers.*
+import org.junit.*
+
+class SoneComparsisonTest {
+
+       private val oldSone = mock<Sone>()
+       private val newSone = mock<Sone>()
+
+       private val oldPost = mock<Post>()
+       private val removedPost = mock<Post>()
+       private val newPost = mock<Post>()
+       private val oldPostReply = mock<PostReply>()
+       private val removedPostReply = mock<PostReply>()
+       private val newPostReply = mock<PostReply>()
+
+       init {
+               whenever(oldSone.posts).thenReturn(listOf(oldPost, removedPost))
+               whenever(newSone.posts).thenReturn(listOf(oldPost, newPost))
+               whenever(oldSone.replies).thenReturn(setOf(oldPostReply, removedPostReply))
+               whenever(newSone.replies).thenReturn(setOf(oldPostReply, newPostReply))
+       }
+
+       private val soneComparison = SoneComparison(oldSone, newSone)
+
+       @Test
+       fun `new posts are identified correctly`() {
+               assertThat(soneComparison.newPosts, contains(newPost))
+       }
+
+       @Test
+       fun `removed posts are identified correctly`() {
+               assertThat(soneComparison.removedPosts, contains(removedPost))
+       }
+
+       @Test
+       fun `new post replies are identified correctly`() {
+               assertThat(soneComparison.newPostReplies, contains(newPostReply))
+       }
+
+       @Test
+       fun `removed post replies are identified correctly`() {
+               assertThat(soneComparison.removedPostReplies, contains(removedPostReply))
+       }
+
+}