| Index: content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionFactoryTest.java
|
| diff --git a/content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionFactoryTest.java b/content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionFactoryTest.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..af40a611c46b2a5037d815fdfc96af624186158e
|
| --- /dev/null
|
| +++ b/content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionFactoryTest.java
|
| @@ -0,0 +1,290 @@
|
| +// Copyright 2016 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +package org.chromium.content.browser.input;
|
| +
|
| +import static org.junit.Assert.assertFalse;
|
| +import static org.junit.Assert.assertNotNull;
|
| +import static org.junit.Assert.assertNull;
|
| +import static org.junit.Assert.assertTrue;
|
| +import static org.junit.Assert.fail;
|
| +import static org.mockito.ArgumentMatchers.any;
|
| +import static org.mockito.Mockito.doAnswer;
|
| +import static org.mockito.Mockito.inOrder;
|
| +import static org.mockito.Mockito.when;
|
| +
|
| +import android.content.Context;
|
| +import android.os.Handler;
|
| +import android.view.View;
|
| +import android.view.inputmethod.EditorInfo;
|
| +import android.view.inputmethod.InputConnection;
|
| +import android.view.inputmethod.InputMethodManager;
|
| +
|
| +import org.chromium.base.ThreadUtils;
|
| +import org.chromium.base.test.util.Feature;
|
| +import org.chromium.testing.local.LocalRobolectricTestRunner;
|
| +import org.junit.Before;
|
| +import org.junit.Test;
|
| +import org.junit.runner.RunWith;
|
| +import org.mockito.InOrder;
|
| +import org.mockito.Mock;
|
| +import org.mockito.Mockito;
|
| +import org.mockito.MockitoAnnotations;
|
| +import org.mockito.invocation.InvocationOnMock;
|
| +import org.mockito.stubbing.Answer;
|
| +import org.robolectric.Robolectric;
|
| +import org.robolectric.annotation.Config;
|
| +import org.robolectric.internal.ShadowExtractor;
|
| +import org.robolectric.shadows.ShadowLooper;
|
| +
|
| +import java.util.concurrent.Callable;
|
| +
|
| +/**
|
| + * Unit tests for {@ThreadedInputConnectionFactory}.
|
| + */
|
| +@RunWith(LocalRobolectricTestRunner.class)
|
| +@Config(manifest = Config.NONE)
|
| +public class ThreadedInputConnectionFactoryTest {
|
| +
|
| + /**
|
| + * A testable version of ThreadedInputConnectionFactory.
|
| + */
|
| + private class TestFactory extends ThreadedInputConnectionFactory {
|
| +
|
| + private boolean mSucceeded;
|
| + private boolean mFailed;
|
| +
|
| + TestFactory(InputMethodManagerWrapper inputMethodManagerWrapper) {
|
| + super(inputMethodManagerWrapper);
|
| + }
|
| +
|
| + @Override
|
| + protected Handler createHandler() {
|
| + mImeHandler = super.createHandler();
|
| + mImeShadowLooper = (ShadowLooper) ShadowExtractor.extract(mImeHandler.getLooper());
|
| + return mImeHandler;
|
| + }
|
| +
|
| + @Override
|
| + protected ThreadedInputConnectionProxyView createProxyView(Handler handler,
|
| + View containerView) {
|
| + return mProxyView;
|
| + }
|
| +
|
| + @Override
|
| + protected InputMethodUma createInputMethodUma() {
|
| + return null;
|
| + }
|
| +
|
| + @Override
|
| + protected void onRegisterProxyViewSuccess() {
|
| + mSucceeded = true;
|
| + }
|
| +
|
| + @Override
|
| + protected void onRegisterProxyViewFailure() {
|
| + mFailed = true;
|
| + }
|
| +
|
| + public boolean hasFailed() {
|
| + return mFailed;
|
| + }
|
| + public boolean hasSucceeded() {
|
| + return mSucceeded;
|
| + }
|
| + }
|
| +
|
| + @Mock
|
| + private ImeAdapter mImeAdapter;
|
| + @Mock
|
| + private View mContainerView;
|
| + @Mock
|
| + private ThreadedInputConnectionProxyView mProxyView;
|
| + @Mock
|
| + private InputMethodManager mInputMethodManager;
|
| + @Mock
|
| + private Context mContext;
|
| +
|
| + private EditorInfo mEditorInfo;
|
| + private Handler mImeHandler;
|
| + private Handler mUiHandler;
|
| + private ShadowLooper mImeShadowLooper;
|
| + private TestFactory mFactory;
|
| + private InputConnection mInputConnection;
|
| + private InOrder mInOrder;
|
| + private boolean mWindowFocusChanged;
|
| +
|
| + @Before
|
| + public void setUp() throws Exception {
|
| + MockitoAnnotations.initMocks(this);
|
| +
|
| + mEditorInfo = new EditorInfo();
|
| + mUiHandler = new Handler();
|
| +
|
| + mContext = Mockito.mock(Context.class);
|
| + mContainerView = Mockito.mock(View.class);
|
| + mImeAdapter = Mockito.mock(ImeAdapter.class);
|
| + mInputMethodManager = Mockito.mock(InputMethodManager.class);
|
| +
|
| + mFactory = new TestFactory(new InputMethodManagerWrapper(mContext));
|
| +
|
| + when(mContext.getSystemService(Context.INPUT_METHOD_SERVICE))
|
| + .thenReturn(mInputMethodManager);
|
| + when(mContainerView.getContext()).thenReturn(mContext);
|
| + when(mContainerView.getHandler()).thenReturn(mUiHandler);
|
| + when(mContainerView.hasFocus()).thenReturn(true);
|
| + when(mContainerView.hasWindowFocus()).thenReturn(true);
|
| +
|
| + mProxyView = Mockito.mock(ThreadedInputConnectionProxyView.class);
|
| + when(mProxyView.getContext()).thenReturn(mContext);
|
| + when(mProxyView.requestFocus()).thenReturn(true);
|
| + when(mProxyView.getHandler()).thenReturn(mImeHandler);
|
| + doAnswer(new Answer<Void>() {
|
| + @Override
|
| + public Void answer(InvocationOnMock invocation) throws Throwable {
|
| + mWindowFocusChanged = true;
|
| + return null;
|
| + }
|
| + }).when(mProxyView).onWindowFocusChanged(true);
|
| + final Callable<InputConnection> callable = new Callable<InputConnection>() {
|
| + @Override
|
| + public InputConnection call() throws Exception {
|
| + return mFactory.initializeAndGet(
|
| + mContainerView, mImeAdapter, 1, 0, 0, 0, mEditorInfo);
|
| + }
|
| + };
|
| + when(mProxyView.onCreateInputConnection(any(EditorInfo.class))).thenAnswer(
|
| + new Answer<InputConnection>() {
|
| + @Override
|
| + public InputConnection answer(InvocationOnMock invocation) throws Throwable {
|
| + return ThreadUtils.runOnUiThreadBlockingNoException(callable);
|
| + }
|
| + });
|
| +
|
| + when(mInputMethodManager.isActive(mContainerView)).thenAnswer(new Answer<Boolean>() {
|
| + private int mCount;
|
| +
|
| + @Override
|
| + public Boolean answer(InvocationOnMock invocation) throws Throwable {
|
| + mCount++;
|
| + if (mCount == 1 && mWindowFocusChanged) {
|
| + mInputConnection = mProxyView.onCreateInputConnection(mEditorInfo);
|
| + return false;
|
| + } else if (mCount == 2) {
|
| + return true;
|
| + }
|
| + fail();
|
| + return false;
|
| + }
|
| + });
|
| + when(mInputMethodManager.isActive(mProxyView)).thenReturn(true);
|
| +
|
| + mInOrder = inOrder(mImeAdapter, mInputMethodManager, mContainerView, mProxyView);
|
| + }
|
| +
|
| + private void activateInput() {
|
| + mUiHandler.post(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + assertNull(mFactory.initializeAndGet(
|
| + mContainerView, mImeAdapter, 1, 0, 0, 0, mEditorInfo));
|
| + }
|
| + });
|
| + }
|
| +
|
| + private void runOneUiTask() {
|
| + assertTrue(Robolectric.getForegroundThreadScheduler().runOneTask());
|
| + }
|
| +
|
| + @Test
|
| + @Feature({"TextInput"})
|
| + public void testCreateInputConnection_Success() {
|
| + // Pause all the loopers.
|
| + Robolectric.getForegroundThreadScheduler().pause();
|
| + mImeShadowLooper.pause();
|
| +
|
| + activateInput();
|
| +
|
| + // The first onCreateInputConnection().
|
| + runOneUiTask();
|
| + mInOrder.verify(mContainerView).hasFocus();
|
| + mInOrder.verify(mContainerView).hasWindowFocus();
|
| + mInOrder.verify(mProxyView).requestFocus();
|
| + mInOrder.verify(mContainerView).getHandler();
|
| + mInOrder.verifyNoMoreInteractions();
|
| + assertNull(mInputConnection);
|
| +
|
| + // The second onCreateInputConnection().
|
| + runOneUiTask();
|
| + mInOrder.verify(mProxyView).onWindowFocusChanged(true);
|
| + mInOrder.verify(mInputMethodManager).isActive(mContainerView);
|
| + mInOrder.verify(mProxyView).onCreateInputConnection(any(EditorInfo.class));
|
| + mInOrder.verify(mContainerView).getContext(); // BaseInputConnection#<init>
|
| + mInOrder.verifyNoMoreInteractions();
|
| + assertNotNull(mInputConnection);
|
| + assertTrue(ThreadedInputConnection.class.isInstance(mInputConnection));
|
| +
|
| + // Verification process.
|
| + mImeShadowLooper.runOneTask();
|
| + runOneUiTask();
|
| +
|
| + mInOrder.verify(mInputMethodManager).isActive(mProxyView);
|
| + mInOrder.verifyNoMoreInteractions();
|
| +
|
| + assertTrue(mFactory.hasSucceeded());
|
| + assertFalse(mFactory.hasFailed());
|
| + }
|
| +
|
| + @Test
|
| + @Feature({"TextInput"})
|
| + public void testCreateInputConnection_WindowFocusLostOnFirstLoop() {
|
| + // Somehow input was activated right after window focus was lost.
|
| + when(mContainerView.hasWindowFocus()).thenReturn(false);
|
| +
|
| + // Pause all the loopers.
|
| + Robolectric.getForegroundThreadScheduler().pause();
|
| + mImeShadowLooper.pause();
|
| +
|
| + activateInput();
|
| +
|
| + // The first onCreateInputConnection().
|
| + runOneUiTask();
|
| + mInOrder.verify(mContainerView).hasFocus();
|
| + mInOrder.verify(mContainerView).hasWindowFocus();
|
| + mInOrder.verifyNoMoreInteractions();
|
| + assertNull(mInputConnection);
|
| + assertFalse(mFactory.hasSucceeded());
|
| + assertFalse(mFactory.hasFailed());
|
| + }
|
| +
|
| + @Test
|
| + @Feature({"TextInput"})
|
| + public void testCreateInputConnection_WindowFocusLostOnSecondLoop() {
|
| + // Pause all the loopers.
|
| + Robolectric.getForegroundThreadScheduler().pause();
|
| + mImeShadowLooper.pause();
|
| +
|
| + activateInput();
|
| +
|
| + // The first onCreateInputConnection().
|
| + runOneUiTask();
|
| + mInOrder.verify(mContainerView).hasFocus();
|
| + mInOrder.verify(mContainerView).hasWindowFocus();
|
| + mInOrder.verify(mProxyView).requestFocus();
|
| + mInOrder.verify(mContainerView).getHandler();
|
| + mInOrder.verifyNoMoreInteractions();
|
| + assertNull(mInputConnection);
|
| +
|
| + // Now window focus was lost before the second onCreateInputConnection().
|
| + mFactory.onWindowFocusChanged(false);
|
| +
|
| + // The second onCreateInputConnection() gets ignored.
|
| + runOneUiTask();
|
| + mInOrder.verify(mProxyView).onOriginalViewWindowFocusChanged(false);
|
| + mInOrder.verifyNoMoreInteractions();
|
| + assertNull(mInputConnection);
|
| + assertFalse(mFactory.hasSucceeded());
|
| + assertFalse(mFactory.hasFailed());
|
| + }
|
| +}
|
|
|