package com.mopub.nativeads;
import android.os.Handler;
import android.view.View;
import com.mopub.common.test.support.SdkTestRunner;
import org.fest.util.Lists;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.robolectric.Robolectric;
import org.robolectric.shadows.ShadowSystemClock;
import java.util.HashMap;
import static com.mopub.nativeads.MoPubNative.MoPubNativeListener;
import static com.mopub.nativeads.VisibilityTracker.VisibilityChecker;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(SdkTestRunner.class)
public class ImpressionTrackerTest {
private ImpressionTracker subject;
private TimestampWrapper<NativeResponse> timeStampWrapper;
private HashMap<View, NativeResponse> trackedViews;
private HashMap<View, TimestampWrapper<NativeResponse>> pollingViews;
@Mock private NativeResponse nativeResponse;
@Mock private NativeResponse nativeResponse2;
@Mock private MoPubNativeListener moPubNativeListener;
@Mock private VisibilityTracker visibilityTracker;
@Mock private Handler handler;
@Mock private View view;
@Mock private View view2;
@Before
public void setUp() {
view = VisibilityTrackerTest.createViewMock(View.VISIBLE, 100, 100, 100, 100, true, true);
view2 = VisibilityTrackerTest.createViewMock(View.VISIBLE, 100, 100, 100, 100, true, true);
pollingViews = new HashMap<View, TimestampWrapper<NativeResponse>>(10);
trackedViews = new HashMap<View, NativeResponse>(10);
final VisibilityChecker visibilityChecker = new VisibilityChecker();
subject = new ImpressionTracker(trackedViews, pollingViews, visibilityChecker,
visibilityTracker, handler);
timeStampWrapper = new TimestampWrapper<NativeResponse>(nativeResponse);
when(nativeResponse.getImpressionMinPercentageViewed()).thenReturn(50);
when(nativeResponse.getImpressionMinTimeViewed()).thenReturn(1000);
when(nativeResponse2.getImpressionMinPercentageViewed()).thenReturn(50);
when(nativeResponse2.getImpressionMinTimeViewed()).thenReturn(1000);
// XXX We need this to ensure that our SystemClock starts
ShadowSystemClock.uptimeMillis();
}
@Test
public void addView_shouldAddViewToTrackedViews_shouldAddViewToVisibilityTracker() {
subject.addView(view, nativeResponse);
assertThat(trackedViews).hasSize(1);
assertThat(trackedViews.get(view)).isEqualTo(nativeResponse);
verify(visibilityTracker).addView(view, nativeResponse.getImpressionMinPercentageViewed());
}
@Test
public void addView_withRecordedImpression_shouldNotAddView() {
when(nativeResponse.getRecordedImpression()).thenReturn(true);
subject.addView(view, nativeResponse);
assertThat(trackedViews).hasSize(0);
verify(visibilityTracker, never())
.addView(view, nativeResponse.getImpressionMinPercentageViewed());
}
@Test
public void addView_withDestroyedNativeResponse_shouldNotAddView() {
when(nativeResponse.isDestroyed()).thenReturn(true);
subject.addView(view, nativeResponse);
assertThat(trackedViews).isEmpty();
verify(visibilityTracker, never())
.addView(view, nativeResponse.getImpressionMinPercentageViewed());
}
@Test
public void addView_withDifferentNativeResponse_shouldRemoveFromPollingViews() {
subject.addView(view, nativeResponse);
assertThat(trackedViews).hasSize(1);
assertThat(trackedViews.get(view)).isEqualTo(nativeResponse);
verify(visibilityTracker).addView(view, nativeResponse.getImpressionMinPercentageViewed());
pollingViews.put(view, timeStampWrapper);
subject.addView(view, nativeResponse2);
assertThat(trackedViews).hasSize(1);
assertThat(trackedViews.get(view)).isEqualTo(nativeResponse2);
assertThat(pollingViews).isEmpty();
verify(visibilityTracker, times(2))
.addView(view, nativeResponse.getImpressionMinPercentageViewed());
}
@Test
public void addView_withDifferentAlreadyImpressedNativeResponse_shouldRemoveFromPollingViews_shouldNotTrack() {
when(nativeResponse2.getRecordedImpression()).thenReturn(true);
subject.addView(view, nativeResponse);
assertThat(trackedViews).hasSize(1);
assertThat(trackedViews.get(view)).isEqualTo(nativeResponse);
verify(visibilityTracker).addView(view, nativeResponse.getImpressionMinPercentageViewed());
pollingViews.put(view, timeStampWrapper);
subject.addView(view, nativeResponse2);
assertThat(trackedViews).hasSize(0);
assertThat(trackedViews.get(view)).isNull();
assertThat(pollingViews).isEmpty();
verify(visibilityTracker).addView(view, nativeResponse.getImpressionMinPercentageViewed());
}
@Test
public void addView_withSameNativeResponse_shouldNotAddView() {
subject.addView(view, nativeResponse);
assertThat(trackedViews).hasSize(1);
assertThat(trackedViews.get(view)).isEqualTo(nativeResponse);
verify(visibilityTracker).addView(view, nativeResponse.getImpressionMinPercentageViewed());
pollingViews.put(view, timeStampWrapper);
subject.addView(view, nativeResponse);
assertThat(trackedViews).hasSize(1);
assertThat(trackedViews.get(view)).isEqualTo(nativeResponse);
assertThat(pollingViews.keySet()).containsOnly(view);
// Still only one call
verify(visibilityTracker).addView(view, nativeResponse.getImpressionMinPercentageViewed());
}
@Test
public void removeView_shouldRemoveViewFromViewTrackedViews_shouldRemoveViewFromPollingMap_shouldRemoveViewFromVisibilityTracker() {
trackedViews.put(view, nativeResponse);
pollingViews.put(view, new TimestampWrapper<NativeResponse>(nativeResponse));
visibilityTracker.addView(view, nativeResponse.getImpressionMinPercentageViewed());
subject.removeView(view);
assertThat(trackedViews).isEmpty();
assertThat(pollingViews).isEmpty();
verify(visibilityTracker).removeView(view);
}
@Test
public void clear_shouldClearViewTrackedViews_shouldClearPollingViews_shouldClearVisibilityTracker_shouldClearPollHandler() {
trackedViews.put(view, nativeResponse);
trackedViews.put(view2, nativeResponse);
pollingViews.put(view, timeStampWrapper);
pollingViews.put(view2, timeStampWrapper);
visibilityTracker.addView(view, nativeResponse.getImpressionMinPercentageViewed());
visibilityTracker.addView(view2, nativeResponse.getImpressionMinPercentageViewed());
subject.clear();
assertThat(trackedViews).isEmpty();
assertThat(pollingViews).isEmpty();
verify(visibilityTracker).clear();
verify(handler).removeMessages(0);
}
@Test
public void destroy_shouldCallClear_shouldDestroyVisibilityTracker_shouldSetVisibilityTrackerListenerToNull() throws Exception {
trackedViews.put(view, nativeResponse);
trackedViews.put(view2, nativeResponse);
pollingViews.put(view, timeStampWrapper);
pollingViews.put(view2, timeStampWrapper);
visibilityTracker.addView(view, nativeResponse.getImpressionMinPercentageViewed());
visibilityTracker.addView(view2, nativeResponse.getImpressionMinPercentageViewed());
assertThat(subject.getVisibilityTrackerListener()).isNotNull();
subject.destroy();
assertThat(trackedViews).isEmpty();
assertThat(pollingViews).isEmpty();
verify(visibilityTracker).clear();
verify(handler).removeMessages(0);
verify(visibilityTracker).destroy();
assertThat(subject.getVisibilityTrackerListener()).isNull();
}
@Test
public void scheduleNextPoll_shouldPostDelayedThePollingRunnable() {
when(handler.hasMessages(0)).thenReturn(false);
subject.scheduleNextPoll();
verify(handler).postDelayed(any(ImpressionTracker.PollingRunnable.class), eq((long) 250));
}
@Test
public void scheduleNextPoll_withMessages_shouldNotPostDelayedThePollingRunnable() {
when(handler.hasMessages(0)).thenReturn(true);
subject.scheduleNextPoll();
verify(handler, never())
.postDelayed(any(ImpressionTracker.PollingRunnable.class), eq((long) 250));
}
@Test
public void visibilityTrackerListener_onVisibilityChanged_withVisibleViews_shouldAddViewToPollingViews_shouldScheduleNextPoll() {
subject.addView(view, nativeResponse);
assertThat(pollingViews).isEmpty();
subject.getVisibilityTrackerListener()
.onVisibilityChanged(Lists.newArrayList(view), Lists.<View>newArrayList());
assertThat(pollingViews.keySet()).containsOnly(view);
verify(handler).postDelayed(any(ImpressionTracker.PollingRunnable.class), eq((long) 250));
}
@Test
public void visibilityTrackerListener_onVisibilityChanged_withVisibleViews_shouldRemoveViewFromPollingViews() {
subject.addView(view, nativeResponse);
subject.getVisibilityTrackerListener()
.onVisibilityChanged(Lists.newArrayList(view), Lists.<View>newArrayList());
assertThat(trackedViews.keySet()).containsOnly(view);
assertThat(pollingViews.keySet()).containsOnly(view);
subject.getVisibilityTrackerListener()
.onVisibilityChanged(Lists.<View>newArrayList(), Lists.newArrayList(view));
assertThat(trackedViews.keySet()).containsOnly(view);
assertThat(pollingViews).isEmpty();
}
@Test
public void pollingRunnableRun_whenLessThanOneSecondHasElapsed_shouldNotTrackImpression_shouldScheduleNextPoll() {
// Force the last viewed timestamp to be a known value
timeStampWrapper.mCreatedTimestamp = 5555;
pollingViews.put(view, timeStampWrapper);
// We progress 999 milliseconds
Robolectric.getUiThreadScheduler().advanceBy(5555 + 999);
subject.new PollingRunnable().run();
verify(nativeResponse, never()).recordImpression(view);
assertThat(pollingViews.keySet()).containsOnly(view);
verify(handler).postDelayed(any(ImpressionTracker.PollingRunnable.class), eq((long) 250));
}
@Test
public void pollingRunnableRun_whenMoreThanOneSecondHasElapsed_shouldTrackImpression_shouldNotScheduleNextPoll() {
// Force the last viewed timestamp to be a known value
timeStampWrapper.mCreatedTimestamp = 5555;
pollingViews.put(view, timeStampWrapper);
// We progress 1000 milliseconds
Robolectric.getUiThreadScheduler().advanceBy(5555 + 1000);
subject.new PollingRunnable().run();
verify(nativeResponse).recordImpression(view);
assertThat(pollingViews).isEmpty();
verify(handler, never())
.postDelayed(any(ImpressionTracker.PollingRunnable.class), eq((long) 250));
}
@Test(expected = NullPointerException.class)
public void pollingRunnableRun_whenWrapperIsNull_shouldThrowNPE() {
pollingViews.put(view, null);
subject.new PollingRunnable().run();
verify(nativeResponse, never()).recordImpression(view);
}
@Test(expected = NullPointerException.class)
public void pollingRunnableRun_whenNativeResponseIsNull_shouldThrowNPE() {
// This doesn't normally happen; perhaps we're being overly defensive
pollingViews.put(view, new TimestampWrapper<NativeResponse>(null));
subject.new PollingRunnable().run();
verify(nativeResponse, never()).recordImpression(view);
}
}