chromium/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchRelatedSearchesTest.java

// Copyright 2022 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.contextualsearch;

import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;

import androidx.test.filters.SmallTest;

import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.layouts.animation.CompositorAnimationHandler;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.browser_ui.widget.chips.ChipProperties;

import java.util.ArrayList;
import java.util.List;

/** Tests the Related Searches Feature of Contextual Search using instrumentation tests. */
@RunWith(ChromeJUnit4ClassRunner.class)
// NOTE: Disable online detection so we we'll default to online on test bots with no network.
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@EnableFeatures(ChromeFeatureList.CONTEXTUAL_SEARCH_DISABLE_ONLINE_DETECTION)
@Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
@Batch(Batch.PER_CLASS)
public class ContextualSearchRelatedSearchesTest extends ContextualSearchInstrumentationBase {
    @Override
    @Before
    public void setUp() throws Exception {
        mTestPage = "/chrome/test/data/android/contextualsearch/tap_test.html";
        super.setUp();
    }

    // --------------------------------------------------------------------------------------------
    // Related Searches Feature tests: base feature enables requests, UI feature allows results.
    // --------------------------------------------------------------------------------------------

    @Test
    @SmallTest
    @Feature({"ContextualSearch"})
    public void testRelatedSearchesInBar() throws Exception {
        ContextualSearchFakeServer.FakeResolveSearch fakeSearch =
                simulateResolveSearch("intelligence");
        ResolvedSearchTerm resolvedSearchTerm = fakeSearch.getResolvedSearchTerm();
        Assert.assertTrue(
                "Related Searches results should have been returned but were not!",
                !resolvedSearchTerm.relatedSearchesJson().isEmpty());
        // Select a chip in the Bar, which should expand the panel.
        final int chipToSelect = 1;
        ThreadUtils.runOnUiThreadBlocking(
                () -> mPanel.getRelatedSearchesInBarControl().selectChipForTest(chipToSelect));
        waitForPanelToExpand();

        // Close the panel
        closePanel();
        // TODO(donnd): Validate UMA metrics once we log in-bar selections.
    }

    /**
     * Tests that the offset of the SERP is unaffected by whether we are showing Related Searches in
     * the Bar or not. See https://crbug.com/1250546.
     *
     * @throws Exception
     */
    @Test
    @SmallTest
    @Feature({"ContextualSearch"})
    public void testRelatedSearchesInBarSerpOffset() throws Exception {
        simulateResolveSearch(SEARCH_NODE);
        float plainSearchBarHeight = mPanel.getBarHeight();
        float plainSearchContentY = mPanel.getContentY();
        closePanel();

        // Bring up a panel with Related Searches in order to expand the Bar
        simulateResolveSearch(RELATED_SEARCHES_NODE);
        // Wait for the animation to start growing the Bar.
        CriteriaHelper.pollUiThread(
                () -> {
                    Criteria.checkThat(
                            mPanel.getInBarRelatedSearchesAnimatedHeightDps(),
                            Matchers.greaterThan(0f));
                });
        // We should have a taller Bar, but that should not affect the Y offset of the content.
        Assert.assertNotEquals(
                "Test code failure - unable to open panels with differing Bar heights!",
                plainSearchBarHeight,
                mPanel.getBarHeight(),
                0.1f);
        Assert.assertEquals(
                "SERP content offsets with and without Related Searches should match!",
                plainSearchContentY,
                mPanel.getContentY(),
                0.1f);
    }

    @Test
    @SmallTest
    @Feature({"ContextualSearch"})
    public void testRelatedSearchesInBarWithDefaultQuery() throws Exception {
        ContextualSearchFakeServer.FakeResolveSearch fakeSearch =
                simulateResolveSearch("intelligence");
        ResolvedSearchTerm resolvedSearchTerm = fakeSearch.getResolvedSearchTerm();
        Assert.assertTrue(
                "Related Searches results should have been returned but were not!",
                !resolvedSearchTerm.relatedSearchesJson().isEmpty());
        // Select a chip in the Bar, which should expand the panel.
        final int chipToSelect = 0;
        ThreadUtils.runOnUiThreadBlocking(
                () -> mPanel.getRelatedSearchesInBarControl().selectChipForTest(chipToSelect));
        waitForPanelToExpand();

        CriteriaHelper.pollUiThread(
                () -> {
                    Criteria.checkThat(
                            mPanel.getSearchBarControl().getSearchTerm(),
                            Matchers.is("Intelligence"));
                });

        // Close the panel
        closePanel();
        // TODO(donnd): Validate UMA metrics once we log in-bar selections.
    }

