@Test
  public void addView_shouldClearViewAfterNumAccesses() {
    // Access 1 time
    subject.addView(view, MIN_PERCENTAGE_VIEWED);
    assertThat(trackedViews).hasSize(1);

    // Access 2-49 times
    for (int i = 0; i < VisibilityTracker.NUM_ACCESSES_BEFORE_TRIMMING - 2; ++i) {
      subject.addView(view2, MIN_PERCENTAGE_VIEWED);
    }
    assertThat(trackedViews).hasSize(2);

    // 50th time
    subject.addView(view2, MIN_PERCENTAGE_VIEWED);
    assertThat(trackedViews).hasSize(2);

    // 51-99
    for (int i = 0; i < VisibilityTracker.NUM_ACCESSES_BEFORE_TRIMMING - 1; ++i) {
      subject.addView(view2, MIN_PERCENTAGE_VIEWED);
    }
    assertThat(trackedViews).hasSize(2);

    // 100
    subject.addView(view2, MIN_PERCENTAGE_VIEWED);
    assertThat(trackedViews).hasSize(1);
  }
  @Test
  public void destroy_shouldCallClear_shouldRemoveListenerFromDecorView() throws Exception {
    Activity activity1 = mock(Activity.class);
    Window window = mock(Window.class);
    View decorView = mock(View.class);
    ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);

    when(activity1.getWindow()).thenReturn(window);
    when(window.getDecorView()).thenReturn(decorView);
    when(decorView.getViewTreeObserver()).thenReturn(viewTreeObserver);
    when(viewTreeObserver.isAlive()).thenReturn(true);

    subject = new VisibilityTracker(activity1, trackedViews, visibilityChecker, visibilityHandler);

    subject.addView(view, MIN_PERCENTAGE_VIEWED);
    subject.addView(view2, MIN_PERCENTAGE_VIEWED);
    assertThat(trackedViews).hasSize(2);

    subject.destroy();

    assertThat(trackedViews).isEmpty();
    verify(visibilityHandler).removeMessages(0);
    verify(viewTreeObserver).removeOnPreDrawListener(any(OnPreDrawListener.class));
    assertThat(subject.mOnPreDrawListener).isNull();
  }
  @Test
  public void removeView_shouldRemoveFromTrackedViews() throws Exception {
    subject.addView(view, MIN_PERCENTAGE_VIEWED);

    assertThat(trackedViews).hasSize(1);
    assertThat(trackedViews).containsKey(view);

    subject.removeView(view);

    assertThat(trackedViews).isEmpty();
  }
  @Test
  public void
      clear_shouldRemoveAllViewsFromTrackedViews_shouldRemoveMessagesFromVisibilityHandler_shouldResetIsVisibilityScheduled()
          throws Exception {
    subject.addView(view, MIN_PERCENTAGE_VIEWED);
    subject.addView(view2, MIN_PERCENTAGE_VIEWED);
    assertThat(trackedViews).hasSize(2);

    subject.clear();

    assertThat(trackedViews).isEmpty();
    verify(visibilityHandler).removeMessages(0);
  }
  @Test
  public void visibilityRunnable_run_withVisibleView_shouldCallOnVisibleCallback()
      throws Exception {
    subject.addView(view, MIN_PERCENTAGE_VIEWED);

    subject.new VisibilityRunnable().run();

    verify(visibilityTrackerListener)
        .onVisibilityChanged(Lists.newArrayList(view), Lists.<View>newArrayList());
  }
  @Test
  public void visibilityRunnable_run_withNonVisibleView_shouldCallOnNonVisibleCallback()
      throws Exception {
    when(view.getVisibility()).thenReturn(View.INVISIBLE);
    subject.addView(view, MIN_PERCENTAGE_VIEWED);

    subject.new VisibilityRunnable().run();

    ArgumentCaptor<List> visibleCaptor = ArgumentCaptor.forClass(List.class);
    ArgumentCaptor<List> invisibleCaptor = ArgumentCaptor.forClass(List.class);
    verify(visibilityTrackerListener)
        .onVisibilityChanged(visibleCaptor.capture(), invisibleCaptor.capture());
    assertThat(visibleCaptor.getValue().size()).isEqualTo(0);
    assertThat(invisibleCaptor.getValue().size()).isEqualTo(1);
  }
  @Before
  public void setUp() throws Exception {
    trackedViews = new WeakHashMap<View, TrackingInfo>();
    visibilityChecker = new VisibilityChecker();
    activity = new Activity();
    view = createViewMock(View.VISIBLE, 100, 100, 100, 100, true, true);
    view2 = createViewMock(View.VISIBLE, 100, 100, 100, 100, true, true);

    // Add a proxy listener to that makes a safe copy of the listener args.
    VisibilityTrackerListener proxyListener =
        new VisibilityTrackerListener() {
          @Override
          public void onVisibilityChanged(List<View> visibleViews, List<View> invisibleViews) {
            ArrayList<View> safeVisibleViews = new ArrayList<View>(visibleViews);
            ArrayList<View> safeInVisibleViews = new ArrayList<View>(invisibleViews);
            visibilityTrackerListener.onVisibilityChanged(safeVisibleViews, safeInVisibleViews);
          }
        };
    subject = new VisibilityTracker(activity, trackedViews, visibilityChecker, visibilityHandler);
    subject.setVisibilityTrackerListener(proxyListener);

    // XXX We need this to ensure that our SystemClock starts
    ShadowSystemClock.uptimeMillis();
  }
  @Test(expected = AssertionError.class)
  public void addView_whenViewIsNull_shouldThrowNPE() throws Exception {
    subject.addView(null, MIN_PERCENTAGE_VIEWED);

    assertThat(trackedViews).isEmpty();
  }
  @Test
  public void addView_withVisibleView_shouldAddVisibleViewToTrackedViews() throws Exception {
    subject.addView(view, MIN_PERCENTAGE_VIEWED);

    assertThat(trackedViews).hasSize(1);
  }