// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.toolbar;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.animation.Animator;
import android.content.res.Resources;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.ColorInt;
import androidx.test.core.app.ActivityScenario;
import androidx.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.shadows.ShadowLooper;
import org.chromium.base.MathUtils;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
import org.chromium.components.browser_ui.styles.SemanticColorUtils;
import org.chromium.components.browser_ui.widget.ClipDrawableProgressBar.ProgressBarObserver;
import org.chromium.ui.base.TestActivity;
import java.util.concurrent.TimeoutException;
/** Tests related to the ToolbarProgressBar. */
@RunWith(BaseRobolectricTestRunner.class)
public class ToolbarProgressBarTest {
@Mock ProgressBarObserver mMockProgressBarObserver;
private ToolbarProgressBar mProgressBar;
private ShadowLooper mShadowLooper;
private ActivityScenario<TestActivity> mActivityScenario;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mShadowLooper = ShadowLooper.shadowMainLooper();
mActivityScenario =
ActivityScenario.launch(TestActivity.class)
.onActivity(
activity -> {
activity.setTheme(R.style.Theme_BrowserUI_DayNight);
ViewGroup view = new FrameLayout(activity);
view.setVisibility(View.VISIBLE);
FrameLayout.LayoutParams params =
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
activity.setContentView(view, params);
Resources res = activity.getResources();
int heightPx =
res.getDimensionPixelSize(
R.dimen.toolbar_progress_bar_height);
View anchor = new View(activity);
view.addView(
anchor,
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
heightPx * 2));
mProgressBar =
new ToolbarProgressBar(
activity, heightPx, anchor, false);
final @ColorInt int toolbarColor =
SemanticColorUtils.getToolbarBackgroundPrimary(
activity);
mProgressBar.setThemeColor(toolbarColor, false);
mProgressBar.setProgressBarObserver(mMockProgressBarObserver);
view.addView(
mProgressBar,
new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, heightPx));
});
}
@After
public void tearDown() throws Exception {
mActivityScenario.close();
}
/**
* Get the current progress from the UI thread.
*
* @return The current progress displayed by the progress bar.
*/
private float getProgress() {
return mProgressBar.getProgress();
}
/**
* Get the current progress bar visibility from the UI thread.
*
* @return The current progress displayed by the progress bar.
*/
private boolean isProgressBarVisible() {
return mProgressBar.getVisibility() == View.VISIBLE;
}
/** Test that the progress bar indeterminate animation completely traverses the screen. */
@Test
@Feature({"Android-Progress-Bar"})
@SmallTest
public void testProgressBarCompletion_indeterminateAnimation() throws TimeoutException {
Animator progressAnimator = mProgressBar.getIndeterminateAnimatorForTesting();
mProgressBar.start();
assertFalse("Indeterminate animation should not be running.", progressAnimator.isRunning());
// Wait for a visibility change.
mShadowLooper.idle();
verify(mMockProgressBarObserver, times(1)).onVisibilityChanged();
mProgressBar.startIndeterminateAnimationForTesting();
mProgressBar.setProgress(0.5f);
assertTrue("Indeterminate animation should be running.", progressAnimator.isRunning());
// Wait for progress updates to reach 50%.
while (!MathUtils.areFloatsEqual(getProgress(), 0.5f)) {
mShadowLooper.runOneTask();
}
mProgressBar.finish(true);
// Wait for progress updates to reach 100%.
while (!MathUtils.areFloatsEqual(getProgress(), 1.0f)) {
mShadowLooper.runOneTask();
}
// Make sure the progress bar remains visible through completion.
assertTrue("Progress bar should still be visible.", isProgressBarVisible());
assertEquals("Progress should have reached 100%.", 1.0f, getProgress(), MathUtils.EPSILON);
// Wait for a visibility change now that progress has completed.
mShadowLooper.runToEndOfTasks();
verify(mMockProgressBarObserver, times(2)).onVisibilityChanged();
assertFalse("Indeterminate animation should not be running.", progressAnimator.isRunning());
assertFalse("Progress bar should not be visible.", isProgressBarVisible());
}
/** Test that the progress bar completely traverses the screen without animation. */
@Test
@Feature({"Android-Progress-Bar"})
@SmallTest
public void testProgressBarCompletion_noAnimation() throws TimeoutException {
mProgressBar.start();
mProgressBar.setProgress(0.5f);
// Wait for a visibility change.
mShadowLooper.idle();
verify(mMockProgressBarObserver).onVisibilityChanged();
assertTrue("Progress bar should be visible.", isProgressBarVisible());
// Ensure progress updates reached 50%.
verify(mMockProgressBarObserver, times(1)).onVisibleProgressUpdated();
assertEquals("Progress should have reached 50%.", 0.5f, getProgress(), MathUtils.EPSILON);
// Finish progress bar.
mProgressBar.finish(true);
// Ensure progress reached 100%.
verify(mMockProgressBarObserver, times(2)).onVisibleProgressUpdated();
assertEquals("Progress should have reached 100%.", 1.0f, getProgress(), MathUtils.EPSILON);
// Make sure the progress bar remains visible through completion.
assertTrue("Progress bar should still be visible.", isProgressBarVisible());
// Wait for hiding tasks.
mShadowLooper.runToEndOfTasks();
// Ensure that visibility changed now that progress has completed.
assertFalse("Progress bar should not be visible.", isProgressBarVisible());
verify(mMockProgressBarObserver, times(2)).onVisibilityChanged();
}
/** Test that the progress bar ends immediately if #finish(...) is called with delay = false. */
@Test
@Feature({"Android-Progress-Bar"})
@SmallTest
public void testProgressBarCompletion_indeterminateAnimation_noDelay() throws TimeoutException {
Animator progressAnimator = mProgressBar.getIndeterminateAnimatorForTesting();
mProgressBar.start();
assertFalse("Indeterminate animation should not be running.", progressAnimator.isRunning());
// Wait for a visibility change.
mShadowLooper.idle();
verify(mMockProgressBarObserver).onVisibilityChanged();
assertTrue("Progress bar should be visible.", isProgressBarVisible());
mProgressBar.startIndeterminateAnimationForTesting();
mProgressBar.setProgress(0.5f);
assertTrue("Indeterminate animation should be running.", progressAnimator.isRunning());
// Wait for progress updates to reach 50%.
while (!MathUtils.areFloatsEqual(getProgress(), 0.5f)) {
mShadowLooper.runOneTask();
}
// Finish progress with no delay.
mProgressBar.finish(false);
// The progress bar should immediately be invisible.
assertFalse("Progress bar should be invisible.", isProgressBarVisible());
assertFalse("Indeterminate animation should not be running.", progressAnimator.isRunning());
}
/**
* Test that the progress bar resets if a navigation occurs mid-progress while the indeterminate
* animation is running.
*/
@Test
@Feature({"Android-Progress-Bar"})
@SmallTest
public void testProgressBarReset_indeterminateAnimation() throws TimeoutException {
Animator progressAnimator = mProgressBar.getIndeterminateAnimatorForTesting();
mProgressBar.start();
assertFalse("Indeterminate animation should not be running.", progressAnimator.isRunning());
// Wait for a visibility change.
mShadowLooper.idle();
verify(mMockProgressBarObserver).onVisibilityChanged();
mProgressBar.startIndeterminateAnimationForTesting();
mProgressBar.setProgress(0.5f);
assertTrue("Indeterminate animation should be running.", progressAnimator.isRunning());
// Wait for progress updates to reach 50%.
while (!MathUtils.areFloatsEqual(getProgress(), 0.5f)) {
mShadowLooper.runOneTask();
}
// Restart the progress bar.
mProgressBar.start();
// Cancel the indeterminate animator, it doesn't stop on its own.
progressAnimator.cancel();
// Make sure the progress bar remains visible through completion.
assertTrue("Progress bar should still be visible.", isProgressBarVisible());
assertEquals("Progress should be at 0%.", 0.0f, getProgress(), MathUtils.EPSILON);
}
}