| Index: content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionTest.java
 | 
| diff --git a/content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionTest.java b/content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionTest.java
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..a8e7890913b9d78193209aa1526d10d52d3f9f90
 | 
| --- /dev/null
 | 
| +++ b/content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionTest.java
 | 
| @@ -0,0 +1,162 @@
 | 
| +// 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.assertEquals;
 | 
| +import static org.junit.Assert.assertFalse;
 | 
| +import static org.junit.Assert.assertNull;
 | 
| +import static org.junit.Assert.assertTrue;
 | 
| +import static org.junit.Assert.fail;
 | 
| +import static org.mockito.Mockito.inOrder;
 | 
| +import static org.mockito.Mockito.never;
 | 
| +import static org.mockito.Mockito.when;
 | 
| +
 | 
| +import android.os.Handler;
 | 
| +
 | 
| +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.robolectric.annotation.Config;
 | 
| +
 | 
| +/**
 | 
| + * Unit tests for {@ThreadedInputConnection}.
 | 
| + */
 | 
| +@RunWith(LocalRobolectricTestRunner.class)
 | 
| +@Config(manifest = Config.NONE)
 | 
| +public class ThreadedInputConnectionTest {
 | 
| +    @Mock ImeAdapter mImeAdapter;
 | 
| +
 | 
| +    ThreadedInputConnection mConnection;
 | 
| +    InOrder mInOrder;
 | 
| +
 | 
| +    @Before
 | 
| +    public void setUp() throws Exception {
 | 
| +        MockitoAnnotations.initMocks(this);
 | 
| +
 | 
| +        mImeAdapter = Mockito.mock(ImeAdapter.class);
 | 
| +        mInOrder = inOrder(mImeAdapter);
 | 
| +        // Let's create Handler for test thread and pretend that it is running on IME thread.
 | 
| +        mConnection = new ThreadedInputConnection(mImeAdapter, new Handler());
 | 
| +    }
 | 
| +
 | 
| +    @Test
 | 
| +    @Feature({"TextInput"})
 | 
| +    public void testComposeGetTextFinishGetText() {
 | 
| +        // IME app calls setComposingText().
 | 
| +        mConnection.setComposingText("hello", 1);
 | 
| +        mInOrder.verify(mImeAdapter).sendCompositionToNative("hello", 1, false);
 | 
| +
 | 
| +        // Renderer updates states asynchronously.
 | 
| +        mConnection.updateStateOnUiThread("hello", 5, 5, 0, 5, true, true);
 | 
| +        mInOrder.verify(mImeAdapter).updateSelection(5, 5, 0, 5);
 | 
| +        assertEquals(0, mConnection.getQueueForTest().size());
 | 
| +
 | 
| +        // Prepare to call requestTextInputStateUpdate.
 | 
| +        mConnection.updateStateOnUiThread("hello", 5, 5, 0, 5, true, false);
 | 
| +        assertEquals(1, mConnection.getQueueForTest().size());
 | 
| +        when(mImeAdapter.requestTextInputStateUpdate()).thenReturn(true);
 | 
| +
 | 
| +        // IME app calls getTextBeforeCursor().
 | 
| +        assertEquals("hello", mConnection.getTextBeforeCursor(20, 0));
 | 
| +
 | 
| +        // IME app calls finishComposingText().
 | 
| +        mConnection.finishComposingText();
 | 
| +        mInOrder.verify(mImeAdapter).finishComposingText();
 | 
| +        mConnection.updateStateOnUiThread("hello", 5, 5, -1, -1, true, true);
 | 
| +        mInOrder.verify(mImeAdapter).updateSelection(5, 5, -1, -1);
 | 
| +
 | 
| +        // Prepare to call requestTextInputStateUpdate.
 | 
| +        mConnection.updateStateOnUiThread("hello", 5, 5, -1, -1, true, false);
 | 
| +        assertEquals(1, mConnection.getQueueForTest().size());
 | 
| +        when(mImeAdapter.requestTextInputStateUpdate()).thenReturn(true);
 | 
| +
 | 
| +        // IME app calls getTextBeforeCursor().
 | 
| +        assertEquals("hello", mConnection.getTextBeforeCursor(20, 0));
 | 
| +
 | 
| +        assertEquals(0, mConnection.getQueueForTest().size());
 | 
| +    }
 | 
| +
 | 
| +    @Test
 | 
| +    @Feature({"TextInput"})
 | 
| +    public void testRenderChangeUpdatesSelection() {
 | 
| +        // User moves the cursor.
 | 
| +        mConnection.updateStateOnUiThread("hello", 4, 4, -1, -1, true, true);
 | 
| +        mInOrder.verify(mImeAdapter).updateSelection(4, 4, -1, -1);
 | 
| +        assertEquals(0, mConnection.getQueueForTest().size());
 | 
| +    }
 | 
| +
 | 
| +    @Test
 | 
| +    @Feature({"TextInput"})
 | 
| +    public void testBatchEdit() {
 | 
| +        // IME app calls beginBatchEdit().
 | 
| +        assertTrue(mConnection.beginBatchEdit());
 | 
| +        // Type hello real fast.
 | 
| +        mConnection.commitText("hello", 1);
 | 
| +        mInOrder.verify(mImeAdapter).sendCompositionToNative("hello", 1, true);
 | 
| +
 | 
| +        // Renderer updates states asynchronously.
 | 
| +        mConnection.updateStateOnUiThread("hello", 5, 5, -1, -1, true, true);
 | 
| +        mInOrder.verify(mImeAdapter, never()).updateSelection(5, 5, -1, -1);
 | 
| +        assertEquals(0, mConnection.getQueueForTest().size());
 | 
| +
 | 
| +        {
 | 
| +            // Nest another batch edit.
 | 
| +            assertTrue(mConnection.beginBatchEdit());
 | 
| +            // Move the cursor to the left.
 | 
| +            mConnection.setSelection(4, 4);
 | 
| +            assertTrue(mConnection.endBatchEdit());
 | 
| +        }
 | 
| +        // We still have one outer batch edit, so should not update selection yet.
 | 
| +        mInOrder.verify(mImeAdapter, never()).updateSelection(4, 4, -1, -1);
 | 
| +
 | 
| +        // Prepare to call requestTextInputStateUpdate.
 | 
| +        mConnection.updateStateOnUiThread("hello", 4, 4, -1, -1, true, false);
 | 
| +        assertEquals(1, mConnection.getQueueForTest().size());
 | 
| +        when(mImeAdapter.requestTextInputStateUpdate()).thenReturn(true);
 | 
| +
 | 
| +        // IME app calls endBatchEdit().
 | 
| +        assertFalse(mConnection.endBatchEdit());
 | 
| +        // Batch edit is finished, now update selection.
 | 
| +        mInOrder.verify(mImeAdapter).updateSelection(4, 4, -1, -1);
 | 
| +        assertEquals(0, mConnection.getQueueForTest().size());
 | 
| +    }
 | 
| +
 | 
| +    @Test
 | 
| +    @Feature({"TextInput"})
 | 
| +    public void testFailToRequestToRenderer() {
 | 
| +        when(mImeAdapter.requestTextInputStateUpdate()).thenReturn(false);
 | 
| +        // Should not hang here. Return null to indicate failure.
 | 
| +        assertNull(null, mConnection.getTextBeforeCursor(10, 0));
 | 
| +    }
 | 
| +
 | 
| +    @Test
 | 
| +    @Feature({"TextInput"})
 | 
| +    public void testRendererCannotUpdateState() {
 | 
| +        when(mImeAdapter.requestTextInputStateUpdate()).thenReturn(true);
 | 
| +        // We found that renderer cannot update state, e.g., due to a crash.
 | 
| +        ThreadUtils.postOnUiThread(new Runnable() {
 | 
| +            @Override
 | 
| +            public void run() {
 | 
| +                try {
 | 
| +                    // TODO(changwan): find a way to avoid this.
 | 
| +                    Thread.sleep(1000);
 | 
| +                } catch (InterruptedException e) {
 | 
| +                    e.printStackTrace();
 | 
| +                    fail();
 | 
| +                }
 | 
| +                mConnection.unblockOnUiThread();
 | 
| +            }
 | 
| +        });
 | 
| +        // Should not hang here. Return null to indicate failure.
 | 
| +        assertEquals(null, mConnection.getTextBeforeCursor(10, 0));
 | 
| +    }
 | 
| +}
 | 
| 
 |