blob: a8e7890913b9d78193209aa1526d10d52d3f9f90 [file] [log] [blame]
// 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));
}
}