    @Test
    @SmallTest
    @Feature({"ContextualSearch"})
    public void testRelatedSearchesInBarWithDefaultQuery_HighlightDefaultQuery() throws Exception {
        ContextualSearchFakeServer.FakeResolveSearch fakeSearch =
                simulateResolveSearch("intelligence");
        ResolvedSearchTerm resolvedSearchTerm = fakeSearch.getResolvedSearchTerm();
        Assert.assertTrue(
                "Related Searches results should have been returned but were not!",
                !resolvedSearchTerm.relatedSearchesJson().isEmpty());
        // Select a chip in the Bar, which should expand the panel.
        expandPanelAndAssert();

        CriteriaHelper.pollUiThread(
                () -> {
                    Criteria.checkThat(
                            mPanel.getSearchBarControl().getSearchTerm(),
                            Matchers.is("Intelligence"));
                    Criteria.checkThat(
                            mPanel.getRelatedSearchesInBarControl().getSelectedChipForTest(),
                            Matchers.is(0));
                });

        // Close the panel
        closePanel();
        // TODO(donnd): Validate UMA metrics once we log in-bar selections.
    }

    @Test
    @SmallTest
    @Feature({"ContextualSearch"})
    public void testRelatedSearchesInBarWithDefaultQuery_Ellipsize() throws Exception {
        ContextualSearchFakeServer.FakeResolveSearch fakeSearch =
                simulateResolveSearch("intelligence");
        ResolvedSearchTerm resolvedSearchTerm = fakeSearch.getResolvedSearchTerm();
        Assert.assertTrue(
                "Related Searches results should have been returned but were not!",
                !resolvedSearchTerm.relatedSearchesJson().isEmpty());
        // Select a chip in the Bar, which should expand the panel.
        expandPanelAndAssert();

        CriteriaHelper.pollUiThread(
                () -> {
                    Criteria.checkThat(
                            mPanel.getRelatedSearchesInBarControl()
                                    .getChipsForTest()
                                    .get(0)
                                    .model
                                    .get(ChipProperties.TEXT_MAX_WIDTH_PX),
                            Matchers.not(ChipProperties.SHOW_WHOLE_TEXT));
                });

        // Close the panel
        closePanel();
        // TODO(donnd): Validate UMA metrics once we log in-bar selections.
    }

    @Test
    @SmallTest
    @Feature({"ContextualSearch"})
    public void testRelatedSearchesInBarForDefinitionCard() throws Exception {
        CompositorAnimationHandler.setTestingMode(true);
        // Do a normal search without Related Searches or Definition cards.
        simulateResolveSearch("search");
        float normalHeight = mPanel.getHeight();

        // Simulate a response that includes both a definition and Related Searches
        List<String> inBarSuggestions = new ArrayList<String>();
        inBarSuggestions.add("Related Suggestion 1");
        inBarSuggestions.add("Related Suggestion 2");
        ThreadUtils.runOnUiThreadBlocking(
                () ->
                        mPanel.onSearchTermResolved(
                                "obscure · əbˈskyo͝or",
                                null,
                                null,
                                QuickActionCategory.NONE,
                                ResolvedSearchTerm.CardTag.CT_DEFINITION,
                                inBarSuggestions));
        boolean didPanelGetTaller = mPanel.getHeight() > normalHeight;
        Assert.assertTrue(
                "Related Searches should show in a taller Bar when there's a definition card, "
                        + "but they did not!",
                didPanelGetTaller);
        // Clean up
        closePanel();
        CompositorAnimationHandler.setTestingMode(false);
    }

    @Test
    @SmallTest
    @Feature({"ContextualSearch"})
    @DisabledTest(message = "https://crbug.com/1255084")
    public void testRelatedSearchesDismissDuringAnimation() throws Exception {
        // Use the "intelligence" node to generate Related Searches suggestions.
        simulateResolveSearch("intelligence");

        // Wait for the animation to start growing the Bar.
        CriteriaHelper.pollUiThread(
                () -> {
                    Criteria.checkThat(
                            mPanel.getInBarRelatedSearchesAnimatedHeightDps(),
                            Matchers.greaterThan(0f));
                });

        // Wait for the animation to change to make sure that doesn't bring the Bar back
        final boolean[] didAnimationChange = {false};
        mPanel.getSearchBarControl()
                .setInBarAnimationTestNotifier(
                        () -> {
                            didAnimationChange[0] = true;
                        });
        CriteriaHelper.pollUiThread(
                () -> {
                    Criteria.checkThat(didAnimationChange[0], Matchers.is(true));
                });
        // Repeatedly closing the panel should not bring it back even during ongoing animation.
        closePanel();
        Assert.assertFalse("The panel is showing again due to Animation!", mPanel.isShowing());
        // Another scroll might try to close the panel when it thinks it's already closed, which
        // could fail due to inconsistencies in internal logic, so test that too.
        closePanel();
        Assert.assertFalse(
                "Expected the panel to not be showing after a close! "
                        + "Animation of the Bar height is the likely cause.",
                mPanel.isShowing());
    }
}