blob: db96c05d13fc8361f335cb5dc7634caf4d5052cf [file] [log] [blame]
// Copyright 2010-2014, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package org.mozc.android.inputmethod.japanese;
import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.isA;
import static org.easymock.EasyMock.same;
import org.mozc.android.inputmethod.japanese.CandidateWordView.Orientation;
import org.mozc.android.inputmethod.japanese.CandidateWordView.OrientationTrait;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCandidates.CandidateList;
import org.mozc.android.inputmethod.japanese.protobuf.ProtoCandidates.CandidateWord;
import org.mozc.android.inputmethod.japanese.testing.InstrumentationTestCaseWithMock;
import org.mozc.android.inputmethod.japanese.testing.MozcLayoutUtil;
import org.mozc.android.inputmethod.japanese.testing.Parameter;
import org.mozc.android.inputmethod.japanese.testing.VisibilityProxy;
import org.mozc.android.inputmethod.japanese.ui.CandidateLayout;
import org.mozc.android.inputmethod.japanese.ui.CandidateLayout.Row;
import org.mozc.android.inputmethod.japanese.ui.CandidateLayout.Span;
import org.mozc.android.inputmethod.japanese.ui.CandidateLayouter;
import org.mozc.android.inputmethod.japanese.ui.SnapScroller;
import org.mozc.android.inputmethod.japanese.view.DummyDrawable;
import org.mozc.android.inputmethod.japanese.view.Skin;
import com.google.common.base.Optional;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
*/
public class CandidateWordViewTest extends InstrumentationTestCaseWithMock {
class StubCandidateWordView extends CandidateWordView {
StubCandidateWordView(Context context, OrientationTrait orientationTrait) {
super(context, orientationTrait);
}
@Override
public void invalidate() {
// We are not willing to test rendering routine running background of event processing test.
// So just ignore this method's invocation.
}
@Override
protected Drawable getViewBackgroundDrawable(Skin skin) {
return DummyDrawable.getInstance();
}
}
public static Span createNullCandidateWordSpan(int left, int right) {
Span span = new Span(
Optional.<CandidateWord>absent(), 0, 0, Collections.<String>emptyList());
span.setLeft(left);
span.setRight(right);
return span;
}
private static final List<Row> ROW_DATA =
Arrays.asList(
MozcLayoutUtil.createRow(10, 20, 50,
MozcLayoutUtil.createSpan(0, 10, 30),
MozcLayoutUtil.createSpan(1, 40, 60)),
MozcLayoutUtil.createRow(40, 20, 50,
MozcLayoutUtil.createSpan(0, 10, 30),
MozcLayoutUtil.createSpan(1, 40, 60)),
MozcLayoutUtil.createRow(70, 20, 50,
createNullCandidateWordSpan(10, 30),
createNullCandidateWordSpan(40, 60)));
private static final CandidateList CANDIDATE_LIST = CandidateList.newBuilder()
.addCandidates(CandidateWord.newBuilder().setId(0).setValue("cand1"))
.addCandidates(CandidateWord.newBuilder().setId(1).setValue("cand2"))
.build();
@SmallTest
public void testOrientationHorizontal() {
View view = createViewMock(View.class);
OrientationTrait trait = Orientation.HORIZONTAL;
// getScrollPosition
// We cannot do enough test because we cannot control the result of
// View#getScrollX().
assertEquals(view.getScrollX(), trait.getScrollPosition(view));
// scrollTo
view.scrollTo(100, 0);
replayAll();
trait.scrollTo(view, 100);
verifyAll();
// getValueFromPair
assertEquals(100f, trait.projectVector(100f, 0f));
// Note that Row and Span are final class.
Row row = MozcLayoutUtil.createRow(100, 200, 400);
Span span = MozcLayoutUtil.createSpan(0, 300, 700);
// getPosition
assertEquals(300f, trait.getCandidatePosition(row, span));
// getLength
assertEquals(400f, trait.getCandidateLength(row, span));
// getViewLength
// We cannot do enough test because we cannot control the result of
// View#getWidth().
// TODO(matsuza): Use #setLeft() method (API Level 11)
assertEquals(view.getWidth(), trait.getViewLength(view));
}
@SmallTest
public void testOrientationVertical() {
View view = createViewMock(View.class);
OrientationTrait trait = Orientation.VERTICAL;
// getScrollPosition
// We cannot do enough test because we cannot control the result of
// View#getScrollY().
assertEquals(view.getScrollY(), trait.getScrollPosition(view));
// scrollTo
view.scrollTo(0, 100);
replayAll();
trait.scrollTo(view, 100);
verifyAll();
// getValueFromPair
assertEquals(100f, trait.projectVector(0f, 100f));
// Note that Row and Span are final class.
Row row = MozcLayoutUtil.createRow(100, 200, 400);
Span span = MozcLayoutUtil.createSpan(0, 300, 700);
// getPosition
assertEquals(100f, trait.getCandidatePosition(row, span));
// getLength
assertEquals(200f, trait.getCandidateLength(row, span));
// getViewLength
// We cannot do enough test because we cannot control the result of
// View#getWidth().
// TODO(matsuza): Use #setBotton() method (API Level 11)
assertEquals(view.getHeight(), trait.getViewLength(view));
}
@SmallTest
public void testOnSingleTapUp()
throws IllegalArgumentException, SecurityException {
CandidateWordView candidateWordView =
new StubCandidateWordView(getInstrumentation().getTargetContext(),
CandidateWordView.Orientation.VERTICAL);
CandidateSelectListener candidateSelectListener = createMock(CandidateSelectListener.class);
candidateWordView.setCandidateSelectListener(candidateSelectListener);
CandidateLayout mockLayout = MozcLayoutUtil.createCandidateLayoutMock(getMockSupport());
candidateWordView.calculatedLayout = mockLayout;
// onCandidateSelected() is never be called back.
List<MotionEvent> events = new ArrayList<MotionEvent>();
try {
expect(mockLayout.getRowList()).andStubReturn(ROW_DATA);
replayAll();
events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, Integer.MIN_VALUE, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 1000, MotionEvent.ACTION_UP, 0, Integer.MIN_VALUE, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
verifyAll();
resetAll();
expect(mockLayout.getRowList()).andStubReturn(ROW_DATA);
replayAll();
events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, Integer.MAX_VALUE, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 1000, MotionEvent.ACTION_UP, 0, Integer.MAX_VALUE, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
verifyAll();
resetAll();
expect(mockLayout.getRowList()).andStubReturn(ROW_DATA);
replayAll();
events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, Integer.MIN_VALUE, 0, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 1000, MotionEvent.ACTION_UP, Integer.MIN_VALUE, 0, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
verifyAll();
resetAll();
expect(mockLayout.getRowList()).andStubReturn(ROW_DATA);
replayAll();
events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, Integer.MAX_VALUE, 0, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 1000, MotionEvent.ACTION_UP, Integer.MAX_VALUE, 0, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
verifyAll();
// TouchUp on a candidate.
resetAll();
expect(mockLayout.getRowList()).andStubReturn(ROW_DATA);
candidateSelectListener.onCandidateSelected(
ROW_DATA.get(0).getSpanList().get(0).getCandidateWord().get(), Optional.<Integer>of(0));
replayAll();
events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 10, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 1000, MotionEvent.ACTION_UP, 10, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
verifyAll();
// Slide within a candidate (top-left corner).
resetAll();
expect(mockLayout.getRowList()).andStubReturn(ROW_DATA);
candidateSelectListener.onCandidateSelected(
ROW_DATA.get(0).getSpanList().get(0).getCandidateWord().get(), Optional.<Integer>of(0));
replayAll();
events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 10, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 1000, MotionEvent.ACTION_MOVE, 12, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 2000, MotionEvent.ACTION_UP, 11, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
verifyAll();
// Slide within a candidate (bottom-right corner).
resetAll();
expect(mockLayout.getRowList()).andStubReturn(ROW_DATA);
candidateSelectListener.onCandidateSelected(
ROW_DATA.get(0).getSpanList().get(0).getCandidateWord().get(), Optional.<Integer>of(0));
replayAll();
events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 29, 27, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 1000, MotionEvent.ACTION_MOVE, 29, 29, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 2000, MotionEvent.ACTION_UP, 29, 28, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
verifyAll();
// Slide out of a candidate. No selection.
resetAll();
expect(mockLayout.getRowList()).andStubReturn(ROW_DATA);
replayAll();
events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 29, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 1000, MotionEvent.ACTION_MOVE, 30, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 2000, MotionEvent.ACTION_UP, 30, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
verifyAll();
// Slide out of a candidate, then come back and release. No selection.
resetAll();
expect(mockLayout.getRowList()).andStubReturn(ROW_DATA);
replayAll();
events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 29, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 1000, MotionEvent.ACTION_MOVE, 30, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 2000, MotionEvent.ACTION_MOVE, 29, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 3000, MotionEvent.ACTION_UP, 29, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
verifyAll();
// Must consider scroll value.
resetAll();
expect(mockLayout.getRowList()).andStubReturn(ROW_DATA);
replayAll();
candidateWordView.scrollTo(0, -5);
events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 10, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 1000, MotionEvent.ACTION_UP, 10, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
verifyAll();
resetAll();
expect(mockLayout.getRowList()).andStubReturn(ROW_DATA);
replayAll();
candidateWordView.scrollTo(-5, 0);
events.add(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 10, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
events.add(MotionEvent.obtain(0, 1000, MotionEvent.ACTION_UP, 10, 10, 0));
assertTrue(candidateWordView.onTouchEvent(events.get(events.size() - 1)));
verifyAll();
} finally {
for (MotionEvent event : events) {
event.recycle();
}
}
}
@SmallTest
public void testUpdate_noLayouter() {
CandidateWordView candidateWordView = new StubCandidateWordView(
getInstrumentation().getTargetContext(), Orientation.VERTICAL);
assertNull(candidateWordView.getCandidateLayouter());
candidateWordView.update(CANDIDATE_LIST);
assertEquals(CANDIDATE_LIST, candidateWordView.currentCandidateList);
assertNull(candidateWordView.calculatedLayout);
}
@SmallTest
public void testUpdate_successPath() {
CandidateWordView candidateWordView = new StubCandidateWordView(
getInstrumentation().getTargetContext(), Orientation.VERTICAL);
CandidateLayouter layouter = createMock(CandidateLayouter.class);
expect(layouter.layout(isA(CandidateList.class)))
.andReturn(Optional.of(MozcLayoutUtil.createNiceCandidateLayoutMock(getMockSupport())));
candidateWordView.layouter = layouter;
expect(layouter.getPageHeight()).andReturn(50);
replayAll();
candidateWordView.updateLayouter();
assertNotNull(candidateWordView.layouter); // Pre-condition
candidateWordView.update(CANDIDATE_LIST);
verifyAll();
assertEquals(CANDIDATE_LIST, candidateWordView.currentCandidateList);
assertNotNull(candidateWordView.calculatedLayout);
}
@SmallTest
public void testUpdate_identicalOutput() {
CandidateWordView candidateWordView = new StubCandidateWordView(
getInstrumentation().getTargetContext(), Orientation.VERTICAL);
CandidateLayouter layouter = createMock(CandidateLayouter.class);
expect(layouter.layout(isA(CandidateList.class)))
.andStubReturn(Optional.of(MozcLayoutUtil.createNiceCandidateLayoutMock(getMockSupport())));
expect(layouter.getPageHeight()).andStubReturn(50);
candidateWordView.layouter = layouter;
replayAll();
candidateWordView.updateLayouter();
assertNotNull(candidateWordView.layouter); // Pre-condition
// If the same CandidateList is passed, post-condition should not be changed.
candidateWordView.update(CANDIDATE_LIST);
candidateWordView.update(CANDIDATE_LIST);
verifyAll();
assertEquals(CANDIDATE_LIST, candidateWordView.currentCandidateList);
assertNotNull(candidateWordView.calculatedLayout);
}
@SmallTest
public void testUpdate_null() {
CandidateWordView candidateWordView = new StubCandidateWordView(
getInstrumentation().getTargetContext(), Orientation.VERTICAL);
CandidateLayouter layouter = createMock(CandidateLayouter.class);
expect(layouter.layout(isA(CandidateList.class)))
.andStubReturn(Optional.of(MozcLayoutUtil.createNiceCandidateLayoutMock(getMockSupport())));
expect(layouter.getPageHeight()).andReturn(50);
candidateWordView.layouter = layouter;
replayAll();
candidateWordView.updateLayouter();
assertNotNull(candidateWordView.layouter);
candidateWordView.update(CANDIDATE_LIST);
// Pre-condition
verifyAll();
assertNotNull(candidateWordView.calculatedLayout);
resetAll();
replayAll();
// Update with null should reset calculatedLayout.
candidateWordView.update(null);
verifyAll();
assertNull(candidateWordView.calculatedLayout);
}
@SmallTest
public void testGetUpdatedScrollPosition() {
class TestData extends Parameter {
final float candidatePosition;
final int scrollPosition;
final float candidateLength;
final int viewLength;
final int expected;
TestData(int scrollPosition, float candidatePosition, float candidateLength, int viewLength,
int expected) {
this.scrollPosition = scrollPosition;
this.candidatePosition = candidatePosition;
this.candidateLength = candidateLength;
this.viewLength = viewLength;
this.expected = expected;
}
}
TestData[] testDataList = {
// Should scroll to forward direction.
new TestData(100, 50, 0, 0, 50),
// Should scroll to backward direction.
new TestData(0, 50, 100, 100, 50),
// Stay here.
new TestData(50, 50, 100, 100, 50),
new TestData(0, 0, 0, 0, 0),
};
CandidateWordView.OrientationTrait trait =
createMock(CandidateWordView.OrientationTrait.class);
CandidateWordView candidateWordView = new StubCandidateWordView(
getInstrumentation().getTargetContext(), trait);
for (TestData testData : testDataList) {
Row row = new Row();
Span span = MozcLayoutUtil.createEmptySpan();
resetAll();
expect(trait.getScrollPosition(candidateWordView)).andReturn(testData.scrollPosition);
expect(trait.getCandidatePosition(row, span)).andReturn(testData.candidatePosition);
expect(trait.getCandidateLength(row, span)).andReturn(testData.candidateLength);
expect(trait.getViewLength(candidateWordView)).andStubReturn(testData.viewLength);
replayAll();
assertEquals(
testData.toString(),
testData.expected,
candidateWordView.getUpdatedScrollPosition(row, span));
verifyAll();
}
}
@SmallTest
public void testUpdateScrollPositionBasedOnFocusedIndex() {
SnapScroller scroller = createMock(SnapScroller.class);
OrientationTrait trait = createMock(CandidateWordView.OrientationTrait.class);
CandidateWordView candidateWordView = new StubCandidateWordView(
getInstrumentation().getTargetContext(), trait);
VisibilityProxy.setField(candidateWordView, "scroller", scroller);
candidateWordView.currentCandidateList =
CandidateList.newBuilder().setFocusedIndex(Integer.MAX_VALUE).build();
CandidateLayout mockLayout = MozcLayoutUtil.createCandidateLayoutMock(getMockSupport());
// No layout is set
scroller.scrollTo(0);
expect(scroller.getScrollPosition()).andReturn(0);
trait.scrollTo(same(candidateWordView), eq(0));
replayAll();
candidateWordView.updateScrollPositionBasedOnFocusedIndex();
verifyAll();
// No correspoinding candidate
resetAll();
expect(mockLayout.getRowList()).andStubReturn(ROW_DATA);
scroller.scrollTo(0);
expect(scroller.getScrollPosition()).andReturn(0);
trait.scrollTo(same(candidateWordView), eq(0));
replayAll();
candidateWordView.calculatedLayout = mockLayout;
candidateWordView.scrollTo(100, 100);
candidateWordView.updateScrollPositionBasedOnFocusedIndex();
verifyAll();
// Correspoinding candidate is found
candidateWordView.currentCandidateList =
CandidateList.newBuilder().setFocusedIndex(0).build();
resetAll();
expect(trait.getScrollPosition(candidateWordView)).andStubReturn(0);
expect(trait.getCandidatePosition(isA(Row.class), isA(Span.class))).andReturn(0f);
expect(trait.getCandidateLength(isA(Row.class), isA(Span.class))).andReturn(0f);
expect(trait.getViewLength(candidateWordView)).andReturn(0);
expect(mockLayout.getRowList()).andStubReturn(ROW_DATA);
scroller.scrollTo(anyInt());
expect(scroller.getScrollPosition()).andReturn(10);
trait.scrollTo(same(candidateWordView), eq(10));
replayAll();
candidateWordView.calculatedLayout = mockLayout;
candidateWordView.updateScrollPositionBasedOnFocusedIndex();
verifyAll();
}
@SuppressLint("WrongCall")
@SmallTest
public void testOnLayout() {
CandidateWordView candWordView = new StubCandidateWordView(
getInstrumentation().getTargetContext(), Orientation.VERTICAL);
CandidateLayouter layouter = createMock(CandidateLayouter.class);
candWordView.layouter = layouter;
replayAll();
// Check pre-condition.
assertNull(candWordView.calculatedLayout);
assertNull(candWordView.currentCandidateList);
verifyAll();
// Call onLayout with empty view size.
resetAll();
expect(layouter.setViewSize(0, 0)).andReturn(false);
replayAll();
candWordView.currentCandidateList = CANDIDATE_LIST;
candWordView.onLayout(true, 0, 0, 0, 0);
verifyAll();
assertNull(candWordView.calculatedLayout);
assertNotNull(candWordView.currentCandidateList);
// Call onLayout with non-empty view size.
resetAll();
expect(layouter.setViewSize(320, 240)).andReturn(true);
expect(layouter.layout(same(CANDIDATE_LIST)))
.andReturn(Optional.of(MozcLayoutUtil.createNiceCandidateLayoutMock(getMockSupport())));
expect(layouter.getPageHeight()).andReturn(50);
replayAll();
candWordView.onLayout(true, 0, 0, 320, 240);
verifyAll();
assertNotNull(candWordView.calculatedLayout);
assertNotNull(candWordView.currentCandidateList);
}
}