package com.jakewharton.rxbinding2.view; import android.annotation.TargetApi; import android.content.Context; import android.support.test.InstrumentationRegistry; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SdkSuppress; import android.support.test.rule.UiThreadTestRule; import android.support.test.runner.AndroidJUnit4; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; import com.jakewharton.rxbinding2.RecordingObserver; import com.jakewharton.rxbinding2.internal.Functions; import io.reactivex.functions.Consumer; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import static android.os.Build.VERSION_CODES.JELLY_BEAN; import static android.os.Build.VERSION_CODES.M; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_HOVER_ENTER; import static android.view.MotionEvent.ACTION_HOVER_EXIT; import static android.view.MotionEvent.ACTION_HOVER_MOVE; import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; import static com.jakewharton.rxbinding.MotionEventUtil.hoverMotionEventAtPosition; import static com.jakewharton.rxbinding.MotionEventUtil.motionEventAtPosition; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @RunWith(AndroidJUnit4.class) public final class RxViewTest { @Rule public final UiThreadTestRule uiThread = new UiThreadTestRule(); private final Context context = InstrumentationRegistry.getContext(); private final View view = new View(context); @Test @UiThreadTest public void clicks() { RecordingObserver<Object> o = new RecordingObserver<>(); RxView.clicks(view).subscribe(o); o.assertNoMoreEvents(); // No initial value. view.performClick(); assertNotNull(o.takeNext()); view.performClick(); assertNotNull(o.takeNext()); o.dispose(); view.performClick(); o.assertNoMoreEvents(); } @Test @UiThreadTest public void drags() { //RecordingObserver<ViewClickEvent> o = new RecordingObserver<>(); //RxView.clickEvents(view).subscribe(o); //o.assertNoMoreEvents(); // No initial value. // //clock.advance(1, SECONDS); //view.performClick(); //assertEquals(ViewClickEvent.create(view, 1000), o.takeNext()); // //clock.advance(1, SECONDS); //view.performClick(); //assertEquals(ViewClickEvent.create(view, 2000), o.takeNext()); // //o.dispose(); // //clock.advance(1, SECONDS); //view.performClick(); //o.assertNoMoreEvents(); } @TargetApi(JELLY_BEAN) @SdkSuppress(minSdkVersion = JELLY_BEAN) @Test @UiThreadTest public void drawEvents() { RecordingObserver<Object> o = new RecordingObserver<>(); RxView.draws(view).subscribe(o); o.assertNoMoreEvents(); // No initial value. view.getViewTreeObserver().dispatchOnDraw(); assertNotNull(o.takeNext()); o.dispose(); view.getViewTreeObserver().dispatchOnDraw(); o.assertNoMoreEvents(); } @Test @UiThreadTest public void focusChanges() { // We need a parent which can take focus from our view when it attempts to clear. LinearLayout parent = new LinearLayout(context); parent.setFocusable(true); parent.addView(view); view.setFocusable(true); RecordingObserver<Boolean> o = new RecordingObserver<>(); RxView.focusChanges(view).subscribe(o); assertFalse(o.takeNext()); view.requestFocus(); assertTrue(o.takeNext()); view.clearFocus(); assertFalse(o.takeNext()); o.dispose(); view.requestFocus(); o.assertNoMoreEvents(); } @Test @UiThreadTest public void globalLayouts() { RecordingObserver<Object> o = new RecordingObserver<>(); RxView.globalLayouts(view).subscribe(o); o.assertNoMoreEvents(); // No initial value. view.getViewTreeObserver().dispatchOnGlobalLayout(); assertNotNull(o.takeNext()); o.dispose(); view.getViewTreeObserver().dispatchOnGlobalLayout(); o.assertNoMoreEvents(); } @Test @UiThreadTest public void hovers() { RecordingObserver<MotionEvent> o = new RecordingObserver<>(); RxView.hovers(view).subscribe(o); o.assertNoMoreEvents(); view.dispatchGenericMotionEvent(hoverMotionEventAtPosition(view, ACTION_HOVER_ENTER, 0, 50)); MotionEvent event1 = o.takeNext(); assertEquals(ACTION_HOVER_ENTER, event1.getAction()); view.dispatchGenericMotionEvent(hoverMotionEventAtPosition(view, ACTION_HOVER_MOVE, 1, 50)); MotionEvent event2 = o.takeNext(); assertEquals(ACTION_HOVER_MOVE, event2.getAction()); o.dispose(); view.dispatchGenericMotionEvent(hoverMotionEventAtPosition(view, ACTION_HOVER_EXIT, 1, 50)); o.assertNoMoreEvents(); } @Test @UiThreadTest public void layoutChanges() { RecordingObserver<Object> o = new RecordingObserver<>(); RxView.layoutChanges(view).subscribe(o); o.assertNoMoreEvents(); view.layout(view.getLeft() - 5, view.getTop() - 5, view.getRight(), view.getBottom()); assertNotNull(o.takeNext()); view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); o.assertNoMoreEvents(); o.dispose(); view.layout(view.getLeft() - 5, view.getTop() - 5, view.getRight(), view.getBottom()); o.assertNoMoreEvents(); } @Test @UiThreadTest public void layoutChangeEvents() { RecordingObserver<ViewLayoutChangeEvent> o = new RecordingObserver<>(); RxView.layoutChangeEvents(view).subscribe(o); o.assertNoMoreEvents(); view.layout(view.getLeft() - 5, view.getTop() - 5, view.getRight(), view.getBottom()); ViewLayoutChangeEvent event1 = o.takeNext(); assertSame(view, event1.view()); assertNotSame(event1.oldLeft(), event1.left()); assertSame(event1.oldRight(), event1.right()); view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); o.assertNoMoreEvents(); o.dispose(); view.layout(view.getLeft() - 5, view.getTop() - 5, view.getRight(), view.getBottom()); o.assertNoMoreEvents(); } @Test @UiThreadTest public void longClicks() { // We need a parent because long presses delegate to the parent. LinearLayout parent = new LinearLayout(context) { @Override public boolean showContextMenuForChild(View originalView) { return true; } }; parent.addView(view); RecordingObserver<Object> o = new RecordingObserver<>(); RxView.longClicks(view).subscribe(o); o.assertNoMoreEvents(); // No initial value. view.performLongClick(); assertNotNull(o.takeNext()); view.performLongClick(); assertNotNull(o.takeNext()); o.dispose(); view.performLongClick(); o.assertNoMoreEvents(); } @Test @UiThreadTest public void preDrawEvents() { RecordingObserver<Object> o = new RecordingObserver<>(); RxView.preDraws(view, Functions.CALLABLE_ALWAYS_TRUE).subscribe(o); o.assertNoMoreEvents(); // No initial value. view.getViewTreeObserver().dispatchOnPreDraw(); assertNotNull(o.takeNext()); o.dispose(); view.getViewTreeObserver().dispatchOnPreDraw(); o.assertNoMoreEvents(); } @TargetApi(M) @SdkSuppress(minSdkVersion = M) @Test @UiThreadTest public void scrollChangeEvents() { RecordingObserver<ViewScrollChangeEvent> o = new RecordingObserver<>(); RxView.scrollChangeEvents(view).subscribe(o); o.assertNoMoreEvents(); view.scrollTo(1, 1); ViewScrollChangeEvent event0 = o.takeNext(); assertSame(view, event0.view()); assertEquals(1, event0.scrollX()); assertEquals(1, event0.scrollY()); assertEquals(0, event0.oldScrollX()); assertEquals(0, event0.oldScrollY()); view.scrollTo(2, 2); ViewScrollChangeEvent event1 = o.takeNext(); assertSame(view, event1.view()); assertEquals(2, event1.scrollX()); assertEquals(2, event1.scrollY()); assertEquals(1, event1.oldScrollX()); assertEquals(1, event1.oldScrollY()); o.dispose(); view.scrollTo(3, 3); o.assertNoMoreEvents(); } @Test @UiThreadTest public void touches() { RecordingObserver<MotionEvent> o = new RecordingObserver<>(); RxView.touches(view).subscribe(o); o.assertNoMoreEvents(); view.dispatchTouchEvent(motionEventAtPosition(view, ACTION_DOWN, 0, 50)); MotionEvent event1 = o.takeNext(); assertEquals(ACTION_DOWN, event1.getAction()); view.dispatchTouchEvent(motionEventAtPosition(view, ACTION_MOVE, 1, 50)); MotionEvent event2 = o.takeNext(); assertEquals(ACTION_MOVE, event2.getAction()); o.dispose(); view.dispatchTouchEvent(motionEventAtPosition(view, ACTION_UP, 1, 50)); o.assertNoMoreEvents(); } @Test @UiThreadTest public void keys() { RecordingObserver<KeyEvent> o = new RecordingObserver<>(); RxView.keys(view).subscribe(o); o.assertNoMoreEvents(); view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_R)); KeyEvent event1 = o.takeNext(); assertEquals(KeyEvent.ACTION_DOWN, event1.getAction()); view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_H)); KeyEvent event2 = o.takeNext(); assertEquals(KeyEvent.KEYCODE_H, event2.getKeyCode()); o.dispose(); view.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_S)); o.assertNoMoreEvents(); } @Test @UiThreadTest public void activated() throws Exception { view.setActivated(true); Consumer<? super Boolean> action = RxView.activated(view); action.accept(false); assertFalse(view.isActivated()); action.accept(true); assertTrue(view.isActivated()); } @Test @UiThreadTest public void clickable() throws Exception { view.setClickable(true); Consumer<? super Boolean> action = RxView.clickable(view); action.accept(false); assertFalse(view.isClickable()); action.accept(true); assertTrue(view.isClickable()); } @Test @UiThreadTest public void enabled() throws Exception { view.setEnabled(true); Consumer<? super Boolean> action = RxView.enabled(view); action.accept(false); assertFalse(view.isEnabled()); action.accept(true); assertTrue(view.isEnabled()); } @Test @UiThreadTest public void pressed() throws Exception { view.setPressed(true); Consumer<? super Boolean> action = RxView.pressed(view); action.accept(false); assertFalse(view.isPressed()); action.accept(true); assertTrue(view.isPressed()); } @Test @UiThreadTest public void selected() throws Exception { view.setSelected(true); Consumer<? super Boolean> action = RxView.selected(view); action.accept(false); assertFalse(view.isSelected()); action.accept(true); assertTrue(view.isSelected()); } @Test @UiThreadTest public void visibility() throws Exception { view.setVisibility(View.VISIBLE); Consumer<? super Boolean> action = RxView.visibility(view); action.accept(false); assertEquals(View.GONE, view.getVisibility()); action.accept(true); assertEquals(View.VISIBLE, view.getVisibility()); } @Test @UiThreadTest public void visibilityCustomFalse() throws Exception { view.setVisibility(View.VISIBLE); Consumer<? super Boolean> action = RxView.visibility(view, View.INVISIBLE); action.accept(false); assertEquals(View.INVISIBLE, view.getVisibility()); action.accept(true); assertEquals(View.VISIBLE, view.getVisibility()); } @SuppressWarnings("ResourceType") @Test @UiThreadTest public void setVisibilityCustomFalseToVisibleThrows() { try { RxView.visibility(view, View.VISIBLE); fail(); } catch (IllegalArgumentException e) { assertEquals("Setting visibility to VISIBLE when false would have no effect.", e.getMessage()); } } }