2024-10-01 18:41:44 +00:00

210 lines
8.8 KiB
Java

/*
* Copyright 2022 The Android Open Source Project
*
* 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.google.android.material.carousel;
import com.google.android.material.test.R;
import static com.google.android.material.carousel.CarouselHelper.createDataSetWithSize;
import static com.google.android.material.carousel.CarouselHelper.scrollHorizontallyBy;
import static com.google.android.material.carousel.CarouselHelper.scrollToPosition;
import static com.google.android.material.carousel.CarouselHelper.setAdapterItems;
import static com.google.android.material.carousel.CarouselHelper.setViewSize;
import static com.google.android.material.testing.RtlTestUtils.applyRtlPseudoLocale;
import static com.google.android.material.testing.RtlTestUtils.checkAppSupportsRtl;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import com.google.android.material.carousel.CarouselHelper.CarouselTestAdapter;
import com.google.android.material.carousel.CarouselHelper.WrappedCarouselLayoutManager;
import com.google.android.material.carousel.CarouselStrategy.StrategyType;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
/** RTL tests for {@link CarouselLayoutManager}. */
@RunWith(RobolectricTestRunner.class)
@org.junit.Ignore("(b/265311943) Fix RTL support for Robolectric tests.")
public class CarouselLayoutManagerRtlTest {
private static final int DEFAULT_RECYCLER_VIEW_WIDTH = 1320;
private static final int DEFAULT_RECYCLER_VIEW_HEIGHT = 200;
private static final int DEFAULT_ITEM_WIDTH = 450;
private static final int DEFAULT_ITEM_HEIGHT = 200;
private final Context context = ApplicationProvider.getApplicationContext();
RecyclerView recyclerView;
WrappedCarouselLayoutManager layoutManager;
CarouselTestAdapter adapter;
@Before
public void setUp() {
checkAppSupportsRtl();
applyRtlPseudoLocale();
createAndSetFixtures(DEFAULT_RECYCLER_VIEW_WIDTH, DEFAULT_ITEM_WIDTH);
layoutManager.setCarouselStrategy(
new CarouselStrategy() {
@Override
public KeylineState onFirstChildMeasuredWithMargins(
@NonNull Carousel carousel, @NonNull View child) {
return getTestCenteredKeylineState();
}
});
}
@Test
public void testFirstAdapterItem_isDrawnAtRightOfContainer() throws Throwable {
setAdapterItems(recyclerView, layoutManager, adapter, createDataSetWithSize(10));
MaskableFrameLayout firstChild = (MaskableFrameLayout) recyclerView.getChildAt(0);
assertThat(recyclerView.getChildAdapterPosition(firstChild)).isEqualTo(0);
assertThat(firstChild.getRight()).isEqualTo(DEFAULT_RECYCLER_VIEW_WIDTH);
}
@Config(qualifiers = "sw1320dp-w1320dp")
@Test
public void testScrollBeyondMaxHorizontalScroll_shouldLimitToMaxScrollOffset() throws Throwable {
KeylineState keylineState = getTestCenteredKeylineState();
setAdapterItems(recyclerView, layoutManager, adapter, createDataSetWithSize(10));
scrollToPosition(recyclerView, layoutManager, 200);
KeylineState leftState =
KeylineStateList.from(
layoutManager, KeylineState.reverse(keylineState, DEFAULT_RECYCLER_VIEW_WIDTH),
0, 0, 0, StrategyType.CONTAINED)
.getStartState();
MaskableFrameLayout child =
(MaskableFrameLayout) recyclerView.getChildAt(recyclerView.getChildCount() - 1);
float childCenterX = child.getLeft() + (child.getWidth() / 2F);
assertThat(childCenterX).isEqualTo(leftState.getFirstFocalKeyline().locOffset);
}
@Test
public void testSingleItem_shouldBeInFocalRange() throws Throwable {
setAdapterItems(recyclerView, layoutManager, adapter, CarouselHelper.createDataSetWithSize(1));
RectF maskRect = ((Maskable) recyclerView.getChildAt(0)).getMaskRectF();
assertThat((int) (maskRect.right - maskRect.left)).isEqualTo(DEFAULT_ITEM_WIDTH);
}
@Test
public void testSingleItem_shouldNotScrollLeft() throws Throwable {
setAdapterItems(recyclerView, layoutManager, adapter, CarouselHelper.createDataSetWithSize(1));
scrollHorizontallyBy(recyclerView, layoutManager, 100);
assertThat(recyclerView.getChildAt(0).getRight()).isEqualTo(DEFAULT_RECYCLER_VIEW_WIDTH);
}
@Test
public void testSingleItem_shouldNotScrollRight() throws Throwable {
setAdapterItems(recyclerView, layoutManager, adapter, CarouselHelper.createDataSetWithSize(1));
scrollHorizontallyBy(recyclerView, layoutManager, -100);
assertThat(recyclerView.getChildAt(0).getRight()).isEqualTo(DEFAULT_RECYCLER_VIEW_WIDTH);
}
@Test
public void testChangeAdapterItemCount_shouldAlignFirstItemToStart() throws Throwable {
setAdapterItems(
recyclerView, layoutManager, adapter, CarouselHelper.createDataSetWithSize(200));
scrollToPosition(recyclerView, layoutManager, 100);
setAdapterItems(recyclerView, layoutManager, adapter, createDataSetWithSize(1));
assertThat(recyclerView.getChildCount()).isEqualTo(1);
assertThat(recyclerView.getChildAt(0).getRight()).isEqualTo(DEFAULT_RECYCLER_VIEW_WIDTH);
}
@Test
public void testScrollToEndThenToStart_childrenHaveValidOrder() throws Throwable {
// TODO(b/271293808): Refactor to use parameterized tests.
setAdapterItems(recyclerView, layoutManager, adapter, CarouselHelper.createDataSetWithSize(10));
scrollToPosition(recyclerView, layoutManager, 9);
scrollToPosition(recyclerView, layoutManager, 2);
CarouselHelper.assertChildrenHaveValidOrder(layoutManager);
}
@Test
public void testRequestChildRectangleOnScreen_doesntScrollIfChildIsFocal() throws Throwable {
setAdapterItems(recyclerView, layoutManager, adapter, createDataSetWithSize(10));
assertThat(layoutManager.scrollOffset).isEqualTo(0);
// Bring second child into focus
layoutManager.requestChildRectangleOnScreen(
recyclerView, recyclerView.getChildAt(1), new Rect(), /* immediate= */ true);
// Test Keyline state has 2 focal keylines at the start; default item with is 450 and
// focal keyline size is 450, so the scroll offset should be 0.
assertThat(layoutManager.scrollOffset).isEqualTo(0);
}
/**
* Assigns explicit sizes to fixtures being used to construct the testing environment.
*
* @param recyclerWidth The width of the recycler view being used
* @param itemWidth The width each item added to the recycler view would like to be laid out with.
* What would be defined in xml as {@code android:layout_width}.
*/
private void createAndSetFixtures(int recyclerWidth, int itemWidth) {
ApplicationProvider.getApplicationContext().setTheme(R.style.Theme_Material3_Light);
AppCompatActivity activity = Robolectric.buildActivity(AppCompatActivity.class).setup().get();
recyclerView = new RecyclerView(context);
activity.addContentView(
recyclerView, new LayoutParams(recyclerWidth, DEFAULT_RECYCLER_VIEW_HEIGHT));
setViewSize(recyclerView, recyclerWidth, DEFAULT_RECYCLER_VIEW_HEIGHT);
layoutManager = new WrappedCarouselLayoutManager();
adapter = new CarouselTestAdapter(itemWidth, DEFAULT_ITEM_HEIGHT);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
}
private static KeylineState getTestCenteredKeylineState() {
float smallSize = 56F;
float extraSmallSize = 10F;
float largeSize = 450F;
float mediumSize = 88F;
float extraSmallMask = 1F - (extraSmallSize / largeSize);
float smallMask = 1F - (smallSize / largeSize);
float mediumMask = 1F - (mediumSize / largeSize);
return new KeylineState.Builder(450F, DEFAULT_RECYCLER_VIEW_WIDTH)
.addKeyline(5F, extraSmallMask, extraSmallSize)
.addKeylineRange(38F, smallMask, smallSize, 2)
.addKeyline(166F, mediumMask, mediumSize)
.addKeylineRange(435F, 0F, largeSize, 2, true)
.addKeyline(1154F, mediumMask, mediumSize)
.addKeylineRange(1226F, smallMask, smallSize, 2)
.addKeyline(1315F, extraSmallMask, extraSmallSize)
.build();
}
}