package com.kickstarter.viewmodels; import android.support.annotation.NonNull; import com.kickstarter.KSRobolectricTestCase; import com.kickstarter.factories.DiscoverEnvelopeFactory; import com.kickstarter.factories.ProjectFactory; import com.kickstarter.libs.Environment; import com.kickstarter.libs.KoalaEvent; import com.kickstarter.libs.RefTag; import com.kickstarter.models.Project; import com.kickstarter.services.DiscoveryParams; import com.kickstarter.services.MockApiClient; import com.kickstarter.services.apiresponses.DiscoverEnvelope; import org.junit.Test; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; import rx.Observable; import rx.observers.TestSubscriber; import rx.schedulers.TestScheduler; public class SearchViewModelTest extends KSRobolectricTestCase { private SearchViewModel.ViewModel vm; private final TestSubscriber<Project> goToProject = new TestSubscriber<>(); private final TestSubscriber<RefTag> goToRefTag = new TestSubscriber<>(); private final TestSubscriber<List<Project>> popularProjects = new TestSubscriber<>(); private final TestSubscriber<Boolean> popularProjectsPresent = new TestSubscriber<>(); private final TestSubscriber<List<Project>> searchProjects = new TestSubscriber<>(); private final TestSubscriber<Boolean> searchProjectsPresent = new TestSubscriber<>(); private void setUpEnvironment(final @NonNull Environment environment) { this.vm = new SearchViewModel.ViewModel(environment); this.vm.outputs.startProjectActivity().map(p -> p.first).subscribe(this.goToProject); this.vm.outputs.startProjectActivity().map(p -> p.second).subscribe(this.goToRefTag); this.vm.outputs.popularProjects().subscribe(this.popularProjects); this.vm.outputs.searchProjects().subscribe(this.searchProjects); this.vm.outputs.popularProjects().map(ps -> ps.size() > 0).subscribe(this.popularProjectsPresent); this.vm.outputs.searchProjects().map(ps -> ps.size() > 0).subscribe(this.searchProjectsPresent); } @Test public void testPopularProjectsLoadImmediately() { setUpEnvironment(environment()); this.popularProjectsPresent.assertValues(true); this.searchProjectsPresent.assertNoValues(); } @Test public void testSearchProjectsWhenEnterSearchTerm() { final TestScheduler scheduler = new TestScheduler(); final Environment env = environment().toBuilder() .scheduler(scheduler) .build(); setUpEnvironment(env); // Popular projects emit immediately. this.popularProjectsPresent.assertValues(true); this.searchProjectsPresent.assertNoValues(); this.koalaTest.assertValues(KoalaEvent.VIEWED_SEARCH, KoalaEvent.DISCOVER_SEARCH_LEGACY); // Searching shouldn't emit values immediately this.vm.inputs.search("hello"); this.searchProjectsPresent.assertNoValues(); this.koalaTest.assertValues(KoalaEvent.VIEWED_SEARCH, KoalaEvent.DISCOVER_SEARCH_LEGACY); // Waiting a small amount time shouldn't emit values scheduler.advanceTimeBy(200, TimeUnit.MILLISECONDS); this.searchProjectsPresent.assertNoValues(); this.koalaTest.assertValues(KoalaEvent.VIEWED_SEARCH, KoalaEvent.DISCOVER_SEARCH_LEGACY); // Waiting the rest of the time makes the search happen scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); this.searchProjectsPresent.assertValues(false, true); this.koalaTest.assertValues( KoalaEvent.VIEWED_SEARCH, KoalaEvent.DISCOVER_SEARCH_LEGACY, KoalaEvent.LOADED_SEARCH_RESULTS, KoalaEvent.DISCOVER_SEARCH_RESULTS_LEGACY ); // Typing more search terms doesn't emit more values this.vm.inputs.search("hello world!"); this.searchProjectsPresent.assertValues(false, true); this.koalaTest.assertValues( KoalaEvent.VIEWED_SEARCH, KoalaEvent.DISCOVER_SEARCH_LEGACY, KoalaEvent.LOADED_SEARCH_RESULTS, KoalaEvent.DISCOVER_SEARCH_RESULTS_LEGACY ); // Waiting enough time emits search results scheduler.advanceTimeBy(300, TimeUnit.MILLISECONDS); this.searchProjectsPresent.assertValues(false, true, false, true); this.koalaTest.assertValues( KoalaEvent.VIEWED_SEARCH, KoalaEvent.DISCOVER_SEARCH_LEGACY, KoalaEvent.LOADED_SEARCH_RESULTS, KoalaEvent.DISCOVER_SEARCH_RESULTS_LEGACY, KoalaEvent.LOADED_SEARCH_RESULTS, KoalaEvent.DISCOVER_SEARCH_RESULTS_LEGACY ); // Clearing search terms brings back popular projects. this.vm.inputs.search(""); this.searchProjectsPresent.assertValues(false, true, false, true, false); this.popularProjectsPresent.assertValues(true, false, true); this.koalaTest.assertValues( KoalaEvent.VIEWED_SEARCH, KoalaEvent.DISCOVER_SEARCH_LEGACY, KoalaEvent.LOADED_SEARCH_RESULTS, KoalaEvent.DISCOVER_SEARCH_RESULTS_LEGACY, KoalaEvent.LOADED_SEARCH_RESULTS, KoalaEvent.DISCOVER_SEARCH_RESULTS_LEGACY, KoalaEvent.CLEARED_SEARCH_TERM); } @Test public void testSearchPagination() { final TestScheduler scheduler = new TestScheduler(); final Environment env = environment().toBuilder() .scheduler(scheduler) .build(); setUpEnvironment(env); this.searchProjectsPresent.assertNoValues(); this.koalaTest.assertValues(KoalaEvent.VIEWED_SEARCH, KoalaEvent.DISCOVER_SEARCH_LEGACY); this.vm.inputs.search("cats"); scheduler.advanceTimeBy(300, TimeUnit.MILLISECONDS); this.searchProjectsPresent.assertValues(false, true); this.koalaTest.assertValues( KoalaEvent.VIEWED_SEARCH, KoalaEvent.DISCOVER_SEARCH_LEGACY, KoalaEvent.LOADED_SEARCH_RESULTS, KoalaEvent.DISCOVER_SEARCH_RESULTS_LEGACY ); this.vm.inputs.nextPage(); this.searchProjectsPresent.assertValues(false, true); this.koalaTest.assertValues( KoalaEvent.VIEWED_SEARCH, KoalaEvent.DISCOVER_SEARCH_LEGACY, KoalaEvent.LOADED_SEARCH_RESULTS, KoalaEvent.DISCOVER_SEARCH_RESULTS_LEGACY, KoalaEvent.LOADED_MORE_SEARCH_RESULTS, KoalaEvent.DISCOVER_SEARCH_RESULTS_LOAD_MORE_LEGACY ); } @Test public void testFeaturedSearchRefTags() { final TestScheduler scheduler = new TestScheduler(); final List<Project> projects = Arrays.asList( ProjectFactory.allTheWayProject(), ProjectFactory.almostCompletedProject(), ProjectFactory.backedProject() ); final MockApiClient apiClient = new MockApiClient() { @Override public @NonNull Observable<DiscoverEnvelope> fetchProjects(final @NonNull DiscoveryParams params) { return Observable.just(DiscoverEnvelopeFactory.discoverEnvelope(projects)); } }; final Environment env = environment().toBuilder() .scheduler(scheduler) .apiClient(apiClient) .build(); setUpEnvironment(env); this.vm.inputs.search("cat"); scheduler.advanceTimeBy(300, TimeUnit.MILLISECONDS); this.vm.inputs.projectClicked(projects.get(0)); this.goToRefTag.assertValues(RefTag.searchFeatured()); this.goToProject.assertValues(projects.get(0)); } @Test public void testSearchRefTags() { final TestScheduler scheduler = new TestScheduler(); final List<Project> projects = Arrays.asList( ProjectFactory.allTheWayProject(), ProjectFactory.almostCompletedProject(), ProjectFactory.backedProject() ); final MockApiClient apiClient = new MockApiClient() { @Override public @NonNull Observable<DiscoverEnvelope> fetchProjects(final @NonNull DiscoveryParams params) { return Observable.just(DiscoverEnvelopeFactory.discoverEnvelope(projects)); } }; final Environment env = environment().toBuilder() .scheduler(scheduler) .apiClient(apiClient) .build(); setUpEnvironment(env); // populate search and overcome debounce this.vm.inputs.search("cat"); scheduler.advanceTimeBy(300, TimeUnit.MILLISECONDS); this.vm.inputs.projectClicked(projects.get(1)); this.goToRefTag.assertValues(RefTag.search()); this.goToProject.assertValues(projects.get(1)); } @Test public void testFeaturedPopularRefTags() { final TestScheduler scheduler = new TestScheduler(); final List<Project> projects = Arrays.asList( ProjectFactory.allTheWayProject(), ProjectFactory.almostCompletedProject(), ProjectFactory.backedProject() ); final MockApiClient apiClient = new MockApiClient() { @Override public @NonNull Observable<DiscoverEnvelope> fetchProjects(final @NonNull DiscoveryParams params) { return Observable.just(DiscoverEnvelopeFactory.discoverEnvelope(projects)); } }; final Environment env = environment().toBuilder() .scheduler(scheduler) .apiClient(apiClient) .build(); setUpEnvironment(env); // populate search and overcome debounce this.vm.inputs.search(""); scheduler.advanceTimeBy(300, TimeUnit.MILLISECONDS); this.vm.inputs.projectClicked(projects.get(0)); this.goToRefTag.assertValues(RefTag.searchPopularFeatured()); this.goToProject.assertValues(projects.get(0)); } @Test public void testPopularRefTags() { final TestScheduler scheduler = new TestScheduler(); final List<Project> projects = Arrays.asList( ProjectFactory.allTheWayProject(), ProjectFactory.almostCompletedProject(), ProjectFactory.backedProject() ); final MockApiClient apiClient = new MockApiClient() { @Override public @NonNull Observable<DiscoverEnvelope> fetchProjects(final @NonNull DiscoveryParams params) { return Observable.just(DiscoverEnvelopeFactory.discoverEnvelope(projects)); } }; final Environment env = environment().toBuilder() .scheduler(scheduler) .apiClient(apiClient) .build(); setUpEnvironment(env); // populate search and overcome debounce this.vm.inputs.search(""); scheduler.advanceTimeBy(300, TimeUnit.MILLISECONDS); this.vm.inputs.projectClicked(projects.get(2)); this.goToRefTag.assertValues(RefTag.searchPopular()); this.goToProject.assertValues(projects.get(2)); } @Test public void testNoResults() { final TestScheduler scheduler = new TestScheduler(); final List<Project> projects = Arrays.asList( ); final MockApiClient apiClient = new MockApiClient() { @Override public @NonNull Observable<DiscoverEnvelope> fetchProjects(final @NonNull DiscoveryParams params) { return Observable.just(DiscoverEnvelopeFactory.discoverEnvelope(projects)); } }; final Environment env = environment().toBuilder() .scheduler(scheduler) .apiClient(apiClient) .build(); setUpEnvironment(env); // populate search and overcome debounce this.vm.inputs.search("__"); scheduler.advanceTimeBy(300, TimeUnit.MILLISECONDS); this.searchProjects.assertValueCount(2); } }