/*
 * Copyright (C) 2017 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 androidx.appcompat.widget;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.app.Instrumentation;
import android.content.res.Resources;
import android.text.InputType;
import android.text.TextUtils;
import android.view.inputmethod.EditorInfo;

import androidx.appcompat.test.R;
import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;

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

/**
 * Test {@link SearchView}.
 */
@MediumTest
@RunWith(AndroidJUnit4.class)
public class SearchViewTest {
    private Instrumentation mInstrumentation;
    private Activity mActivity;
    private SearchView mSearchView;

    @Rule
    public ActivityTestRule<SearchViewTestActivity> mActivityRule =
            new ActivityTestRule<>(SearchViewTestActivity.class);

    @Before
    public void setup() {
        mInstrumentation = InstrumentationRegistry.getInstrumentation();
        mActivity = mActivityRule.getActivity();
        mSearchView = (SearchView) mActivity.findViewById(R.id.search_view);
    }

    @UiThreadTest
    @Test
    public void testConstructor() {
        new SearchView(mActivity);

        new SearchView(mActivity, null);

        new SearchView(mActivity, null, android.R.attr.searchViewStyle);
    }

    @UiThreadTest
    @Test
    public void testAttributesFromXml() {
        SearchView searchViewWithAttributes =
                (SearchView) mActivity.findViewById(R.id.search_view_with_defaults);
        assertEquals(mActivity.getString(R.string.search_query_hint),
                searchViewWithAttributes.getQueryHint());
        assertFalse(searchViewWithAttributes.isIconfiedByDefault());
        assertEquals(EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS | EditorInfo.TYPE_CLASS_TEXT,
                searchViewWithAttributes.getInputType());
        assertEquals(EditorInfo.IME_ACTION_DONE, searchViewWithAttributes.getImeOptions());
        assertEquals(mActivity.getResources().getDimensionPixelSize(R.dimen.search_view_max_width),
                searchViewWithAttributes.getMaxWidth());
    }

    @UiThreadTest
    @Test
    public void testAccessIconified() {
        mSearchView.setIconified(true);
        assertTrue(mSearchView.isIconified());

        mSearchView.setIconified(false);
        assertFalse(mSearchView.isIconified());
    }

    @UiThreadTest
    @Test
    public void testAccessIconifiedByDefault() {
        mSearchView.setIconifiedByDefault(true);
        assertTrue(mSearchView.isIconfiedByDefault());

        mSearchView.setIconifiedByDefault(false);
        assertFalse(mSearchView.isIconfiedByDefault());
    }

