chromium/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/FeedListContentManagerTest.java

// Copyright 2020 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.feed;

import static androidx.test.espresso.matcher.ViewMatchers.assertThat;

import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import android.app.Activity;
import android.content.Context;
import android.view.View;
import android.view.ViewParent;
import android.widget.FrameLayout;
import android.widget.LinearLayout;

import androidx.test.filters.SmallTest;

import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;

import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.xsurface.ListContentManagerObserver;
import org.chromium.chrome.browser.xsurface.LoggingParameters;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/** Unit tests for {@link FeedListContentManager}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class FeedListContentManagerTest implements ListContentManagerObserver {
    private FeedListContentManager mManager;
    private Context mContext;
    private LinearLayout mParent;

    private boolean mItemRangeInserted;
    private int mItemRangeInsertedStartIndex;
    private int mItemRangeInsertedCount;
    private boolean mItemRangeRemoved;
    private int mItemRangeRemovedStartIndex;
    private int mItemRangeRemovedCount;
    private boolean mItemRangeChanged;
    private int mItemRangeChangedStartIndex;
    private int mItemRangeChangedCount;
    private boolean mItemMoved;
    private int mItemMovedCurIndex;
    private int mItemMovedNewIndex;
    private String mObservedChanges = "";
    private final FeedLoggingParameters mLoggingParametersA =
            new FeedLoggingParameters(
                    "instance-id",
                    "A",
                    /* loggingEnabled= */ true,
                    /* viewActionsEnabled= */ true,
                    null);
    private final FeedLoggingParameters mLoggingParametersB =
            new FeedLoggingParameters(
                    "instance-id",
                    "B",
                    /* loggingEnabled= */ true,
                    /* viewActionsEnabled= */ true,
                    null);

    @Before
    public void setUp() {
        mContext = Robolectric.buildActivity(Activity.class).get();
        mParent = new LinearLayout(mContext);
        mManager = new FeedListContentManager();
        mManager.addObserver(this);
    }

    @Test
    @SmallTest
    public void testFindContentPositionByKey() {
        FeedListContentManager.FeedContent c1 = createExternalViewContent("a");
        FeedListContentManager.FeedContent c2 = createExternalViewContent("b");
        FeedListContentManager.FeedContent c3 = createExternalViewContent("c");

        addContents(0, Arrays.asList(new FeedListContentManager.FeedContent[] {c1, c2, c3}));
        assertEquals(3, mManager.getItemCount());

        assertEquals(0, mManager.findContentPositionByKey("a"));
        assertEquals(1, mManager.findContentPositionByKey("b"));
        assertEquals(2, mManager.findContentPositionByKey("c"));
    }

    @Test
    @SmallTest
    public void testAddContents() {
        FeedListContentManager.FeedContent c1 = createExternalViewContent("a");
        FeedListContentManager.FeedContent c2 = createExternalViewContent("b");
        FeedListContentManager.FeedContent c3 = createExternalViewContent("c");

        addContents(0, Arrays.asList(new FeedListContentManager.FeedContent[] {c1}));
        assertEquals(1, mManager.getItemCount());
        assertEquals(c1, mManager.getContent(0));

        addContents(0, Arrays.asList(new FeedListContentManager.FeedContent[] {c2, c3}));
        assertEquals(3, mManager.getItemCount());
        assertEquals(c2, mManager.getContent(0));
        assertEquals(c3, mManager.getContent(1));
        assertEquals(c1, mManager.getContent(2));

        addContents(3, Arrays.asList(new FeedListContentManager.FeedContent[] {c2, c3}));
        assertEquals(5, mManager.getItemCount());
        assertEquals(c2, mManager.getContent(0));
        assertEquals(c3, mManager.getContent(1));
        assertEquals(c1, mManager.getContent(2));
        assertEquals(c2, mManager.getContent(3));
        assertEquals(c3, mManager.getContent(4));
    }

    @Test
    @SmallTest
    public void testRemoveContents() {
        FeedListContentManager.FeedContent c1 = createExternalViewContent("a");
        FeedListContentManager.FeedContent c2 = createExternalViewContent("b");
        FeedListContentManager.FeedContent c3 = createExternalViewContent("c");
        FeedListContentManager.FeedContent c4 = createExternalViewContent("d");
        FeedListContentManager.FeedContent c5 = createExternalViewContent("e");

        addContents(
                0, Arrays.asList(new FeedListContentManager.FeedContent[] {c1, c2, c3, c4, c5}));
        assertEquals(5, mManager.getItemCount());

        removeContents(0, 2);
        assertEquals(3, mManager.getItemCount());
        assertEquals(c3, mManager.getContent(0));
        assertEquals(c4, mManager.getContent(1));
        assertEquals(c5, mManager.getContent(2));

        removeContents(1, 2);
        assertEquals(1, mManager.getItemCount());
        assertEquals(c3, mManager.getContent(0));

        removeContents(0, 1);
        assertEquals(0, mManager.getItemCount());
    }

    @Test
    @SmallTest
    public void testUpdateContents() {
        FeedListContentManager.FeedContent c1 = createExternalViewContent("a");
        FeedListContentManager.FeedContent c2 = createExternalViewContent("b");
        FeedListContentManager.FeedContent c3 = createExternalViewContent("c");
        FeedListContentManager.FeedContent c4 = createExternalViewContent("d");
        FeedListContentManager.FeedContent c5 = createExternalViewContent("e");

        addContents(0, Arrays.asList(new FeedListContentManager.FeedContent[] {c1, c2, c3}));
        assertEquals(3, mManager.getItemCount());

        updateContents(1, Arrays.asList(new FeedListContentManager.FeedContent[] {c4, c5}));
        assertEquals(3, mManager.getItemCount());
        assertEquals(c1, mManager.getContent(0));
        assertEquals(c4, mManager.getContent(1));
        assertEquals(c5, mManager.getContent(2));
    }

    @Test
    @SmallTest
    public void testMoveContent() {
        FeedListContentManager.FeedContent c1 = createExternalViewContent("a");
        FeedListContentManager.FeedContent c2 = createExternalViewContent("b");
        FeedListContentManager.FeedContent c3 = createExternalViewContent("c");
        FeedListContentManager.FeedContent c4 = createExternalViewContent("d");
        FeedListContentManager.FeedContent c5 = createExternalViewContent("e");

        addContents(
                0, Arrays.asList(new FeedListContentManager.FeedContent[] {c1, c2, c3, c4, c5}));
        assertEquals(5, mManager.getItemCount());

        moveContent(0, 3);
        assertEquals(5, mManager.getItemCount());
        assertEquals(c2, mManager.getContent(0));
        assertEquals(c3, mManager.getContent(1));
        assertEquals(c4, mManager.getContent(2));
        assertEquals(c1, mManager.getContent(3));
        assertEquals(c5, mManager.getContent(4));

        moveContent(4, 2);
        assertEquals(5, mManager.getItemCount());
        assertEquals(c2, mManager.getContent(0));
        assertEquals(c3, mManager.getContent(1));
        assertEquals(c5, mManager.getContent(2));
        assertEquals(c4, mManager.getContent(3));
        assertEquals(c1, mManager.getContent(4));
    }

    @Test
    @SmallTest
    public void testGetViewData() {
        FeedListContentManager.FeedContent c1 = createExternalViewContent("foo");
        View v2 = new View(mContext);
        FeedListContentManager.FeedContent c2 = createNativeViewContent(v2);
        View v3 = new View(mContext);
        FeedListContentManager.FeedContent c3 = createNativeViewContent(v3);
        FeedListContentManager.FeedContent c4 = createExternalViewContent("hello");
        View v5 = new View(mContext);
        FeedListContentManager.FeedContent c5 = createNativeViewContent(v5);

        addContents(
                0, Arrays.asList(new FeedListContentManager.FeedContent[] {c1, c2, c3, c4, c5}));
        assertEquals(5, mManager.getItemCount());

        assertFalse(mManager.isNativeView(0));
        assertTrue(mManager.isNativeView(1));
        assertTrue(mManager.isNativeView(2));
        assertFalse(mManager.isNativeView(3));
        assertTrue(mManager.isNativeView(4));

        assertFalse(mManager.isFullSpan(0));
        assertTrue(mManager.isFullSpan(1));
        assertTrue(mManager.isFullSpan(2));
        assertFalse(mManager.isFullSpan(3));
        assertTrue(mManager.isFullSpan(4));

        assertArrayEquals("foo".getBytes(), mManager.getExternalViewBytes(0));
        assertEquals(v2, getNativeView(mManager.getViewType(1)));
        assertEquals(v3, getNativeView(mManager.getViewType(2)));
        assertArrayEquals("hello".getBytes(), mManager.getExternalViewBytes(3));
        assertEquals(v5, getNativeView(mManager.getViewType(4)));
    }

    @Test
    @SmallTest
    public void testGetViewDataCreatesEnclosingViewOnce() {
        View v = new View(mContext);
        FeedListContentManager.FeedContent c = createNativeViewContent(v);

        addContents(0, Arrays.asList(new FeedListContentManager.FeedContent[] {c}));

        assertEquals(v, getNativeView(mManager.getViewType(0)));
        ViewParent p = v.getParent();

        // Remove and re-add the same view, but with a new NativeViewContent.
        // This time, getNativeView() creates a new enclosing parent view.
        removeContents(0, 1);
        c = createNativeViewContent(v);
        addContents(0, Arrays.asList(new FeedListContentManager.FeedContent[] {c}));
        assertEquals(v, getNativeView(mManager.getViewType(0)));
        assertNotEquals(p, v.getParent());
    }

    @Test
    @SmallTest
    public void testGetContextValuesReturnsLoggingParameters() {
        addContents(
                0,
                Arrays.asList(
                        new FeedListContentManager.FeedContent[] {
                            createExternalViewContent("A", mLoggingParametersA),
                            createExternalViewContent("B", mLoggingParametersB)
                        }));

        LoggingParameters parameters1 =
                (LoggingParameters) mManager.getContextValues(0).get(LoggingParameters.KEY);
        LoggingParameters parameters2 =
                (LoggingParameters) mManager.getContextValues(1).get(LoggingParameters.KEY);
        assertEquals(parameters1, mLoggingParametersA);
        assertEquals(parameters2, mLoggingParametersB);
    }

    @Test
    @SmallTest
    public void testGetContextValues_SetHandlersAfterAddingContent() {
        addContents(
                0,
                Arrays.asList(
                        new FeedListContentManager.FeedContent[] {
                            createExternalViewContent("A", mLoggingParametersA)
                        }));
        mManager.setHandlers(Map.of("HKEY1", "someHandler"));

        assertEquals(
                Map.of("HKEY1", "someHandler", LoggingParameters.KEY, mLoggingParametersA),
                mManager.getContextValues(0));
    }

    @Test
    @SmallTest
    public void testGetContextValues_SetHandlersBeforeAddingContent() {
        mManager.setHandlers(Map.of("HKEY1", "someHandler"));
        addContents(
                0,
                Arrays.asList(
                        new FeedListContentManager.FeedContent[] {
                            createExternalViewContent("A", mLoggingParametersA)
                        }));

        assertEquals(
                Map.of("HKEY1", "someHandler", LoggingParameters.KEY, mLoggingParametersA),
                mManager.getContextValues(0));
    }

    @Test
    @SmallTest
    public void testGetNativeViewAfterMove() {
        View v1 = new View(mContext);
        FeedListContentManager.FeedContent c1 = createNativeViewContent(v1);
        View v0 = new View(mContext);
        FeedListContentManager.FeedContent c0 = createNativeViewContent(v0);

        addContents(0, Arrays.asList(new FeedListContentManager.FeedContent[] {c1}));
        int t1 = mManager.getViewType(0);
        addContents(0, Arrays.asList(new FeedListContentManager.FeedContent[] {c0}));
        int t0 = mManager.getViewType(0);

        assertEquals(1, t1);
        assertEquals(2, t0);

        assertEquals(t0, mManager.getViewType(0));
        assertEquals(t1, mManager.getViewType(1));
        assertEquals(v0, getNativeView(t0));
        assertEquals(v1, getNativeView(t1));
    }

    @Test
    @SmallTest
    public void testReplaceRange_Empty() {
        boolean changed =
                mManager.replaceRange(
                        0, 0, Arrays.asList(new FeedListContentManager.FeedContent[] {}));

        assertThat(getContentKeys(), Matchers.empty());
        assertFalse(changed);
        assertEquals("", mObservedChanges);
    }

    @Test
    @SmallTest
    public void testReplaceRange_twoWhileEmpty() {
        FeedListContentManager.FeedContent c1 = createExternalViewContent("a");
        FeedListContentManager.FeedContent c2 = createExternalViewContent("b");
        boolean changed =
                mManager.replaceRange(
                        0, 0, Arrays.asList(new FeedListContentManager.FeedContent[] {c1, c2}));

        assertThat(getContentKeys(), contains("a", "b"));
        assertTrue(changed);
        assertEquals("rangeInserted index=0 count=2", mObservedChanges);
    }

    @Test
    @SmallTest
    public void testReplaceRange_twoInMiddle() {
        FeedListContentManager.FeedContent c1 = createExternalViewContent("a");
        FeedListContentManager.FeedContent c2 = createExternalViewContent("b");
        FeedListContentManager.FeedContent c3 = createExternalViewContent("c");
        FeedListContentManager.FeedContent c4 = createExternalViewContent("d");
        FeedListContentManager.FeedContent c5 = createExternalViewContent("e");
        mManager.replaceRange(
                0, 0, Arrays.asList(new FeedListContentManager.FeedContent[] {c1, c2, c3}));
        mObservedChanges = "";

        boolean changed =
                mManager.replaceRange(
                        1, 1, Arrays.asList(new FeedListContentManager.FeedContent[] {c4, c5}));

        assertThat(getContentKeys(), contains("a", "d", "e", "c"));
        assertTrue(changed);
        assertEquals(
                "rangeRemoved index=1 count=1" + "\nrangeInserted index=1 count=2",
                mObservedChanges);
    }

    @Test
    @SmallTest
    public void testReplaceRange_twoAtEnd() {
        FeedListContentManager.FeedContent c1 = createExternalViewContent("a");
        FeedListContentManager.FeedContent c2 = createExternalViewContent("b");
        FeedListContentManager.FeedContent c3 = createExternalViewContent("c");
        FeedListContentManager.FeedContent c4 = createExternalViewContent("d");
        FeedListContentManager.FeedContent c5 = createExternalViewContent("e");
        mManager.replaceRange(
                0, 0, Arrays.asList(new FeedListContentManager.FeedContent[] {c1, c2, c3}));
        mObservedChanges = "";

        boolean changed =
                mManager.replaceRange(
                        2, 1, Arrays.asList(new FeedListContentManager.FeedContent[] {c4, c5}));

        assertThat(getContentKeys(), contains("a", "b", "d", "e"));
        assertTrue(changed);
        assertEquals(
                "rangeRemoved index=2 count=1" + "\nrangeInserted index=2 count=2",
                mObservedChanges);
    }

    @Test
    @SmallTest
    public void testReplaceRange_twoAtStart() {
        FeedListContentManager.FeedContent c1 = createExternalViewContent("a");
        FeedListContentManager.FeedContent c2 = createExternalViewContent("b");
        FeedListContentManager.FeedContent c3 = createExternalViewContent("c");
        FeedListContentManager.FeedContent c4 = createExternalViewContent("d");
        FeedListContentManager.FeedContent c5 = createExternalViewContent("e");
        mManager.replaceRange(
                0, 0, Arrays.asList(new FeedListContentManager.FeedContent[] {c1, c2, c3}));
        mObservedChanges = "";

        boolean changed =
                mManager.replaceRange(
                        0, 1, Arrays.asList(new FeedListContentManager.FeedContent[] {c4, c5}));

        assertThat(getContentKeys(), contains("d", "e", "b", "c"));
        assertTrue(changed);
        assertEquals(
                "rangeRemoved index=0 count=1" + "\nrangeInserted index=0 count=2",
                mObservedChanges);
    }

    @Test
    @SmallTest
    public void testReplaceRange_moveFirstToLast() {
        FeedListContentManager.FeedContent c1 = createExternalViewContent("a");
        FeedListContentManager.FeedContent c2 = createExternalViewContent("b");
        FeedListContentManager.FeedContent c3 = createExternalViewContent("c");
        mManager.replaceRange(
                0, 0, Arrays.asList(new FeedListContentManager.FeedContent[] {c1, c2, c3}));
        mObservedChanges = "";

        boolean changed =
                mManager.replaceRange(
                        0, 3, Arrays.asList(new FeedListContentManager.FeedContent[] {c2, c3, c1}));

        assertTrue(changed);
        assertThat(getContentKeys(), contains("b", "c", "a"));
        assertEquals("itemMoved from=1 to=0\nitemMoved from=2 to=1", mObservedChanges);
    }

    @Test
    @SmallTest
    public void testReplaceRange_reverseOrder() {
        FeedListContentManager.FeedContent c1 = createExternalViewContent("a");
        FeedListContentManager.FeedContent c2 = createExternalViewContent("b");
        FeedListContentManager.FeedContent c3 = createExternalViewContent("c");
        mManager.replaceRange(
                0, 0, Arrays.asList(new FeedListContentManager.FeedContent[] {c1, c2, c3}));
        mObservedChanges = "";

        boolean changed =
                mManager.replaceRange(
                        0, 3, Arrays.asList(new FeedListContentManager.FeedContent[] {c3, c2, c1}));

        assertTrue(changed);
        assertThat(getContentKeys(), contains("c", "b", "a"));
        assertEquals("itemMoved from=2 to=0\nitemMoved from=2 to=1", mObservedChanges);
    }

    @Test
    @SmallTest
    public void testReplaceRange_removeAll() {
        FeedListContentManager.FeedContent c1 = createExternalViewContent("a");
        FeedListContentManager.FeedContent c2 = createExternalViewContent("b");
        FeedListContentManager.FeedContent c3 = createExternalViewContent("c");
        mManager.replaceRange(
                0, 0, Arrays.asList(new FeedListContentManager.FeedContent[] {c1, c2, c3}));
        mObservedChanges = "";

        boolean changed =
                mManager.replaceRange(
                        0, 3, Arrays.asList(new FeedListContentManager.FeedContent[] {}));

        assertTrue(changed);
        assertThat(getContentKeys(), Matchers.empty());
        assertEquals("rangeRemoved index=0 count=3", mObservedChanges);
    }

    @Test
    @SmallTest
    public void testReplaceRange_complexUpdate() {
        FeedListContentManager.FeedContent a = createExternalViewContent("a");
        FeedListContentManager.FeedContent b = createExternalViewContent("b");
        FeedListContentManager.FeedContent c = createExternalViewContent("c");
        FeedListContentManager.FeedContent d = createExternalViewContent("d");
        FeedListContentManager.FeedContent e = createExternalViewContent("e");
        FeedListContentManager.FeedContent f = createExternalViewContent("f");
        FeedListContentManager.FeedContent g = createExternalViewContent("g");
        FeedListContentManager.FeedContent h = createExternalViewContent("h");
        FeedListContentManager.FeedContent i = createExternalViewContent("i");
        mManager.replaceRange(
                0, 0, Arrays.asList(new FeedListContentManager.FeedContent[] {a, b, c, d, e}));
        mObservedChanges = "";

        boolean changed =
                mManager.replaceRange(
                        0,
                        5,
                        Arrays.asList(
                                new FeedListContentManager.FeedContent[] {f, g, a, h, c, e, i}));

        assertTrue(changed);
        assertThat(getContentKeys(), contains("f", "g", "a", "h", "c", "e", "i"));
        assertEquals(
                "rangeRemoved index=3 count=1"
                        + "\nrangeRemoved index=1 count=1"
                        + "\nrangeInserted index=0 count=2"
                        + "\nrangeInserted index=3 count=1"
                        + "\nrangeInserted index=6 count=1",
                mObservedChanges);
    }

    @Override
    public void onItemRangeInserted(int startIndex, int count) {
        logChange(
                "rangeInserted index="
                        + String.valueOf(startIndex)
                        + " count="
                        + String.valueOf(count));
        mItemRangeInserted = true;
        mItemRangeInsertedStartIndex = startIndex;
        mItemRangeInsertedCount = count;
    }

    @Override
    public void onItemRangeRemoved(int startIndex, int count) {
        logChange(
                "rangeRemoved index="
                        + String.valueOf(startIndex)
                        + " count="
                        + String.valueOf(count));
        mItemRangeRemoved = true;
        mItemRangeRemovedStartIndex = startIndex;
        mItemRangeRemovedCount = count;
    }

    @Override
    public void onItemRangeChanged(int startIndex, int count) {
        logChange(
                "rangeChanged index="
                        + String.valueOf(startIndex)
                        + " count="
                        + String.valueOf(count));
        mItemRangeChanged = true;
        mItemRangeChangedStartIndex = startIndex;
        mItemRangeChangedCount = count;
    }

    @Override
    public void onItemMoved(int curIndex, int newIndex) {
        logChange("itemMoved from=" + String.valueOf(curIndex) + " to=" + String.valueOf(newIndex));
        mItemMoved = true;
        mItemMovedCurIndex = curIndex;
        mItemMovedNewIndex = newIndex;
    }

    private void addContents(int index, List<FeedListContentManager.FeedContent> contents) {
        mItemRangeInserted = false;
        mItemRangeInsertedStartIndex = -1;
        mItemRangeInsertedCount = -1;
        mManager.addContents(index, contents);
        assertTrue(mItemRangeInserted);
        assertEquals(index, mItemRangeInsertedStartIndex);
        assertEquals(contents.size(), mItemRangeInsertedCount);
    }

    private void removeContents(int index, int count) {
        mItemRangeRemoved = false;
        mItemRangeRemovedStartIndex = -1;
        mItemRangeRemovedCount = -1;
        mManager.removeContents(index, count);
        assertTrue(mItemRangeRemoved);
        assertEquals(index, mItemRangeRemovedStartIndex);
        assertEquals(count, mItemRangeRemovedCount);
    }

    private void updateContents(int index, List<FeedListContentManager.FeedContent> contents) {
        mItemRangeChanged = false;
        mItemRangeChangedStartIndex = -1;
        mItemRangeChangedCount = -1;
        mManager.updateContents(index, contents);
        assertTrue(mItemRangeChanged);
        assertEquals(index, mItemRangeChangedStartIndex);
        assertEquals(contents.size(), mItemRangeChangedCount);
    }

    private void moveContent(int curIndex, int newIndex) {
        mItemMoved = false;
        mItemMovedCurIndex = -1;
        mItemMovedNewIndex = -1;
        mManager.moveContent(curIndex, newIndex);
        assertTrue(mItemMoved);
        assertEquals(curIndex, mItemMovedCurIndex);
        assertEquals(newIndex, mItemMovedNewIndex);
    }

    private void logChange(String change) {
        if (!mObservedChanges.isEmpty()) {
            mObservedChanges += "\n";
        }
        mObservedChanges += change;
    }

    private FeedListContentManager.FeedContent createExternalViewContent(String s) {
        return createExternalViewContent(s, mLoggingParametersA);
    }

    private FeedListContentManager.FeedContent createExternalViewContent(
            String s, FeedLoggingParameters loggingParameters) {
        return new FeedListContentManager.ExternalViewContent(s, s.getBytes(), loggingParameters);
    }

    private FeedListContentManager.FeedContent createNativeViewContent(View v) {
        return new FeedListContentManager.NativeViewContent(123, v.toString(), v);
    }

    private View getNativeView(int viewType) {
        View view = mManager.getNativeView(viewType, mParent);
        assertNotNull(view);
        assertTrue(view instanceof FrameLayout);
        return ((FrameLayout) view).getChildAt(0);
    }

    private List<String> getContentKeys() {
        List<String> result = new ArrayList<>();
        for (FeedListContentManager.FeedContent content : mManager.getContentList()) {
            result.add(content.getKey());
        }
        return result;
    }
}