diff options
Diffstat (limited to 'tests/src/com/android/inputmethod/latin/InputLogicTests.java')
-rw-r--r-- | tests/src/com/android/inputmethod/latin/InputLogicTests.java | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java index 78bd4d741..9d53b63f1 100644 --- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java +++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java @@ -19,9 +19,13 @@ package com.android.inputmethod.latin; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.os.Looper; +import android.os.MessageQueue; import android.preference.PreferenceManager; import android.test.ServiceTestCase; import android.text.InputType; +import android.text.SpannableStringBuilder; +import android.text.style.SuggestionSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -127,6 +131,51 @@ public class InputLogicTests extends ServiceTestCase<LatinIME> { } } + // We need to run the messages added to the handler from LatinIME. The only way to do + // that is to call Looper#loop() on the right looper, so we're going to get the looper + // object and call #loop() here. The messages in the handler actually run on the UI + // thread of the keyboard by design of the handler, so we want to call it synchronously + // on the same thread that the tests are running on to mimic the actual environment as + // closely as possible. + // Now, Looper#loop() never exits in normal operation unless the Looper#quit() method + // is called, so we need to do that at the right time so that #loop() returns at some + // point and we don't end up in an infinite loop. + // After we quit, the looper is still technically ready to process more messages but + // the handler will refuse to enqueue any because #quit() has been called and it + // explicitly tests for it on message enqueuing, so we'll have to reset it so that + // it lets us continue normal operation. + private void runMessages() { + // Here begins deep magic. + final Looper looper = mLatinIME.mHandler.getLooper(); + mLatinIME.mHandler.post(new Runnable() { + @Override + public void run() { + looper.quit(); + } + }); + // The only way to get out of Looper#loop() is to call #quit() on it (or on its queue). + // Once #quit() is called remaining messages are not processed, which is why we post + // a message that calls it instead of calling it directly. + looper.loop(); + + // Once #quit() has been called, the message queue has an "mQuiting" field that prevents + // any subsequent post in this queue. However the queue itself is still fully functional! + // If we have a way of resetting "queue.mQuiting" then we can continue using it as normal, + // coming back to this method to run the messages. + MessageQueue queue = looper.getQueue(); + try { + // However there is no way of doing it externally, and mQuiting is private. + // So... get out the big guns. + java.lang.reflect.Field f = MessageQueue.class.getDeclaredField("mQuiting"); + f.setAccessible(true); // What do you mean "private"? + f.setBoolean(queue, false); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + // type(int) and type(String): helper methods to send a code point resp. a string to LatinIME. private void type(final int codePoint) { // onPressKey and onReleaseKey are explicitly deactivated here, but they do happen in the @@ -152,6 +201,13 @@ public class InputLogicTests extends ServiceTestCase<LatinIME> { } } + // Helper to avoid writing the try{}catch block each time + private static void sleep(final int milliseconds) { + try { + Thread.sleep(milliseconds); + } catch (InterruptedException e) {} + } + public void testTypeWord() { final String WORD_TO_TYPE = "abcd"; type(WORD_TO_TYPE); @@ -446,5 +502,115 @@ public class InputLogicTests extends ServiceTestCase<LatinIME> { EXPECTED_RESULT, mTextView.getText().toString()); } + // A helper class to ease span tests + private static class Span { + final SpannableStringBuilder mInputText; + final SuggestionSpan mSpan; + final int mStart; + final int mEnd; + // The supplied CharSequence should be an instance of SpannableStringBuilder, + // and it should contain exactly zero or one SuggestionSpan. Otherwise, an exception + // is thrown. + public Span(final CharSequence inputText) { + mInputText = (SpannableStringBuilder)inputText; + final SuggestionSpan[] spans = + mInputText.getSpans(0, mInputText.length(), SuggestionSpan.class); + if (0 == spans.length) { + mSpan = null; + mStart = -1; + mEnd = -1; + } else if (1 == spans.length) { + mSpan = spans[0]; + mStart = mInputText.getSpanStart(mSpan); + mEnd = mInputText.getSpanEnd(mSpan); + } else { + throw new RuntimeException("Expected one SuggestionSpan, found " + spans.length); + } + } + public boolean isAutoCorrectionIndicator() { + return 0 != (SuggestionSpan.FLAG_AUTO_CORRECTION & mSpan.getFlags()); + } + } + + static final int DELAY_TO_WAIT_FOR_UNDERLINE = 200; // The message is posted with a 100 ms delay + public void testBlueUnderline() { + final String STRING_TO_TYPE = "tgis"; + final int EXPECTED_SPAN_START = 0; + final int EXPECTED_SPAN_END = 4; + type(STRING_TO_TYPE); + sleep(DELAY_TO_WAIT_FOR_UNDERLINE); + runMessages(); + final Span span = new Span(mTextView.getText()); + assertEquals("show blue underline, span start", EXPECTED_SPAN_START, span.mStart); + assertEquals("show blue underline, span end", EXPECTED_SPAN_END, span.mEnd); + assertEquals("show blue underline, span color", true, span.isAutoCorrectionIndicator()); + } + + public void testBlueUnderlineDisappears() { + final String STRING_1_TO_TYPE = "tgis"; + final String STRING_2_TO_TYPE = "q"; + final int EXPECTED_SPAN_START = 0; + final int EXPECTED_SPAN_END = 5; + type(STRING_1_TO_TYPE); + sleep(DELAY_TO_WAIT_FOR_UNDERLINE); + runMessages(); + type(STRING_2_TO_TYPE); + // We haven't have time to look into the dictionary yet, so the line should still be + // blue to avoid any flicker. + final Span spanBefore = new Span(mTextView.getText()); + assertEquals("extend blue underline, span start", EXPECTED_SPAN_START, spanBefore.mStart); + assertEquals("extend blue underline, span end", EXPECTED_SPAN_END, spanBefore.mEnd); + assertEquals("extend blue underline, span color", true, + spanBefore.isAutoCorrectionIndicator()); + sleep(DELAY_TO_WAIT_FOR_UNDERLINE); + runMessages(); + // Now we have been able to re-evaluate the word, there shouldn't be an auto-correction span + final Span spanAfter = new Span(mTextView.getText()); + assertNull("hide blue underline", spanAfter.mSpan); + } + + public void testBlueUnderlineOnBackspace() { + final String STRING_TO_TYPE = "tgis"; + final int EXPECTED_SPAN_START = 0; + final int EXPECTED_SPAN_END = 4; + type(STRING_TO_TYPE); + sleep(DELAY_TO_WAIT_FOR_UNDERLINE); + runMessages(); + type(Keyboard.CODE_SPACE); + sleep(DELAY_TO_WAIT_FOR_UNDERLINE); + runMessages(); + type(Keyboard.CODE_DELETE); + sleep(DELAY_TO_WAIT_FOR_UNDERLINE); + runMessages(); + type(Keyboard.CODE_DELETE); + sleep(DELAY_TO_WAIT_FOR_UNDERLINE); + runMessages(); + final Span span = new Span(mTextView.getText()); + assertEquals("show blue underline after backspace, span start", + EXPECTED_SPAN_START, span.mStart); + assertEquals("show blue underline after backspace, span end", + EXPECTED_SPAN_END, span.mEnd); + assertEquals("show blue underline after backspace, span color", true, + span.isAutoCorrectionIndicator()); + } + + public void testBlueUnderlineDisappearsWhenCursorMoved() { + final String STRING_TO_TYPE = "tgis"; + final int NEW_CURSOR_POSITION = 0; + type(STRING_TO_TYPE); + sleep(DELAY_TO_WAIT_FOR_UNDERLINE); + // Simulate the onUpdateSelection() event + mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); + runMessages(); + // Here the blue underline has been set. testBlueUnderline() is testing for this already, + // so let's not test it here again. + // Now simulate the user moving the cursor. + mInputConnection.setSelection(NEW_CURSOR_POSITION, NEW_CURSOR_POSITION); + mLatinIME.onUpdateSelection(0, 0, NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1); + sleep(DELAY_TO_WAIT_FOR_UNDERLINE); + runMessages(); + final Span span = new Span(mTextView.getText()); + assertNull("blue underline removed when cursor is moved", span.mSpan); + } // TODO: Add some tests for non-BMP characters } |