1 /*
2  * Copyright 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.recyclerview.selection;
18 
19 import static androidx.recyclerview.selection.testing.TestEvents.Mouse.ALT_CLICK;
20 import static androidx.recyclerview.selection.testing.TestEvents.Mouse.CLICK;
21 import static androidx.recyclerview.selection.testing.TestEvents.Mouse.CTRL_CLICK;
22 import static androidx.recyclerview.selection.testing.TestEvents.Mouse.SECONDARY_CLICK;
23 import static androidx.recyclerview.selection.testing.TestEvents.Mouse.SHIFT_CLICK;
24 import static androidx.recyclerview.selection.testing.TestEvents.Mouse.TERTIARY_CLICK;
25 
26 import static org.junit.Assert.assertFalse;
27 import static org.junit.Assert.assertTrue;
28 
29 import android.view.MotionEvent;
30 
31 import androidx.recyclerview.selection.testing.SelectionProbe;
32 import androidx.recyclerview.selection.testing.SelectionTrackers;
33 import androidx.recyclerview.selection.testing.TestAdapter;
34 import androidx.recyclerview.selection.testing.TestData;
35 import androidx.recyclerview.selection.testing.TestEvents;
36 import androidx.recyclerview.selection.testing.TestFocusDelegate;
37 import androidx.recyclerview.selection.testing.TestItemDetails;
38 import androidx.recyclerview.selection.testing.TestItemDetailsLookup;
39 import androidx.recyclerview.selection.testing.TestItemKeyProvider;
40 import androidx.recyclerview.selection.testing.TestOnContextClickListener;
41 import androidx.recyclerview.selection.testing.TestOnItemActivatedListener;
42 import androidx.recyclerview.widget.RecyclerView;
43 import androidx.test.ext.junit.runners.AndroidJUnit4;
44 import androidx.test.filters.SmallTest;
45 
46 import org.junit.Before;
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 
50 @RunWith(AndroidJUnit4.class)
51 @SmallTest
52 public final class MouseInputHandlerTest {
53 
54     private MouseInputHandler<String> mInputDelegate;
55 
56     private TestOnContextClickListener mMouseCallbacks;
57     private TestOnItemActivatedListener<String> mActivationCallbacks;
58     private TestFocusDelegate<String> mFocusCallbacks;
59 
60     private TestItemDetailsLookup mDetailsLookup;
61     private SelectionProbe mSelection;
62     private SelectionTracker<String> mSelectionMgr;
63 
64     private TestEvents.Builder mEvent;
65 
66     @Before
setUp()67     public void setUp() {
68 
69         mSelectionMgr = SelectionTrackers.createStringTracker("mouse-input-test", 100);
70         mDetailsLookup = new TestItemDetailsLookup();
71         mSelection = new SelectionProbe(mSelectionMgr);
72 
73         mMouseCallbacks = new TestOnContextClickListener();
74         mActivationCallbacks = new TestOnItemActivatedListener<>();
75         mFocusCallbacks = new TestFocusDelegate<>();
76 
77         mInputDelegate = new MouseInputHandler<>(
78                 mSelectionMgr,
79                 new TestItemKeyProvider<>(
80                         ItemKeyProvider.SCOPE_MAPPED,
81                         new TestAdapter<>(TestData.createStringData(100))),
82                 mDetailsLookup,
83                 mMouseCallbacks,
84                 mActivationCallbacks,
85                 mFocusCallbacks);
86 
87         mEvent = TestEvents.builder().mouse();
88         mDetailsLookup.initAt(RecyclerView.NO_POSITION);
89     }
90 
91     @Test
testConfirmedClick_StartsSelection()92     public void testConfirmedClick_StartsSelection() {
93         mDetailsLookup.initAt(11).setInItemSelectRegion(true);
94         mInputDelegate.onSingleTapConfirmed(CLICK);
95 
96         mSelection.assertSelection(11);
97     }
98 
99     @Test
testClickOnSelectRegion_AddsToSelection()100     public void testClickOnSelectRegion_AddsToSelection() {
101         mDetailsLookup.initAt(11).setInItemSelectRegion(true);
102         mInputDelegate.onSingleTapConfirmed(CLICK);
103 
104         mDetailsLookup.initAt(10).setInItemSelectRegion(true);
105         mInputDelegate.onSingleTapUp(CLICK);
106 
107         mSelection.assertSelected(10, 11);
108     }
109 
110     @Test
testClickOnIconOfSelectedItem_RemovesFromSelection()111     public void testClickOnIconOfSelectedItem_RemovesFromSelection() {
112         mDetailsLookup.initAt(8).setInItemSelectRegion(true);
113         mInputDelegate.onSingleTapConfirmed(CLICK);
114 
115         mDetailsLookup.initAt(11);
116         mInputDelegate.onSingleTapUp(SHIFT_CLICK);
117         mSelection.assertSelected(8, 9, 10, 11);
118 
119         mDetailsLookup.initAt(9);
120         mInputDelegate.onSingleTapUp(CLICK);
121         mSelection.assertSelected(8, 10, 11);
122     }
123 
124     @Test
testRightClickDown_StartsContextMenu()125     public void testRightClickDown_StartsContextMenu() {
126         mInputDelegate.onDown(SECONDARY_CLICK);
127 
128         mMouseCallbacks.assertLastEvent(SECONDARY_CLICK);
129     }
130 
131     @Test
testAltClickDown_StartsContextMenu()132     public void testAltClickDown_StartsContextMenu() {
133         mInputDelegate.onDown(ALT_CLICK);
134 
135         mMouseCallbacks.assertLastEvent(ALT_CLICK);
136     }
137 
138     @Test
testScroll_shouldTrap()139     public void testScroll_shouldTrap() {
140         mDetailsLookup.initAt(0);
141         assertTrue(mInputDelegate.onScroll(
142                 null,
143                 mEvent.action(MotionEvent.ACTION_MOVE).primary().build(),
144                 -1,
145                 -1));
146     }
147 
148     @Test
testScroll_NoTrapForTwoFinger()149     public void testScroll_NoTrapForTwoFinger() {
150         mDetailsLookup.initAt(0);
151         assertFalse(mInputDelegate.onScroll(
152                 null,
153                 mEvent.action(MotionEvent.ACTION_MOVE).build(),
154                 -1,
155                 -1));
156     }
157 
158     @Test
testUnconfirmedCtrlClick_AddsToExistingSelection()159     public void testUnconfirmedCtrlClick_AddsToExistingSelection() {
160         mDetailsLookup.initAt(7).setInItemSelectRegion(true);
161         mInputDelegate.onSingleTapConfirmed(CLICK);
162 
163         mDetailsLookup.initAt(11);
164         mInputDelegate.onSingleTapUp(CTRL_CLICK);
165 
166         mSelection.assertSelection(7, 11);
167     }
168 
169     @Test
testUnconfirmedShiftClick_ExtendsSelection()170     public void testUnconfirmedShiftClick_ExtendsSelection() {
171         mDetailsLookup.initAt(7).setInItemSelectRegion(true);
172         mInputDelegate.onSingleTapConfirmed(CLICK);
173 
174         mDetailsLookup.initAt(11);
175         mInputDelegate.onSingleTapUp(SHIFT_CLICK);
176 
177         mSelection.assertSelection(7, 8, 9, 10, 11);
178     }
179 
180     @Test
testConfirmedShiftClick_ExtendsSelectionFromFocus()181     public void testConfirmedShiftClick_ExtendsSelectionFromFocus() {
182         TestItemDetails item = mDetailsLookup.initAt(7);
183         mFocusCallbacks.focusItem(item);
184 
185         // There should be no selected item at this point, just focus on "7".
186         mDetailsLookup.initAt(11);
187         mInputDelegate.onSingleTapConfirmed(SHIFT_CLICK);
188         mSelection.assertSelection(7, 8, 9, 10, 11);
189     }
190 
191     @Test
testUnconfirmedShiftClick_RotatesAroundOrigin()192     public void testUnconfirmedShiftClick_RotatesAroundOrigin() {
193         mDetailsLookup.initAt(7).setInItemSelectRegion(true);
194         mInputDelegate.onSingleTapConfirmed(CLICK);
195 
196         mDetailsLookup.initAt(11);
197         mInputDelegate.onSingleTapUp(SHIFT_CLICK);
198         mSelection.assertSelection(7, 8, 9, 10, 11);
199 
200         mDetailsLookup.initAt(5);
201         mInputDelegate.onSingleTapUp(SHIFT_CLICK);
202 
203         mSelection.assertSelection(5, 6, 7);
204         mSelection.assertNotSelected(8, 9, 10, 11);
205     }
206 
207     @Test
testUnconfirmedShiftCtrlClick_Combination()208     public void testUnconfirmedShiftCtrlClick_Combination() {
209         mDetailsLookup.initAt(7).setInItemSelectRegion(true);
210         mInputDelegate.onSingleTapConfirmed(CLICK);
211 
212         mDetailsLookup.initAt(11);
213         mInputDelegate.onSingleTapUp(SHIFT_CLICK);
214         mSelection.assertSelection(7, 8, 9, 10, 11);
215 
216         mDetailsLookup.initAt(5);
217         mInputDelegate.onSingleTapUp(CTRL_CLICK);
218 
219         mSelection.assertSelection(5, 7, 8, 9, 10, 11);
220     }
221 
222     @Test
testUnconfirmedShiftCtrlClick_ShiftTakesPriority()223     public void testUnconfirmedShiftCtrlClick_ShiftTakesPriority() {
224         mDetailsLookup.initAt(7).setInItemSelectRegion(true);
225         mInputDelegate.onSingleTapConfirmed(CLICK);
226 
227         mDetailsLookup.initAt(11);
228         mInputDelegate.onSingleTapUp(mEvent.ctrl().shift().build());
229 
230         mSelection.assertSelection(7, 8, 9, 10, 11);
231     }
232 
233     // TODO: Add testSpaceBar_Previews, but we need to set a system property
234     // to have a deterministic state.
235 
236     @Test
testDoubleClick_Opens()237     public void testDoubleClick_Opens() {
238         TestItemDetails doc = mDetailsLookup.initAt(11);
239         mInputDelegate.onDoubleTap(CLICK);
240 
241         mActivationCallbacks.assertActivated(doc);
242     }
243 
244     @Test
testMiddleClick_DoesNothing()245     public void testMiddleClick_DoesNothing() {
246         mDetailsLookup.initAt(11).setInItemSelectRegion(true);
247         mInputDelegate.onSingleTapConfirmed(TERTIARY_CLICK);
248 
249         mSelection.assertNoSelection();
250     }
251 
252     @Test
testClickOff_ClearsSelection()253     public void testClickOff_ClearsSelection() {
254         mDetailsLookup.initAt(11).setInItemSelectRegion(true);
255         mInputDelegate.onSingleTapConfirmed(CLICK);
256 
257         mDetailsLookup.initAt(RecyclerView.NO_POSITION);
258         mInputDelegate.onSingleTapUp(CLICK);
259 
260         mSelection.assertNoSelection();
261     }
262 
263     @Test
testClick_Focuses()264     public void testClick_Focuses() {
265         mDetailsLookup.initAt(11).setInItemSelectRegion(false);
266         mInputDelegate.onSingleTapConfirmed(CLICK);
267 
268         mFocusCallbacks.assertHasFocus(true);
269         mFocusCallbacks.assertFocused("11");
270     }
271 
272     @Test
testClickOff_ClearsFocus()273     public void testClickOff_ClearsFocus() {
274         mDetailsLookup.initAt(11).setInItemSelectRegion(false);
275         mInputDelegate.onSingleTapConfirmed(CLICK);
276         mFocusCallbacks.assertHasFocus(true);
277 
278         mDetailsLookup.initAt(RecyclerView.NO_POSITION);
279         mInputDelegate.onSingleTapUp(CLICK);
280         mFocusCallbacks.assertHasFocus(false);
281     }
282 
283     @Test
testClickOffSelection_RemovesSelectionAndFocuses()284     public void testClickOffSelection_RemovesSelectionAndFocuses() {
285         mDetailsLookup.initAt(1).setInItemSelectRegion(true);
286         mInputDelegate.onSingleTapConfirmed(CLICK);
287 
288         mDetailsLookup.initAt(5);
289         mInputDelegate.onSingleTapUp(SHIFT_CLICK);
290 
291         mSelection.assertSelection(1, 2, 3, 4, 5);
292 
293         mDetailsLookup.initAt(11);
294         mInputDelegate.onSingleTapUp(CLICK);
295 
296         mFocusCallbacks.assertFocused("11");
297         mSelection.assertNoSelection();
298     }
299 }
300