/*
* Copyright (C) 2013 Square, Inc.
*
* 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
* distributed 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 the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.picasso;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.widget.ImageView;
import android.widget.RemoteViews;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.ExecutorService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import static android.graphics.Bitmap.Config.ARGB_8888;
import static com.squareup.picasso.Picasso.Listener;
import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY;
import static com.squareup.picasso.RemoteViewsAction.RemoteViewsTarget;
import static com.squareup.picasso.TestUtils.URI_1;
import static com.squareup.picasso.TestUtils.URI_KEY_1;
import static com.squareup.picasso.TestUtils.makeBitmap;
import static com.squareup.picasso.TestUtils.mockAction;
import static com.squareup.picasso.TestUtils.mockCanceledAction;
import static com.squareup.picasso.TestUtils.mockDeferredRequestCreator;
import static com.squareup.picasso.TestUtils.mockHunter;
import static com.squareup.picasso.TestUtils.mockImageViewTarget;
import static com.squareup.picasso.TestUtils.mockTarget;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.fest.assertions.api.Assertions.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
@RunWith(RobolectricGradleTestRunner.class)
@Config(sdk = 23) // Works around https://github.com/robolectric/robolectric/issues/2566.
public class PicassoTest {
@Mock Context context;
@Mock Downloader downloader;
@Mock Dispatcher dispatcher;
@Mock Picasso.RequestTransformer transformer;
@Mock RequestHandler requestHandler;
@Mock Cache cache;
@Mock Listener listener;
@Mock Stats stats;
private Picasso picasso;
final Bitmap bitmap = makeBitmap();
@Before public void setUp() {
initMocks(this);
picasso = new Picasso(context, dispatcher, cache, listener, transformer, null, stats, ARGB_8888,
false, false);
}
@Test public void submitWithNullTargetInvokesDispatcher() {
Action action = mockAction(URI_KEY_1, URI_1);
picasso.enqueueAndSubmit(action);
assertThat(picasso.targetToAction).isEmpty();
verify(dispatcher).dispatchSubmit(action);
}
@Test public void submitWithTargetInvokesDispatcher() {
Action action = mockAction(URI_KEY_1, URI_1, mockImageViewTarget());
assertThat(picasso.targetToAction).isEmpty();
picasso.enqueueAndSubmit(action);
assertThat(picasso.targetToAction).hasSize(1);
verify(dispatcher).dispatchSubmit(action);
}
@Test public void submitWithSameActionDoesNotCancel() {
Action action = mockAction(URI_KEY_1, URI_1, mockImageViewTarget());
picasso.enqueueAndSubmit(action);
verify(dispatcher).dispatchSubmit(action);
assertThat(picasso.targetToAction).hasSize(1).containsValue(action);
picasso.enqueueAndSubmit(action);
verify(action, never()).cancel();
verify(dispatcher, never()).dispatchCancel(action);
}
@Test public void quickMemoryCheckReturnsBitmapIfInCache() {
when(cache.get(URI_KEY_1)).thenReturn(bitmap);
Bitmap cached = picasso.quickMemoryCacheCheck(URI_KEY_1);
assertThat(cached).isEqualTo(bitmap);
verify(stats).dispatchCacheHit();
}
@Test public void quickMemoryCheckReturnsNullIfNotInCache() {
Bitmap cached = picasso.quickMemoryCacheCheck(URI_KEY_1);
assertThat(cached).isNull();
verify(stats).dispatchCacheMiss();
}
@Test public void completeInvokesSuccessOnAllSuccessfulRequests() {
Action action1 = mockAction(URI_KEY_1, URI_1, mockImageViewTarget());
Action action2 = mockCanceledAction();
BitmapHunter hunter = mockHunter(URI_KEY_1, bitmap, false);
when(hunter.getActions()).thenReturn(Arrays.asList(action1, action2));
when(hunter.getLoadedFrom()).thenReturn(MEMORY);
picasso.complete(hunter);
verify(action1).complete(bitmap, MEMORY);
verify(action2, never()).complete(eq(bitmap), any(Picasso.LoadedFrom.class));
}
@Test public void completeInvokesErrorOnAllFailedRequests() {
Action action1 = mockAction(URI_KEY_1, URI_1, mockImageViewTarget());
Action action2 = mockCanceledAction();
Exception exception = mock(Exception.class);
BitmapHunter hunter = mockHunter(URI_KEY_1, null, false);
when(hunter.getException()).thenReturn(exception);
when(hunter.getActions()).thenReturn(Arrays.asList(action1, action2));
picasso.complete(hunter);
verify(action1).error(exception);
verify(action2, never()).error(exception);
verify(listener).onImageLoadFailed(picasso, URI_1, exception);
}
@Test public void completeDeliversToSingle() {
Action action = mockAction(URI_KEY_1, URI_1, mockImageViewTarget());
BitmapHunter hunter = mockHunter(URI_KEY_1, bitmap, false);
when(hunter.getLoadedFrom()).thenReturn(MEMORY);
when(hunter.getAction()).thenReturn(action);
when(hunter.getActions()).thenReturn(Collections.<Action>emptyList());
picasso.complete(hunter);
verify(action).complete(bitmap, MEMORY);
}
@Test public void completeWithReplayDoesNotRemove() {
Action action = mockAction(URI_KEY_1, URI_1, mockImageViewTarget());
when(action.willReplay()).thenReturn(true);
BitmapHunter hunter = mockHunter(URI_KEY_1, bitmap, false);
when(hunter.getLoadedFrom()).thenReturn(MEMORY);
when(hunter.getAction()).thenReturn(action);
picasso.enqueueAndSubmit(action);
assertThat(picasso.targetToAction).hasSize(1);
picasso.complete(hunter);
assertThat(picasso.targetToAction).hasSize(1);
verify(action).complete(bitmap, MEMORY);
}
@Test public void completeDeliversToSingleAndMultiple() {
Action action = mockAction(URI_KEY_1, URI_1, mockImageViewTarget());
Action action2 = mockAction(URI_KEY_1, URI_1, mockImageViewTarget());
BitmapHunter hunter = mockHunter(URI_KEY_1, bitmap, false);
when(hunter.getLoadedFrom()).thenReturn(MEMORY);
when(hunter.getAction()).thenReturn(action);
when(hunter.getActions()).thenReturn(Arrays.asList(action2));
picasso.complete(hunter);
verify(action).complete(bitmap, MEMORY);
verify(action2).complete(bitmap, MEMORY);
}
@Test public void completeSkipsIfNoActions() {
BitmapHunter hunter = mockHunter(URI_KEY_1, bitmap, false);
picasso.complete(hunter);
verify(hunter).getAction();
verify(hunter).getActions();
verifyNoMoreInteractions(hunter);
}
@Test public void loadedFromIsNullThrows() {
Action action = mockAction(URI_KEY_1, URI_1, mockImageViewTarget());
BitmapHunter hunter = mockHunter(URI_KEY_1, bitmap, false);
when(hunter.getAction()).thenReturn(action);
try {
picasso.complete(hunter);
fail("Calling complete() with null LoadedFrom should throw");
} catch (AssertionError expected) {
}
}
@Test public void resumeActionTriggersSubmitOnPausedAction() {
Action action = mockAction(URI_KEY_1, URI_1);
picasso.resumeAction(action);
verify(dispatcher).dispatchSubmit(action);
}
@Test public void resumeActionImmediatelyCompletesCachedRequest() {
when(cache.get(URI_KEY_1)).thenReturn(bitmap);
Action action = mockAction(URI_KEY_1, URI_1);
picasso.resumeAction(action);
verify(action).complete(bitmap, MEMORY);
}
@Test public void cancelExistingRequestWithUnknownTarget() {
ImageView target = mockImageViewTarget();
Action action = mockAction(URI_KEY_1, URI_1, target);
picasso.cancelRequest(target);
verifyZeroInteractions(action, dispatcher);
}
@Test public void cancelExistingRequestWithNullImageView() {
try {
picasso.cancelRequest((ImageView) null);
fail("Canceling with a null ImageView should throw exception.");
} catch (IllegalArgumentException expected) {
}
}
@Test public void cancelExistingRequestWithNullTarget() {
try {
picasso.cancelRequest((Target) null);
fail("Canceling with a null target should throw exception.");
} catch (IllegalArgumentException expected) {
}
}
@Test public void cancelExistingRequestWithImageViewTarget() {
ImageView target = mockImageViewTarget();
Action action = mockAction(URI_KEY_1, URI_1, target);
picasso.enqueueAndSubmit(action);
assertThat(picasso.targetToAction).hasSize(1);
picasso.cancelRequest(target);
assertThat(picasso.targetToAction).isEmpty();
verify(action).cancel();
verify(dispatcher).dispatchCancel(action);
}
@Test public void cancelExistingRequestWithDeferredImageViewTarget() {
ImageView target = mockImageViewTarget();
DeferredRequestCreator deferredRequestCreator = mockDeferredRequestCreator();
picasso.targetToDeferredRequestCreator.put(target, deferredRequestCreator);
picasso.cancelRequest(target);
verify(deferredRequestCreator).cancel();
assertThat(picasso.targetToDeferredRequestCreator).isEmpty();
}
@Test public void enqueueingDeferredRequestCancelsThePreviousOne() throws Exception {
ImageView target = mockImageViewTarget();
DeferredRequestCreator firstRequestCreator = mockDeferredRequestCreator();
picasso.defer(target, firstRequestCreator);
assertThat(picasso.targetToDeferredRequestCreator).containsKey(target);
DeferredRequestCreator secondRequestCreator = mockDeferredRequestCreator();
picasso.defer(target, secondRequestCreator);
verify(firstRequestCreator).cancel();
assertThat(picasso.targetToDeferredRequestCreator).containsKey(target);
}
@Test public void cancelExistingRequestWithTarget() {
Target target = mockTarget();
Action action = mockAction(URI_KEY_1, URI_1, target);
picasso.enqueueAndSubmit(action);
assertThat(picasso.targetToAction).hasSize(1);
picasso.cancelRequest(target);
assertThat(picasso.targetToAction).isEmpty();
verify(action).cancel();
verify(dispatcher).dispatchCancel(action);
}
@Test public void cancelExistingRequestWithNullRemoteViews() {
try {
picasso.cancelRequest(null, 0);
fail("Canceling with a null RemoteViews should throw exception.");
} catch (IllegalArgumentException expected) {
}
}
@Config(sdk = 16) // This test fails on 23 so restore the default level.
@Test public void cancelExistingRequestWithRemoteViewTarget() {
int layoutId = 0;
int viewId = 1;
RemoteViews remoteViews = new RemoteViews("packageName", layoutId);
RemoteViewsTarget target = new RemoteViewsTarget(remoteViews, viewId);
Action action = mockAction(URI_KEY_1, URI_1, target);
picasso.enqueueAndSubmit(action);
assertThat(picasso.targetToAction).hasSize(1);
picasso.cancelRequest(remoteViews, viewId);
assertThat(picasso.targetToAction).isEmpty();
verify(action).cancel();
verify(dispatcher).dispatchCancel(action);
}
@Test public void cancelNullTagThrows() {
try {
picasso.cancelTag(null);
fail("Canceling with a null tag should throw exception.");
} catch (IllegalArgumentException expected) {
}
}
@Test public void cancelTagAllActions() {
ImageView target = mockImageViewTarget();
Action action = mockAction(URI_KEY_1, URI_1, target, "TAG");
picasso.enqueueAndSubmit(action);
assertThat(picasso.targetToAction).hasSize(1);
picasso.cancelTag("TAG");
assertThat(picasso.targetToAction).isEmpty();
verify(action).cancel();
}
@Test public void cancelTagAllDeferredRequests() {
ImageView target = mockImageViewTarget();
DeferredRequestCreator deferredRequestCreator = mockDeferredRequestCreator();
when(deferredRequestCreator.getTag()).thenReturn("TAG");
picasso.defer(target, deferredRequestCreator);
picasso.cancelTag("TAG");
verify(deferredRequestCreator).cancel();
}
@Test public void deferAddsToMap() {
ImageView target = mockImageViewTarget();
DeferredRequestCreator deferredRequestCreator = mockDeferredRequestCreator();
assertThat(picasso.targetToDeferredRequestCreator).isEmpty();
picasso.defer(target, deferredRequestCreator);
assertThat(picasso.targetToDeferredRequestCreator).hasSize(1);
}
@Test public void shutdown() {
picasso.shutdown();
verify(cache).clear();
verify(stats).shutdown();
verify(dispatcher).shutdown();
assertThat(picasso.shutdown).isTrue();
}
@Test public void shutdownTwice() {
picasso.shutdown();
picasso.shutdown();
verify(cache).clear();
verify(stats).shutdown();
verify(dispatcher).shutdown();
assertThat(picasso.shutdown).isTrue();
}
@Test public void shutdownDisallowedOnSingletonInstance() {
Picasso.singleton = null;
try {
Picasso picasso = Picasso.with(RuntimeEnvironment.application);
picasso.shutdown();
fail("Calling shutdown() on static singleton instance should throw");
} catch (UnsupportedOperationException expected) {
}
}
@Test public void shutdownDisallowedOnCustomSingletonInstance() {
Picasso.singleton = null;
try {
Picasso picasso = new Picasso.Builder(RuntimeEnvironment.application).build();
Picasso.setSingletonInstance(picasso);
picasso.shutdown();
fail("Calling shutdown() on static singleton instance should throw");
} catch (UnsupportedOperationException expected) {
}
}
@Test public void setSingletonInstanceRejectsNull() {
Picasso.singleton = null;
try {
Picasso.setSingletonInstance(null);
fail("Can't set singleton instance to null.");
} catch (IllegalArgumentException e) {
assertThat(e).hasMessage("Picasso must not be null.");
}
}
@Test public void setSingletonInstanceMayOnlyBeCalledOnce() {
Picasso.singleton = null;
Picasso picasso = new Picasso.Builder(RuntimeEnvironment.application).build();
Picasso.setSingletonInstance(picasso);
try {
Picasso.setSingletonInstance(picasso);
fail("Can't set singleton instance twice.");
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Singleton instance already exists.");
}
}
@Test public void setSingletonInstanceAfterWithFails() {
Picasso.singleton = null;
// Implicitly create the default singleton instance.
Picasso.with(RuntimeEnvironment.application);
Picasso picasso = new Picasso.Builder(RuntimeEnvironment.application).build();
try {
Picasso.setSingletonInstance(picasso);
fail("Can't set singleton instance after with().");
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Singleton instance already exists.");
}
}
@Test public void setSingleInstanceReturnedFromWith() {
Picasso.singleton = null;
Picasso picasso = new Picasso.Builder(RuntimeEnvironment.application).build();
Picasso.setSingletonInstance(picasso);
assertThat(Picasso.with(RuntimeEnvironment.application)).isSameAs(picasso);
}
@Test public void shutdownClearsDeferredRequests() {
DeferredRequestCreator deferredRequestCreator = mockDeferredRequestCreator();
ImageView target = mockImageViewTarget();
picasso.targetToDeferredRequestCreator.put(target, deferredRequestCreator);
picasso.shutdown();
verify(deferredRequestCreator).cancel();
assertThat(picasso.targetToDeferredRequestCreator).isEmpty();
}
@Test public void whenTransformRequestReturnsNullThrows() {
try {
when(transformer.transformRequest(any(Request.class))).thenReturn(null);
picasso.transformRequest(new Request.Builder(URI_1).build());
fail("Returning null from transformRequest() should throw");
} catch (IllegalStateException expected) {
}
}
@Test public void getSnapshotInvokesStats() {
picasso.getSnapshot();
verify(stats).createSnapshot();
}
@Test public void enableIndicators() {
assertThat(picasso.areIndicatorsEnabled()).isFalse();
picasso.setIndicatorsEnabled(true);
assertThat(picasso.areIndicatorsEnabled()).isTrue();
}
@Test public void loadThrowsWithInvalidInput() {
try {
picasso.load("");
fail("Empty URL should throw exception.");
} catch (IllegalArgumentException expected) {
}
try {
picasso.load(" ");
fail("Empty URL should throw exception.");
} catch (IllegalArgumentException expected) {
}
try {
picasso.load(0);
fail("Zero resourceId should throw exception.");
} catch (IllegalArgumentException expected) {
}
}
@Test public void builderInvalidListener() {
try {
new Picasso.Builder(context).listener(null);
fail("Null listener should throw exception.");
} catch (IllegalArgumentException expected) {
}
try {
new Picasso.Builder(context).listener(listener).listener(listener);
fail("Setting Listener twice should throw exception.");
} catch (IllegalStateException expected) {
}
}
@Test public void builderInvalidLoader() {
try {
new Picasso.Builder(context).downloader(null);
fail("Null Downloader should throw exception.");
} catch (IllegalArgumentException expected) {
}
try {
new Picasso.Builder(context).downloader(downloader).downloader(downloader);
fail("Setting Downloader twice should throw exception.");
} catch (IllegalStateException expected) {
}
}
@Test public void builderInvalidExecutor() {
try {
new Picasso.Builder(context).executor(null);
fail("Null Executor should throw exception.");
} catch (IllegalArgumentException expected) {
}
try {
ExecutorService executor = mock(ExecutorService.class);
new Picasso.Builder(context).executor(executor).executor(executor);
fail("Setting Executor twice should throw exception.");
} catch (IllegalStateException expected) {
}
}
@Test public void builderInvalidCache() {
try {
new Picasso.Builder(context).memoryCache(null);
fail("Null Cache should throw exception.");
} catch (IllegalArgumentException expected) {
}
try {
new Picasso.Builder(context).memoryCache(cache).memoryCache(cache);
fail("Setting Cache twice should throw exception.");
} catch (IllegalStateException expected) {
}
}
@Test public void builderInvalidRequestTransformer() {
try {
new Picasso.Builder(context).requestTransformer(null);
fail("Null request transformer should throw exception.");
} catch (IllegalArgumentException expected) {
}
try {
new Picasso.Builder(context).requestTransformer(transformer).requestTransformer(transformer);
fail("Setting request transformer twice should throw exception.");
} catch (IllegalStateException expected) {
}
}
@Test public void builderInvalidRequestHandler() {
try {
new Picasso.Builder(context).addRequestHandler(null);
fail("Null request handler should throw exception.");
} catch (IllegalArgumentException expected) {
}
try {
new Picasso.Builder(context).addRequestHandler(requestHandler)
.addRequestHandler(requestHandler);
fail("Registering same request handler twice should throw exception.");
} catch (IllegalStateException expected) {
}
}
@Test public void builderWithoutRequestHandler() {
Picasso picasso = new Picasso.Builder(RuntimeEnvironment.application).build();
assertThat(picasso.getRequestHandlers()).isNotEmpty().doesNotContain(requestHandler);
}
@Test public void builderWithRequestHandler() {
Picasso picasso = new Picasso.Builder(RuntimeEnvironment.application)
.addRequestHandler(requestHandler).build();
assertThat(picasso.getRequestHandlers()).isNotNull().isNotEmpty().contains(requestHandler);
}
@Test public void builderInvalidContext() {
try {
new Picasso.Builder(null);
fail("Null context should throw exception.");
} catch (IllegalArgumentException expected) {
}
}
@Test public void builderWithDebugIndicators() {
Picasso picasso = new Picasso.Builder(RuntimeEnvironment.application).indicatorsEnabled(true).build();
assertThat(picasso.areIndicatorsEnabled()).isTrue();
}
@Test public void invalidateString() {
picasso.invalidate("http://example.com");
verify(cache).clearKeyUri("http://example.com");
}
@Test public void invalidateFile() {
picasso.invalidate(new File("/foo/bar/baz"));
verify(cache).clearKeyUri("file:///foo/bar/baz");
}
@Test public void invalidateUri() {
picasso.invalidate(Uri.parse("mock://12345"));
verify(cache).clearKeyUri("mock://12345");
}
}