    @Test
    public void testDenyIconifyingNonInconifiableView() throws Throwable {
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mSearchView.setIconifiedByDefault(false);
                mSearchView.setIconified(false);
            }
        });

        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mSearchView.setIconified(true);
            }
        });
        mInstrumentation.waitForIdleSync();

        // Since our search view is marked with iconifiedByDefault=false, call to setIconified
        // with true us going to be ignored, as detailed in the class-level documentation of
        // SearchView.
        assertFalse(mSearchView.isIconified());
    }

    @Test
    public void testDenyIconifyingInconifiableView() throws Throwable {
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mSearchView.setIconifiedByDefault(true);
                mSearchView.setIconified(false);
            }
        });

        final SearchView.OnCloseListener mockDenyCloseListener =
                mock(SearchView.OnCloseListener.class);
        when(mockDenyCloseListener.onClose()).thenReturn(Boolean.TRUE);
        mSearchView.setOnCloseListener(mockDenyCloseListener);

        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mSearchView.setIconified(true);
            }
        });
        mInstrumentation.waitForIdleSync();

        // Our mock listener is configured to return true from its onClose, thereby preventing
        // the iconify request to be completed. Check that the listener was called and that the
        // search view is not iconified.
        verify(mockDenyCloseListener, times(1)).onClose();
        assertFalse(mSearchView.isIconified());
    }

    @Test
    public void testAllowIconifyingInconifiableView() throws Throwable {
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mSearchView.setIconifiedByDefault(true);
                mSearchView.setIconified(false);
            }
        });

        final SearchView.OnCloseListener mockAllowCloseListener =
                mock(SearchView.OnCloseListener.class);
        when(mockAllowCloseListener.onClose()).thenReturn(Boolean.FALSE);
        mSearchView.setOnCloseListener(mockAllowCloseListener);

        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mSearchView.setIconified(true);
            }
        });
        mInstrumentation.waitForIdleSync();

        // Our mock listener is configured to return false from its onClose, thereby allowing
        // the iconify request to be completed. Check that the listener was called and that the
        // search view is not iconified.
        verify(mockAllowCloseListener, times(1)).onClose();
        assertTrue(mSearchView.isIconified());
    }

    @Test
    public void testAccessMaxWidth() throws Throwable {
        final Resources res = mActivity.getResources();
        final int maxWidth1 = res.getDimensionPixelSize(R.dimen.search_view_max_width);
        final int maxWidth2 = res.getDimensionPixelSize(R.dimen.search_view_max_width2);

        // Set search view to not be iconified before running max-width tests
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mSearchView.setIconified(false);
            }
        });

        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mSearchView.setMaxWidth(maxWidth1);
            }
        });
        mInstrumentation.waitForIdleSync();
        assertEquals(maxWidth1, mSearchView.getMaxWidth());
        assertTrue(mSearchView.getWidth() <= maxWidth1);

        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mSearchView.setMaxWidth(maxWidth2);
            }
        });
        mInstrumentation.waitForIdleSync();
        assertEquals(maxWidth2, mSearchView.getMaxWidth());
        assertTrue(mSearchView.getWidth() <= maxWidth2);
    }

    @UiThreadTest
    @Test
    public void testAccessQuery() {
        mSearchView.setIconified(false);

        final SearchView.OnQueryTextListener mockQueryTextListener =
                mock(SearchView.OnQueryTextListener.class);
        when(mockQueryTextListener.onQueryTextSubmit(anyString())).thenReturn(Boolean.TRUE);
        mSearchView.setOnQueryTextListener(mockQueryTextListener);

        mSearchView.setQuery("alpha", false);
        assertTrue(TextUtils.equals("alpha", mSearchView.getQuery()));
        // Since we passed false as the second parameter to setQuery, our query text listener
        // should have been invoked only with text change
        verify(mockQueryTextListener, times(1)).onQueryTextChange("alpha");
        verify(mockQueryTextListener, never()).onQueryTextSubmit(anyString());

        mSearchView.setQuery("beta", true);
        assertTrue(TextUtils.equals("beta", mSearchView.getQuery()));
        // Since we passed true as the second parameter to setQuery, our query text listener
        // should have been invoked on both callbacks
        verify(mockQueryTextListener, times(1)).onQueryTextChange("beta");
        verify(mockQueryTextListener, times(1)).onQueryTextSubmit("beta");

        mSearchView.setQuery("gamma", true);
        assertTrue(TextUtils.equals("gamma", mSearchView.getQuery()));
        // Since we passed true as the second parameter to setQuery, our query text listener
        // should have been invoked on both callbacks
        verify(mockQueryTextListener, times(1)).onQueryTextChange("gamma");
        verify(mockQueryTextListener, times(1)).onQueryTextSubmit("gamma");

        verifyNoMoreInteractions(mockQueryTextListener);
    }

    @UiThreadTest
    @Test
    public void testAccessQueryHint() {
        mSearchView.setQueryHint("hint 1");
        assertTrue(TextUtils.equals("hint 1", mSearchView.getQueryHint()));

        mSearchView.setQueryHint("hint 2");
        assertTrue(TextUtils.equals("hint 2", mSearchView.getQueryHint()));
    }

    @UiThreadTest
    @Test
    public void testAccessInputType() {
        mSearchView.setInputType(InputType.TYPE_CLASS_NUMBER
                | InputType.TYPE_NUMBER_FLAG_DECIMAL
                | InputType.TYPE_NUMBER_FLAG_SIGNED);
        assertEquals(InputType.TYPE_CLASS_NUMBER
                | InputType.TYPE_NUMBER_FLAG_DECIMAL
                | InputType.TYPE_NUMBER_FLAG_SIGNED, mSearchView.getInputType());

        mSearchView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
        assertEquals(InputType.TYPE_CLASS_TEXT
                | InputType.TYPE_TEXT_FLAG_CAP_WORDS, mSearchView.getInputType());

        mSearchView.setInputType(InputType.TYPE_CLASS_PHONE);
        assertEquals(InputType.TYPE_CLASS_PHONE, mSearchView.getInputType());
    }

    @UiThreadTest
    @Test
    public void testAccessImeOptions() {
        mSearchView.setImeOptions(EditorInfo.IME_ACTION_GO);
        assertEquals(EditorInfo.IME_ACTION_GO, mSearchView.getImeOptions());

        mSearchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
        assertEquals(EditorInfo.IME_ACTION_DONE, mSearchView.getImeOptions());

        mSearchView.setImeOptions(EditorInfo.IME_NULL);
        assertEquals(EditorInfo.IME_NULL, mSearchView.getImeOptions());
    }
}
