chromium/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/dragreorder/DragReorderableRecyclerViewAdapterTest.java

// Copyright 2023 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.components.browser_ui.widget.dragreorder;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.IntDef;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.filters.SmallTest;

import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Batch;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.browser_ui.widget.dragreorder.DragReorderableRecyclerViewAdapter.DragBinder;
import org.chromium.components.browser_ui.widget.dragreorder.DragReorderableRecyclerViewAdapter.DraggabilityProvider;
import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.MVCListAdapter.ViewBuilder;
import org.chromium.ui.modelutil.ModelListAdapter;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModel.WritableIntPropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor.ViewBinder;
import org.chromium.ui.test.util.BlankUiTestActivityTestCase;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/** Tests to ensure/validate {@link DragReorderableRecyclerViewAdapter} behavior. */
@RunWith(ChromeJUnit4ClassRunner.class)
@Batch(Batch.PER_CLASS)
public class DragReorderableRecyclerViewAdapterTest extends BlankUiTestActivityTestCase {
    static final WritableObjectPropertyKey<String> TITLE = new WritableObjectPropertyKey<>();
    static final WritableIntPropertyKey TYPE = new WritableIntPropertyKey();
    static final PropertyKey[] ALL_KEYS = {TITLE, TYPE};

    @IntDef({Type.NORMAL, Type.DRAGGABLE, Type.PASSIVELY_DRAGGABLE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface Type {
        int NORMAL = 0;
        int DRAGGABLE = 1;
        int PASSIVELY_DRAGGABLE = 2;
    }

    private ModelList mModelList;
    private DragReorderableRecyclerViewAdapter mAdapter;
    private RecyclerView mRecyclerView;

    @Override
    public void setUpTest() throws Exception {
        super.setUpTest();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mRecyclerView = new RecyclerView(getActivity());
                    RecyclerView.LayoutParams params =
                            new RecyclerView.LayoutParams(
                                    RecyclerView.LayoutParams.MATCH_PARENT,
                                    RecyclerView.LayoutParams.MATCH_PARENT);
                    mRecyclerView.setLayoutParams(params);
                    mRecyclerView.setLayoutManager(
                            new LinearLayoutManager(
                                    mRecyclerView.getContext(),
                                    LinearLayoutManager.VERTICAL,
                                    false));
                    mRecyclerView.setAdapter(createAdapter());
                    getActivity().setContentView(mRecyclerView);

                    mAdapter.enableDrag();
                });
    }

    @Override
    public void tearDownTest() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mModelList.clear();
                });
    }

    private DragReorderableRecyclerViewAdapter createAdapter() {
        mModelList = new ModelList();
        mAdapter = new DragReorderableRecyclerViewAdapter(getActivity(), mModelList);

        ViewBuilder<View> viewBuilder = (parent) -> createListItemView();
        ViewBinder<PropertyModel, View, PropertyKey> viewBinder =
                (PropertyModel model, View view, PropertyKey key) -> {
                    if (key == TITLE) {
                        TextView tv = (TextView) view;
                        tv.setText(model.get(TITLE));
                    }
                };
        DragBinder dragBinder = (vh, it) -> {};
        DraggabilityProvider draggabilityProvider =
                new DraggabilityProvider() {
                    @Override
                    public boolean isActivelyDraggable(PropertyModel propertyModel) {
                        return propertyModel.get(TYPE) == Type.DRAGGABLE;
                    }

                    @Override
                    public boolean isPassivelyDraggable(PropertyModel propertyModel) {
                        return propertyModel.get(TYPE) == Type.DRAGGABLE
                                || propertyModel.get(TYPE) == Type.PASSIVELY_DRAGGABLE;
                    }
                };

        mAdapter.registerType(Type.NORMAL, viewBuilder, viewBinder);
        mAdapter.registerDraggableType(
                Type.DRAGGABLE, viewBuilder, viewBinder, dragBinder, draggabilityProvider);
        mAdapter.registerDraggableType(
                Type.PASSIVELY_DRAGGABLE,
                viewBuilder,
                viewBinder,
                dragBinder,
                draggabilityProvider);
        mAdapter.enableDrag();
        mAdapter.setLongPressDragDelegate(this::isLongPressDragEnabled);

        return mAdapter;
    }

    private View createListItemView() {
        TextView tv = new TextView(getActivity());
        tv.setLayoutParams(
                new ViewGroup.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        return tv;
    }

    private boolean isLongPressDragEnabled() {
        return false;
    }

    private PropertyModel createPropertyModel(String title, @Type int type) {
        return new PropertyModel.Builder(ALL_KEYS).with(TITLE, title).with(TYPE, type).build();
    }

    private ListItem buildListItem(String title, @Type int type) {
        return new ModelListAdapter.ListItem(type, createPropertyModel(title, type));
    }

    @Test
    @SmallTest
    public void testDrag_cannotDragOverNormal() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mModelList.add(buildListItem("normal_1", Type.NORMAL));
                    mModelList.add(buildListItem("draggable_1", Type.DRAGGABLE));
                });

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mAdapter.simulateDragForTests(1, 0);
                });

        // The order should remain since you can't drag over a normal view.
        assertThat(mModelList.get(0).type, is(Type.NORMAL));
        assertThat(mModelList.get(1).type, is(Type.DRAGGABLE));
    }

    @Test
    @SmallTest
    public void testDrag_normalOverNormal() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mModelList.add(buildListItem("draggable_1", Type.DRAGGABLE));
                    mModelList.add(buildListItem("draggable_2", Type.DRAGGABLE));
                });

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mAdapter.simulateDragForTests(1, 0);
                });

        // The order should remain since you can't drag over a normal view.
        assertThat(mModelList.get(0).model.get(TITLE), is("draggable_2"));
        assertThat(mModelList.get(1).model.get(TITLE), is("draggable_1"));
    }

    @Test
    @SmallTest
    public void testDrag_normalThroughPassivelyDraggable() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mModelList.add(buildListItem("draggable_1", Type.DRAGGABLE));
                    mModelList.add(
                            buildListItem("passively_draggable_1", Type.PASSIVELY_DRAGGABLE));
                    mModelList.add(buildListItem("draggable_2", Type.DRAGGABLE));
                });

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mAdapter.simulateDragForTests(0, 1);
                });

        assertThat(mModelList.get(0).model.get(TITLE), is("passively_draggable_1"));
        assertThat(mModelList.get(1).model.get(TITLE), is("draggable_1"));
    }
}