// 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.FakeSyncManager; import org.projectbuendia.client.events.actions.SyncCancelRequestedEvent; import org.projectbuendia.client.events.data.AppLocationTreeFetchedEvent; import org.projectbuendia.client.events.sync.SyncCanceledEvent; import org.projectbuendia.client.events.sync.SyncFailedEvent; import org.projectbuendia.client.events.sync.SyncProgressEvent; import org.projectbuendia.client.events.sync.SyncStartedEvent; import org.projectbuendia.client.events.sync.SyncSucceededEvent; import org.projectbuendia.client.models.AppModel; import org.projectbuendia.client.models.LocationTree; import org.projectbuendia.client.ui.FakeEventBus; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** Tests for {@link LocationListController}. */ public final class LocationListControllerTest extends AndroidTestCase { private LocationListController mController; private FakeEventBus mFakeEventBus; private FakeSyncManager mFakeSyncManager; @Mock private AppModel mMockAppModel; @Mock private LocationListController.Ui mMockUi; @Mock private LocationListController.LocationFragmentUi mMockFragmentUi; @Mock private PatientSearchController mMockSearchController; /** Tests that locations are loaded during initialization, when available. */ public void testInit_RequestsLoadLocationsWhenDataModelAvailable() { // GIVEN initialized data model and the controller hasn't previously fetched the location // tree when(mMockAppModel.isFullModelAvailable()).thenReturn(true); // WHEN the controller is initialized mController.init(); // THEN the controller asks the location manager to provide the location tree verify(mMockAppModel).fetchLocationTree(mFakeEventBus, "en"); } /** Tests that init does not result in a new sync if data model is available. */ public void testInit_DoesNotStartSyncWhenDataModelAvailable() { // GIVEN initialized data model and the controller hasn't previously fetched the location // tree when(mMockAppModel.isFullModelAvailable()).thenReturn(true); // WHEN the controller is initialized mController.init(); // THEN the controller does not start a new sync assertFalse(mFakeSyncManager.isSyncActive()); } /** Tests that init kicks off a sync if the data model is unavailable. */ public void testInit_StartsSyncWhenDataModelUnavailable() { // GIVEN uninitialized data model,the controller hasn't previously fetched the location // tree, and no sync is already in progress mFakeSyncManager.setSyncing(false); when(mMockAppModel.isFullModelAvailable()).thenReturn(false); // WHEN the controller is initialized mController.init(); // THEN the controller requests a sync assertTrue(mFakeSyncManager.isSyncActive()); } /** Tests that suspend() unregisters any subscribers from the event bus. */ public void testSuspend_UnregistersFromEventBus() { // GIVEN an initialized controller mController.init(); // WHEN the controller is suspended mController.suspend(); // THEN the controller unregisters from the event bus assertEquals(0, mFakeEventBus.countRegisteredReceivers()); } /** Tests that the spinner is hidden after locations are loaded. */ public void testLoadLocations_HidesSpinner() { // GIVEN an initialized controller with a fragment attached mController.init(); mController.attachFragmentUi(mMockFragmentUi); // WHEN the location tree is loaded and sync is not in progress mFakeSyncManager.setSyncing(false); LocationTree locationTree = FakeAppLocationTreeFactory.build(); mFakeEventBus.post(new AppLocationTreeFetchedEvent(locationTree)); // THEN the controller hides the progress spinner verify(mMockFragmentUi).setBusyLoading(false); } /** Tests that the spinner is not hidden if locations are loaded but a sync is in progress. */ public void testLoadLocations_DoesNotHideSpinnerWhenSyncInProgress() { // GIVEN an initialized controller with a fragment attached mController.init(); mController.attachFragmentUi(mMockFragmentUi); // WHEN the location tree is loaded but sync is still in progress mFakeSyncManager.setSyncing(true); LocationTree locationTree = FakeAppLocationTreeFactory.build(); mFakeEventBus.post(new AppLocationTreeFetchedEvent(locationTree)); // THEN the controller does not hide the progress spinner verify(mMockFragmentUi).setBusyLoading(true); } /** Tests that the spinner is hidden if locations are loaded and a sync is completed. */ public void testSpinnerHiddenAfterSyncCompletes() { // GIVEN an initialized controller with a fragment attached mController.init(); mController.attachFragmentUi(mMockFragmentUi); // WHEN the location tree is loaded AND sync has completed mFakeSyncManager.setSyncing(true); LocationTree locationTree = FakeAppLocationTreeFactory.build(); mFakeEventBus.post(new AppLocationTreeFetchedEvent(locationTree)); mFakeEventBus.post(new SyncSucceededEvent()); // THEN the controller hides the progress spinner verify(mMockFragmentUi).setBusyLoading(false); } /** Tests that a sync failure causes the error dialog to appear when no locations are present. */ public void testSyncFailureShowsErrorDialog_noLocations() { // GIVEN an initialized controller with a fragment attached mController.init(); mController.attachFragmentUi(mMockFragmentUi); // WHEN the location tree is loaded BUT sync has failed mFakeSyncManager.setSyncing(true); mFakeEventBus.post(new SyncFailedEvent()); // THEN the controller shows the sync failure dialog verify(mMockUi).showSyncFailedDialog(true); } /** * Tests that if, for some reason, a sync succeeds while the sync dialog is showing, the * sync dialog disappears and the locations are usable. */ public void testSyncSuccessHidesSyncDialog() { // GIVEN an initialized controller with a fragment attached and a failed sync mController.init(); mController.attachFragmentUi(mMockFragmentUi); mFakeEventBus.post(new SyncFailedEvent()); // WHEN the location tree is loaded and a sync succeeds LocationTree locationTree = FakeAppLocationTreeFactory.build(); mFakeEventBus.post(new AppLocationTreeFetchedEvent(locationTree)); mFakeEventBus.post(new SyncSucceededEvent()); // THEN the controller hides the sync failed dialog verify(mMockUi).showSyncFailedDialog(false); } /** Tests that loading an empty location tree results in a new sync if sync has completed. */ public void testFetchingIncompleteLocationTree_causesNewSync() { // GIVEN an initialized controller with a fragment attached mController.init(); mController.attachFragmentUi(mMockFragmentUi); // WHEN an empty location tree is loaded after sync completed mFakeEventBus.post(new SyncSucceededEvent()); LocationTree locationTree = FakeAppLocationTreeFactory.emptyTree(); mFakeEventBus.post(new AppLocationTreeFetchedEvent(locationTree)); // THEN the controller starts a new sync assertTrue(mFakeSyncManager.isSyncActive()); } /** Tests that loading an empty location tree does not hide the sync failed dialog. */ public void testFetchingIncompleteLocationTree_retainsSyncFailedDialog() { // GIVEN an initialized controller with a fragment attached mController.init(); mController.attachFragmentUi(mMockFragmentUi); // WHEN an empty location tree is loaded LocationTree locationTree = FakeAppLocationTreeFactory.emptyTree(); mFakeEventBus.post(new AppLocationTreeFetchedEvent(locationTree)); // THEN the loading dialog is not hidden verify(mMockUi, times(0)).showSyncFailedDialog(false); } /** Tests that loading an empty location tree does not hide the loading dialog. */ public void testFetchingIncompleteLocationTree_retainsLoadingDialog() { // GIVEN an initialized controller with a fragment attached mController.init(); mController.attachFragmentUi(mMockFragmentUi); // WHEN an empty location tree is loaded LocationTree locationTree = FakeAppLocationTreeFactory.emptyTree(); mFakeEventBus.post(new AppLocationTreeFetchedEvent(locationTree)); // THEN the loading dialog is not hidden verify(mMockFragmentUi).setBusyLoading(true); } /** Tests that loading a populated location tree does not result in a new sync. */ public void testFetchingPopulatedLocationTree_doesNotCauseNewSync() { // GIVEN an initialized controller with a fragment attached when(mMockAppModel.isFullModelAvailable()).thenReturn(true); mController.init(); mController.attachFragmentUi(mMockFragmentUi); // WHEN a populated location tree is loaded LocationTree locationTree = FakeAppLocationTreeFactory.build(); mFakeEventBus.post(new AppLocationTreeFetchedEvent(locationTree)); // THEN the controller does not start a new sync assertTrue(!mFakeSyncManager.isSyncActive()); } /** * Tests that attaching a fragment UI does not show the spinner when locations are present, * even if a sync is occurring. */ public void testAttachFragmentUi_doesNotShowSpinnerDuringSyncWhenLocationsPresent() { // GIVEN an initialized controller with a location tree, with a sync in progress mFakeSyncManager.setSyncing(true); mController.init(); LocationTree locationTree = FakeAppLocationTreeFactory.build(); mFakeEventBus.post(new AppLocationTreeFetchedEvent(locationTree)); // WHEN a fragment is attached mController.attachFragmentUi(mMockFragmentUi); // THEN the loading dialog is not displayed verify(mMockFragmentUi).setBusyLoading(false); } /** * Tests that attaching a fragment UI shows the spinner if performing during a sync, * when location tree is empty. */ public void testAttachFragmentUi_showsSpinnerDuringSyncWhenLocationTreeEmpty() { // GIVEN an initialized controller with a location tree, with a sync in progress mFakeSyncManager.setSyncing(true); mController.init(); LocationTree locationTree = FakeAppLocationTreeFactory.emptyTree(); mFakeEventBus.post(new AppLocationTreeFetchedEvent(locationTree)); // WHEN a fragment is attached mController.attachFragmentUi(mMockFragmentUi); // THEN the loading dialog is displayed verify(mMockFragmentUi).setBusyLoading(true); } /** * Tests that attaching a fragment UI shows the spinner if performing during a sync, * when location tree is not present. */ public void testAttachFragmentUi_showsSpinnerDuringSyncWhenLocationsNotPresent() { // GIVEN an initialized controller with a sync in progress and no location tree mFakeSyncManager.setSyncing(true); mController.init(); // WHEN a fragment is attached mController.attachFragmentUi(mMockFragmentUi); // THEN the loading dialog is displayed verify(mMockFragmentUi).setBusyLoading(true); } /** Tests that user-initiated sync cancellation closes the activity. */ public void testSyncCancellation_closesActivityWhenUserInitiated() { // GIVEN an initialized controller and no location tree mController.init(); // WHEN user initiates and completes a sync cancellation mFakeEventBus.post(new SyncCancelRequestedEvent()); mFakeEventBus.post(new SyncCanceledEvent()); // THEN the activity is closed verify(mMockUi).finish(); } /** * Tests that user-initiated sync cancellation closes the activity even when the data model has * become available since cancellation was requested. */ public void testSyncCancellation_closesActivityWhenUserInitiatedAndDataModelAvailable() { // GIVEN an initialized controller mController.init(); // WHEN user initiates a sync cancellation right before the data model is fetched mFakeEventBus.post(new SyncCancelRequestedEvent()); LocationTree locationTree = FakeAppLocationTreeFactory.build(); mFakeEventBus.post(new AppLocationTreeFetchedEvent(locationTree)); mFakeEventBus.post(new SyncCanceledEvent()); // THEN the activity is closed verify(mMockUi).finish(); } /** Tests that sync cancellations requested by the Android OS do not close the activity. */ public void testSyncCancellation_doesNotCloseActivityIfNotUserInitiated() { // GIVEN an initialized controller and no location tree mController.init(); // WHEN a sync is canceled, but not by the user mFakeEventBus.post(new SyncCanceledEvent()); // THEN the activity is not closed verify(mMockUi, times(0)).finish(); } /** Tests that 'sync progress' messages are ignored when the data model is already available. */ public void testSyncProgress_ignoredWhenDataModelAvailable() { // GIVEN an initialized controller with a location tree mController.init(); LocationTree locationTree = FakeAppLocationTreeFactory.build(); mFakeEventBus.post(new AppLocationTreeFetchedEvent(locationTree)); // WHEN a periodic sync reports progress mFakeEventBus.post(new SyncProgressEvent(10, "Foo synced")); // THEN the activity does not notify the UI verify(mMockFragmentUi, times(0)).showIncrementalSyncProgress(10, "Foo synced"); } /** Tests that 'sync failed' messages are ignored when the data model is already available. */ public void testSyncFailed_ignoredWhenDataModelAvailable() { // GIVEN an initialized controller with a location tree mController.init(); LocationTree locationTree = FakeAppLocationTreeFactory.build(); mFakeEventBus.post(new AppLocationTreeFetchedEvent(locationTree)); // WHEN a periodic sync fails mFakeEventBus.post(new SyncFailedEvent()); // THEN the activity does not notify the UI verify(mMockUi, times(0)).showSyncFailedDialog(true); } /** Tests that 'sync started' messages are ignored when the data model is already available. */ public void testSyncStarted_ignoredWhenDataModelAvailable() { // GIVEN an initialized controller with a location tree mController.init(); LocationTree locationTree = FakeAppLocationTreeFactory.build(); mFakeEventBus.post(new AppLocationTreeFetchedEvent(locationTree)); // WHEN a periodic sync starts mFakeEventBus.post(new SyncStartedEvent()); // THEN the activity does not notify the UI verify(mMockFragmentUi, times(0)).resetSyncProgress(); } @Override protected void setUp() throws Exception { super.setUp(); MockitoAnnotations.initMocks(this); mFakeEventBus = new FakeEventBus(); mFakeSyncManager = new FakeSyncManager(); mController = new LocationListController( mMockAppModel, mFakeEventBus, mMockUi, mFakeEventBus, mFakeSyncManager, mMockSearchController); } }