// Copyright 2015 The Project Buendia Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy // of the License at: http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software distrib- // uted under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES // OR CONDITIONS OF ANY KIND, either express or implied. See the License for // specific language governing permissions and limitations under the License. package org.projectbuendia.client.ui.lists; import android.test.AndroidTestCase; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.projectbuendia.client.FakeAppLocationTreeFactory; import org.projectbuendia.client.FakeTypedCursor; import org.projectbuendia.client.events.CrudEventBus; import org.projectbuendia.client.events.data.AppLocationTreeFetchedEvent; import org.projectbuendia.client.events.data.TypedCursorFetchedEvent; import org.projectbuendia.client.events.data.TypedCursorFetchedEventFactory; import org.projectbuendia.client.events.sync.SyncSucceededEvent; import org.projectbuendia.client.filter.db.SimpleSelectionFilter; import org.projectbuendia.client.filter.db.patient.PatientDbFilters; import org.projectbuendia.client.models.AppModel; import org.projectbuendia.client.models.LocationTree; import org.projectbuendia.client.models.Patient; import org.projectbuendia.client.models.TypedCursor; import org.projectbuendia.client.models.Zones; import org.projectbuendia.client.sync.SyncManager; import org.projectbuendia.client.ui.FakeEventBus; import org.projectbuendia.client.ui.matchers.SimpleSelectionFilterMatchers; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; /** Tests for {@link PatientSearchController}. */ public class PatientSearchControllerTest extends AndroidTestCase { private static final String LOCALE = "en"; private PatientSearchController mController; private FakeEventBus mFakeCrudEventBus; private FakeEventBus mFakeGlobalEventBus; @Mock private SyncManager mSyncManager; @Mock private AppModel mMockAppModel; @Mock private PatientSearchController.Ui mMockUi; @Mock private PatientSearchController.FragmentUi mFragmentMockUi; /** Tests that results are reloaded when a sync event occurs. */ public void testSyncSubscriber_reloadsResults() { // GIVEN initialized PatientSearchController // WHEN a sync event completes mFakeGlobalEventBus.post(new SyncSucceededEvent()); // THEN results should be reloaded verify(mMockAppModel).fetchPatients( any(CrudEventBus.class), any(SimpleSelectionFilter.class), anyString()); } /** Tests that results are reloaded when a sync event occurs. */ public void testSyncSubscriber_doesNotShowSpinnerDuringReload() { // GIVEN initialized PatientSearchController // WHEN a sync event completes mFakeGlobalEventBus.post(new SyncSucceededEvent()); // THEN the spinner is not shown verify(mFragmentMockUi, times(0)).showSpinner(true); } /** Tests that patients are passed to fragment UI's after retrieval. */ public void testFilterSubscriber_passesPatientsToFragments() { // GIVEN initialized PatientSearchController mController.loadSearchResults(); // WHEN patients are retrieved TypedCursorFetchedEvent event = TypedCursorFetchedEventFactory.createEvent( Patient.class, getFakeAppPatientCursor()); mFakeCrudEventBus.post(event); // THEN patients are passed to fragment UI's verify(mFragmentMockUi).setPatients(any(TypedCursor.class)); } private TypedCursor<Patient> getFakeAppPatientCursor() { Patient patient = Patient.builder().build(); return new FakeTypedCursor<Patient>(new Patient[] {patient}); } /** Tests that patients are passed to the activity UI after retrieval. */ public void testFilterSubscriber_passesPatientsToActivity() { // GIVEN initialized PatientSearchController mController.loadSearchResults(); // WHEN patients are retrieved TypedCursorFetchedEvent event = TypedCursorFetchedEventFactory.createEvent( Patient.class, getFakeAppPatientCursor()); mFakeCrudEventBus.post(event); // THEN patients are passed to activity UI verify(mMockUi).setPatients(any(TypedCursor.class)); } /** Tests that any old patient cursor is closed after results are reloaded. */ public void testFilterSubscriber_closesExistingPatientCursor() { // GIVEN initialized PatientSearchController with existing results mController.loadSearchResults(); TypedCursorFetchedEvent event = TypedCursorFetchedEventFactory.createEvent( Patient.class, getFakeAppPatientCursor()); mFakeCrudEventBus.post(event); // WHEN new results are retrieved mController.loadSearchResults(); TypedCursorFetchedEvent reloadEvent = TypedCursorFetchedEventFactory.createEvent( Patient.class, getFakeAppPatientCursor()); mFakeCrudEventBus.post(reloadEvent); // THEN old patients cursor is closed assertTrue(((FakeTypedCursor<Patient>) event.cursor).isClosed()); } /** Tests that retrieving a new cursor results in the closure of any existing cursor. */ public void testSuspend_closesExistingPatientCursor() { // GIVEN initialized PatientSearchController with existing results mController.loadSearchResults(); TypedCursorFetchedEvent event = TypedCursorFetchedEventFactory.createEvent( Patient.class, getFakeAppPatientCursor()); mFakeCrudEventBus.post(event); // WHEN controller is suspended mController.suspend(); // THEN patient cursor is closed assertTrue(((FakeTypedCursor<Patient>) event.cursor).isClosed()); } /** Tests that suspend() does not attempt to close a null cursor. */ public void testSuspend_ignoresNullPatientCursor() { // GIVEN initialized PatientSearchController with no search results // WHEN controller is suspended mController.suspend(); // THEN nothing happens (no runtime exception thrown) } /** Tests that search results are loaded properly after a cycle of init() and suspend(). */ public void testLoadSearchResults_functionalAfterInitSuspendCycle() { // GIVEN initialized PatientSearchController with existing results mController.loadSearchResults(); TypedCursorFetchedEvent event = TypedCursorFetchedEventFactory.createEvent( Patient.class, getFakeAppPatientCursor()); mFakeCrudEventBus.post(event); // WHEN a suspend()/init() cycle occurs mController.suspend(); mController.init(); // THEN search results can be loaded successfully mController.loadSearchResults(); TypedCursorFetchedEvent reloadEvent = TypedCursorFetchedEventFactory.createEvent( Patient.class, getFakeAppPatientCursor()); mFakeCrudEventBus.post(reloadEvent); verify(mFragmentMockUi, times(2)).setPatients(any(TypedCursor.class)); } /** * Tests that loadSearchResults() is a no-op for controllers with a specified root location * and no location tree--loadSearchResults() is automatically called when the location tree * is retrieved. */ public void testLoadSearchResults_waitsOnLocations() { // GIVEN PatientSearchController with a location filter and no locations available mController.setLocationFilter(Zones.TRIAGE_ZONE_UUID); // WHEN search results are requested mController.loadSearchResults(); // THEN nothing is returned verify(mMockAppModel, times(0)).fetchPatients( any(CrudEventBus.class), any(SimpleSelectionFilter.class), anyString()); } /** * Tests that loadSearchResults() is called, and patients correctly filtered, when the location * tree is retrieved. */ public void testLoadSearchResults_fetchesFilteredPatientsOnceLocationsPresent() { // GIVEN PatientSearchController with locations available and specified Triage root mController.setLocationFilter(Zones.TRIAGE_ZONE_UUID); LocationTree tree = FakeAppLocationTreeFactory.build(); AppLocationTreeFetchedEvent event = new AppLocationTreeFetchedEvent(tree); mFakeCrudEventBus.post(event); // WHEN search results are requested mController.loadSearchResults(); // THEN patients are fetched from Triage verify(mMockAppModel).fetchPatients( any(CrudEventBus.class), argThat(new SimpleSelectionFilterMatchers.IsFilterGroupWithLocationFilter( Zones.TRIAGE_ZONE_UUID)), anyString()); } /** Tests that the spinner is shown when loadSearchResults() is called. */ public void testLoadSearchResults_showsSpinner() { // GIVEN initialized PatientSearchController // WHEN search results are requested mController.loadSearchResults(); // THEN spinner is shown verify(mFragmentMockUi).showSpinner(true); } /** Tests that the spinner is shown when loadSearchResults() is called. */ public void testLoadSearchResults_hidesSpinnerWhenRequested() { // GIVEN initialized PatientSearchController // WHEN search results are requested with no spinner mController.loadSearchResults(false); // THEN spinner is shown verify(mFragmentMockUi, times(0)).showSpinner(true); } /** Tests that the spinner is hidden after results are retrieved. */ public void testFilterSubscriber_hidesSpinner() { // GIVEN initialized PatientSearchController mController.loadSearchResults(); // WHEN patients are retrieved TypedCursorFetchedEvent event = TypedCursorFetchedEventFactory.createEvent( Patient.class, getFakeAppPatientCursor()); mFakeCrudEventBus.post(event); // THEN patients are passed to fragment UI's verify(mFragmentMockUi).showSpinner(false); } /** Tests that the controller correctly filters when the search term changes. */ public void testOnQuerySubmitted_filtersBySearchTerm() { // GIVEN initialized PatientSearchController with no root location // WHEN search term changes mController.onQuerySubmitted("foo"); // THEN results are requested with that search term verify(mMockAppModel).fetchPatients( mFakeCrudEventBus, PatientDbFilters.getDefaultFilter(), "foo"); } @Override protected void setUp() throws Exception { super.setUp(); MockitoAnnotations.initMocks(this); mFakeCrudEventBus = new FakeEventBus(); mFakeGlobalEventBus = new FakeEventBus(); mController = new PatientSearchController( mMockUi, mFakeCrudEventBus, mFakeGlobalEventBus, mMockAppModel, mSyncManager, LOCALE); mController.attachFragmentUi(mFragmentMockUi); mController.init(); } }