package com.getbase.android.db.loaders; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.*; import com.getbase.android.db.CustomRobolectricTestRunner; import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.collect.Lists; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowContentResolver; import android.content.ContentProvider; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.support.v4.content.Loader; import java.util.List; @RunWith(CustomRobolectricTestRunner.class) @Config(manifest = Config.NONE) public class ComposedCursorLoaderTest { public static final String FAKE_AUTHORITY = "com.getbase.android.database"; private static final Uri TEST_URI = Uri.parse(String.format("content://%s/people", FAKE_AUTHORITY)); private static final Function<Cursor, String> CONST_FUNCTION = new Function<Cursor, String>() { private final static String BOOM = "boom"; @Override public String apply(Cursor input) { return BOOM; } }; private static final Function<Cursor, String> FAILING_FUNCTION = new Function<Cursor, String>() { @Override public String apply(Cursor input) { throw new AssertionError("boom"); } }; @Mock private ContentProvider providerMock; private MatrixCursor cursor; private Function<Cursor, String> genericToStringFunction = new Function<Cursor, String>() { @Override public String apply(Cursor cursor) { return cursor.getString(0); } }; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); ShadowContentResolver.registerProvider(FAKE_AUTHORITY, providerMock); cursor = buildCursor(); makeProviderReturn(cursor); Robolectric.getBackgroundScheduler().pause(); } private MatrixCursor buildCursor() { MatrixCursor cursor = new MatrixCursor(new String[] { "name" }); cursor.addRow(new Object[] { "my_name" }); cursor.addRow(new Object[] { "my_second_name" }); cursor.setNotificationUri(Robolectric.application.getContentResolver(), TEST_URI); return cursor; } private void makeProviderReturn(Cursor cursor) { when(providerMock.query(eq(TEST_URI), (String[]) isNull(), (String) isNull(), (String[]) isNull(), (String) isNull())) .thenReturn(cursor); } @Test public void shouldApplyTransformFunctionInDoInBackground() throws Exception { final Loader<List<String>> loader = CursorLoaderBuilder.forUri(TEST_URI) .transformRow(genericToStringFunction) .build(Robolectric.application); loader.startLoading(); final Loader.OnLoadCompleteListener<List<String>> listenerMock = mock(Loader.OnLoadCompleteListener.class); loader.registerListener(0, listenerMock); Robolectric.getBackgroundScheduler().runOneTask(); verify(listenerMock).onLoadComplete(same(loader), eq(Lists.newArrayList("my_name", "my_second_name"))); } @Test public void shouldNotCloseJustReturnedCursor() throws Exception { final Loader<List<String>> loader = CursorLoaderBuilder.forUri(TEST_URI) .transformRow(genericToStringFunction) .build(Robolectric.application); loader.startLoading(); Robolectric.getBackgroundScheduler().runOneTask(); assertThat(cursor.isClosed()).isFalse(); } @Test public void shouldCloseOldCursorIfNewOneAppears() throws Exception { final Loader<List<String>> loader = CursorLoaderBuilder.forUri(TEST_URI) .transformRow(genericToStringFunction) .build(Robolectric.application); loader.startLoading(); Robolectric.getBackgroundScheduler().runOneTask(); final MatrixCursor secondCursor = new MatrixCursor(cursor.getColumnNames()); loader.reset(); Robolectric.getBackgroundScheduler().runOneTask(); makeProviderReturn(secondCursor); assertThat(cursor.isClosed()).isTrue(); assertThat(secondCursor.isClosed()).isFalse(); } @Test public void shouldTransformCursorProperly() throws Exception { final Loader<MyCustomWrapper> loader = CursorLoaderBuilder.forUri(TEST_URI) .transformRow(genericToStringFunction) .transform(new Function<List<String>, MyCustomWrapper>() { @Override public MyCustomWrapper apply(List<String> strings) { return new MyCustomWrapper(strings); } }) .build(Robolectric.application); loader.startLoading(); final Loader.OnLoadCompleteListener<MyCustomWrapper> listenerMock = mock(Loader.OnLoadCompleteListener.class); loader.registerListener(0, listenerMock); Robolectric.getBackgroundScheduler().runOneTask(); verify(listenerMock).onLoadComplete(same(loader), eq(new MyCustomWrapper(Lists.newArrayList("my_name", "my_second_name")))); } @Test public void shouldNotPerformLazyTransformIfNotNecessary() throws Exception { final Loader<List<String>> loader = CursorLoaderBuilder.forUri(TEST_URI) .transformRow(FAILING_FUNCTION) .lazy() .build(Robolectric.application); loader.startLoading(); Robolectric.getBackgroundScheduler().runOneTask(); } @Ignore @Test public void shouldNotCloseOldCursorInCaseItsSameAsNewOne() throws Exception { final Loader<MyCustomWrapper> loader = CursorLoaderBuilder.forUri(TEST_URI) .transformRow(genericToStringFunction) .transform(new Function<List<String>, MyCustomWrapper>() { @Override public MyCustomWrapper apply(List<String> strings) { return new MyCustomWrapper(strings); } }) .build(Robolectric.application); loader.startLoading(); Robolectric.getBackgroundScheduler().runOneTask(); Robolectric.application.getContentResolver().notifyChange(TEST_URI, null); Robolectric.getBackgroundScheduler().runOneTask(); assertThat(cursor.isClosed()).isFalse(); } private int numberOfOpenedCursors(Cursor... cursors) { int openedCursorsCount = 0; for (Cursor cursor : cursors) { if (!cursor.isClosed()) { openedCursorsCount++; } } return openedCursorsCount; } @Test public void shouldNotCloseOldCursorWhenItsTransformedToTheEqualObjectAsOldOne() throws Exception { final Loader<MyCustomWrapper> loader = CursorLoaderBuilder.forUri(TEST_URI) .transformRow(genericToStringFunction) .transform(new Function<List<String>, MyCustomWrapper>() { @Override public MyCustomWrapper apply(List<String> strings) { return new MyCustomWrapper(strings); } }) .build(Robolectric.application); loader.startLoading(); Robolectric.getBackgroundScheduler().runOneTask(); Robolectric.application.getContentResolver().notifyChange(TEST_URI, null); MatrixCursor nextCursor = buildCursor(); makeProviderReturn(nextCursor); Robolectric.getBackgroundScheduler().runOneTask(); assertThat(numberOfOpenedCursors(cursor, nextCursor)).isEqualTo(1); } @Test public void shouldNotCloseOldCursorWhenItsTransformedToTheSameObjectAsOldOne() throws Exception { final Loader<String> loader = CursorLoaderBuilder.forUri(TEST_URI) .transform(CONST_FUNCTION) .build(Robolectric.application); loader.startLoading(); Robolectric.getBackgroundScheduler().runOneTask(); Robolectric.application.getContentResolver().notifyChange(TEST_URI, null); MatrixCursor nextCursor = buildCursor(); makeProviderReturn(nextCursor); Robolectric.getBackgroundScheduler().runOneTask(); assertThat(numberOfOpenedCursors(cursor, nextCursor)).isEqualTo(1); } @Test public void shouldCloseOldCursorDeliveredEarlierTwice() throws Exception { final Loader<MyCustomWrapper> loader = CursorLoaderBuilder.forUri(TEST_URI) .transformRow(genericToStringFunction) .transform(new Function<List<String>, MyCustomWrapper>() { @Override public MyCustomWrapper apply(List<String> strings) { return new MyCustomWrapper(strings); } }) .build(Robolectric.application); loader.startLoading(); Robolectric.getBackgroundScheduler().runOneTask(); loader.startLoading(); Robolectric.getBackgroundScheduler().runOneTask(); loader.reset(); Robolectric.getBackgroundScheduler().runOneTask(); assertThat(cursor.isClosed()).isTrue(); } @Test public void shouldNotDeliverResultIfLoaderHasBeenResetAlready() throws Exception { final Loader<MyCustomWrapper> loader = CursorLoaderBuilder.forUri(TEST_URI) .transformRow(genericToStringFunction) .transform(new Function<List<String>, MyCustomWrapper>() { @Override public MyCustomWrapper apply(List<String> strings) { return new MyCustomWrapper(strings); } }) .build(Robolectric.application); final Loader.OnLoadCompleteListener<MyCustomWrapper> listenerMock = mock(Loader.OnLoadCompleteListener.class); loader.registerListener(0, listenerMock); loader.startLoading(); loader.reset(); Robolectric.getBackgroundScheduler().runTasks(1); makeProviderReturn(new MatrixCursor(cursor.getColumnNames())); loader.startLoading(); Robolectric.getBackgroundScheduler().runTasks(1); verify(listenerMock, times(1)).onLoadComplete(same(loader), eq(new MyCustomWrapper(Lists.<String>newArrayList()))); } private static class MyCustomWrapper { private List<String> strings; private MyCustomWrapper(List<String> strings) { this.strings = strings; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MyCustomWrapper that = (MyCustomWrapper) o; return Objects.equal(strings, that.strings); } @Override public int hashCode() { return strings != null ? strings.hashCode() : 0; } } }