/* * Copyright (C) 2015 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.android.switchaccess.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import com.android.switchaccess.*; import com.android.talkback.BuildConfig; import com.android.talkback.R; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; import android.view.View; import android.os.Build; import android.view.accessibility.AccessibilityNodeInfo; import android.util.DisplayMetrics; import android.util.TypedValue; import android.widget.LinearLayout; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.robolectric.Robolectric; import org.robolectric.RobolectricGradleTestRunner; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.fakes.RoboSharedPreferences; import org.robolectric.internal.ShadowExtractor; import org.robolectric.shadows.ShadowHandler; import org.robolectric.shadows.ShadowPreferenceManager; import java.util.Collection; import java.util.Iterator; import java.util.List; /** * Tests for OptionManager */ @Config( constants = BuildConfig.class, sdk = 21, shadows = { ShadowAccessibilityNodeInfo.class, ShadowAccessibilityNodeInfo.ShadowAccessibilityAction.class}) @TargetApi(Build.VERSION_CODES.LOLLIPOP) @RunWith(RobolectricGradleTestRunner.class) public class OptionManagerTest { private static final Rect NODE_BOUNDS_1 = new Rect(10, 10, 90, 20); private static final Rect NODE_BOUNDS_2 = new Rect(110, 110, 190, 120); private static final Rect MENU_BUTTON_BOUNDS = new Rect(150, 10, 250, 60); private static final int DEFAULT_HIGHLIGHT_COLOR = 0x4caf50; private static final int ORANGE_500_COLOR = 0xff9800; private static final int BLUE_500_COLOR = 0x2196f3; private final OverlayController mOverlayController = mock(OverlayController.class); private final OptionManager.OptionManagerListener mMockListener = mock(OptionManager.OptionManagerListener.class); private final Context mContext = RuntimeEnvironment.application.getApplicationContext(); private final RoboSharedPreferences mSharedPreferences = (RoboSharedPreferences) ShadowPreferenceManager.getDefaultSharedPreferences(mContext); private OptionManager mOptionManager; private SwitchAccessNodeCompat mCompat1, mCompat2; private AccessibilityNodeActionNode mActionNode1, mActionNode2; private OptionScanSelectionNode mSelectionNode; ShadowAccessibilityNodeInfo mShadowInfo1, mShadowInfo2; @Captor ArgumentCaptor<Collection<Rect>> mHighlightCaptor; @Captor ArgumentCaptor<Paint> mPaintCaptor; @Before public void setUp() { ShadowAccessibilityNodeInfo.resetObtainedInstances(); mCompat1 = new SwitchAccessNodeCompat(AccessibilityNodeInfo.obtain()); mShadowInfo1 = (ShadowAccessibilityNodeInfo) ShadowExtractor.extract((AccessibilityNodeInfo) mCompat1.getInfo()); mCompat2 = new SwitchAccessNodeCompat(AccessibilityNodeInfo.obtain()); mShadowInfo2 = (ShadowAccessibilityNodeInfo) ShadowExtractor.extract((AccessibilityNodeInfo) mCompat2.getInfo()); mSharedPreferences.edit().clear().commit(); MockitoAnnotations.initMocks(this); mCompat1.setBoundsInScreen(NODE_BOUNDS_1); mCompat2.setBoundsInScreen(NODE_BOUNDS_2); mActionNode1 = new AccessibilityNodeActionNode(mCompat1, new AccessibilityNodeInfoCompat.AccessibilityActionCompat( AccessibilityNodeInfoCompat.ACTION_CLICK, "label1")); mActionNode2 = new AccessibilityNodeActionNode(mCompat2, new AccessibilityNodeInfoCompat.AccessibilityActionCompat( AccessibilityNodeInfoCompat.ACTION_CLICK, "label2")); mSelectionNode = new OptionScanSelectionNode(mActionNode1, mActionNode2); when(mOverlayController.getContext()).thenReturn(mContext); mOptionManager = new OptionManager(mOverlayController); } @After public void tearDown() { if (mSelectionNode != null) { mSelectionNode.recycle(); } mCompat1.recycle(); mCompat2.recycle(); try { assertFalse(ShadowAccessibilityNodeInfo.areThereUnrecycledNodes(true)); } finally { ShadowAccessibilityNodeInfo.resetObtainedInstances(); } } @Test public void testListeningForSharedPreferenceChange() { assertTrue(mSharedPreferences.hasListener(mOptionManager)); } @Test public void testUnregisterSharedPreferenceChangeListener() { mOptionManager.shutdown(); assertFalse(mSharedPreferences.hasListener(mOptionManager)); } @Test public void testNewTreeAfterShutdown_shouldNotCrash() { mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.shutdown(); mOptionManager.clearFocusIfNewTree(null); /* Everything should be recycled at this point */ mSelectionNode = null; } @Test public void testClearFocusIfNewTree_shouldIgnoreIdenticalTrees() { mOptionManager.clearFocusIfNewTree(null); verify(mOverlayController, times(0)).clearOverlay(); mOptionManager.clearFocusIfNewTree(mActionNode1); verify(mOverlayController, times(1)).clearOverlay(); mOptionManager.clearFocusIfNewTree(mActionNode1); verify(mOverlayController, times(1)).clearOverlay(); mOptionManager.clearFocusIfNewTree(mActionNode2); verify(mOverlayController, times(2)).clearOverlay(); mOptionManager.clearFocusIfNewTree(null); verify(mOverlayController, times(3)).clearOverlay(); mSelectionNode = null; } @Test public void testClearFocusIfNewTree_shouldClearFocusOnNewTree() { mOptionManager.addOptionManagerListener(mMockListener); mOptionManager.clearFocusIfNewTree(mSelectionNode); verify(mMockListener, times(1)).onOptionManagerClearedFocus(); } @Test public void testOnlyOneActionNode_shouldDoNothingBeforeSelection() { mOptionManager.clearFocusIfNewTree(mActionNode1); assertEquals(0, mShadowInfo1.getPerformedActions().size()); } @Test public void testOnlyOneActionNode_shouldPerformActionOnFirstSelect() { mOptionManager.clearFocusIfNewTree(mActionNode1); mOptionManager.selectOption(0); assertEquals(1, mShadowInfo1.getPerformedActions().size()); } @Test public void testOnlyOneActionNode_shouldClearFocusAndOverlayWhenActionPerformed() { mOptionManager.addOptionManagerListener(mMockListener); mOptionManager.clearFocusIfNewTree(mActionNode1); mOptionManager.selectOption(0); verify(mMockListener, times(2)).onOptionManagerClearedFocus(); verify(mOverlayController, times(2)).clearOverlay(); } @Test public void testNegativeSelection_shouldBeIgnored() { mOptionManager.clearFocusIfNewTree(mActionNode1); mOptionManager.selectOption(-1); assertEquals(0, mShadowInfo1.getPerformedActions().size()); } @Test public void testSelectionNodeWithSettingsCleared_shouldHighlightWithDefault() { mSharedPreferences.edit().clear().commit(); mOptionManager = new OptionManager(mOverlayController); mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); verify(mOverlayController, times(2)) .highlightPerimeterOfRects(mHighlightCaptor.capture(), mPaintCaptor.capture()); /* One paint should be transparent; the other should be the default green */ List<Collection<Rect>> capturedHighlights = mHighlightCaptor.getAllValues(); List<Paint> capturedPaints = mPaintCaptor.getAllValues(); int defaultIndex = 0; int transparentIndex = 1; if (capturedPaints.get(0).getColor() == Color.TRANSPARENT) { transparentIndex = 0; defaultIndex = 1; } assertEquals(Color.TRANSPARENT, capturedPaints.get(transparentIndex).getColor()); assertEquals(DEFAULT_HIGHLIGHT_COLOR, capturedPaints.get(defaultIndex).getColor()); assertEquals(0xff, capturedPaints.get(defaultIndex).getAlpha()); DisplayMetrics dm = mContext.getResources().getDisplayMetrics(); float expectedStrokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, dm); assertEquals(expectedStrokeWidth, capturedPaints.get(defaultIndex).getStrokeWidth(), 0.01); assertTrue(capturedHighlights.get(defaultIndex).contains(NODE_BOUNDS_1)); assertTrue(capturedHighlights.get(transparentIndex).contains(NODE_BOUNDS_2)); } @Test public void testSelectionNodeWithoutOptionScanning_shouldHighlightOneOption() { mSharedPreferences.edit() .putString(mContext.getString(R.string.pref_highlight_0_color_key), mContext.getString(R.string.material_orange_500)) .putString(mContext.getString(R.string.pref_highlight_0_weight_key), mContext.getString(R.string.thickness_4_dp)) .putString(mContext.getString(R.string.pref_highlight_1_color_key), mContext.getString(R.string.material_blue_500)) .putString(mContext.getString(R.string.pref_highlight_1_weight_key), mContext.getString(R.string.thickness_4_dp)) .commit(); mOptionManager.onSharedPreferenceChanged(mSharedPreferences, null); mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); verify(mOverlayController, times(2)) .highlightPerimeterOfRects(mHighlightCaptor.capture(), mPaintCaptor.capture()); /* One paint should be transparent; the other should be orange */ List<Collection<Rect>> capturedHighlights = mHighlightCaptor.getAllValues(); List<Paint> capturedPaints = mPaintCaptor.getAllValues(); int highlightIndex = 0; int transparentIndex = 1; if (capturedPaints.get(0).getAlpha() == 0) { transparentIndex = 0; highlightIndex = 1; } assertEquals(Color.TRANSPARENT, capturedPaints.get(transparentIndex).getColor()); assertEquals(ORANGE_500_COLOR, capturedPaints.get(highlightIndex).getColor()); assertEquals(0xff, capturedPaints.get(highlightIndex).getAlpha()); DisplayMetrics dm = mContext.getResources().getDisplayMetrics(); float expectedStrokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, dm); assertEquals(expectedStrokeWidth, capturedPaints.get(highlightIndex).getStrokeWidth(), 0.01); assertTrue(capturedHighlights.get(highlightIndex).contains(NODE_BOUNDS_1)); assertTrue(capturedHighlights.get(transparentIndex).contains(NODE_BOUNDS_2)); } @Test public void testSelectionNodeWithOptionScanning_shouldHighlightTwoOptions() { mSharedPreferences.edit() .putString(mContext.getString(R.string.pref_highlight_0_color_key), mContext.getString(R.string.material_orange_500)) .putString(mContext.getString(R.string.pref_highlight_0_weight_key), mContext.getString(R.string.thickness_4_dp)) .putString(mContext.getString(R.string.pref_highlight_1_color_key), mContext.getString(R.string.material_blue_500)) .putString(mContext.getString(R.string.pref_highlight_1_weight_key), mContext.getString(R.string.thickness_4_dp)) .putString(mContext.getString(R.string.pref_scanning_methods_key), mContext.getString(R.string.option_scanning_key)) .commit(); mOptionManager.onSharedPreferenceChanged(mSharedPreferences, null); mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); verify(mOverlayController, times(2)) .highlightPerimeterOfRects(mHighlightCaptor.capture(), mPaintCaptor.capture()); /* One paint should be blue; the other should be orange */ List<Collection<Rect>> capturedHighlights = mHighlightCaptor.getAllValues(); List<Paint> capturedPaints = mPaintCaptor.getAllValues(); int orangeIndex = 0; int blueIndex = 1; if (capturedPaints.get(0).getColor() == BLUE_500_COLOR) { blueIndex = 0; orangeIndex = 1; } assertEquals(BLUE_500_COLOR, capturedPaints.get(blueIndex).getColor()); assertEquals(ORANGE_500_COLOR, capturedPaints.get(orangeIndex).getColor()); assertEquals(0xff, capturedPaints.get(orangeIndex).getAlpha()); DisplayMetrics dm = mContext.getResources().getDisplayMetrics(); float expectedStrokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, dm); assertEquals(expectedStrokeWidth, capturedPaints.get(orangeIndex).getStrokeWidth(), 0.01); assertTrue(capturedHighlights.get(orangeIndex).contains(NODE_BOUNDS_1)); assertTrue(capturedHighlights.get(blueIndex).contains(NODE_BOUNDS_2)); } @Test public void testMoveToParent_nullTree_shouldNotCrash() { mOptionManager.clearFocusIfNewTree(null); mOptionManager.moveToParent(false); mOptionManager.moveToParent(true); } @Test public void testOptionScanningEnabled_highlightsMenuButton() { mSharedPreferences.edit() .putString(mContext.getString(R.string.pref_scanning_methods_key), mContext.getString(R.string.option_scanning_key)) .commit(); mOptionManager.onSharedPreferenceChanged(mSharedPreferences, null); when(mOverlayController.getMenuButtonLocation()).thenReturn(MENU_BUTTON_BOUNDS); /* add a context menu with two items to the tree */ CharSequence globalActionLabel0 = "global action label 0"; CharSequence globalActionLabel1 = "global action label 1"; GlobalActionNode globalNode0 = new GlobalActionNode(0, null, globalActionLabel0); GlobalActionNode globalNode1 = new GlobalActionNode(1, null, globalActionLabel1); ContextMenuNode contextMenu = new ContextMenuNode(globalNode0, globalNode1); mSelectionNode = new OptionScanSelectionNode(mSelectionNode, contextMenu); mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); verify(mOverlayController, times(1)).drawMenuButton(); ShadowHandler.runMainLooperToEndOfTasks(); verify(mOverlayController, times(2)).highlightPerimeterOfRects( mHighlightCaptor.capture(), mPaintCaptor.capture()); List<Collection<Rect>> capturedHighlights = mHighlightCaptor.getAllValues(); assertTrue(capturedHighlights.get(0).contains(NODE_BOUNDS_1)); assertTrue(capturedHighlights.get(0).contains(NODE_BOUNDS_2)); assertTrue(capturedHighlights.get(1).contains(MENU_BUTTON_BOUNDS)); } @Test public void testOptionScanningDisabled_NoMenuButtonDrawn() { /* add a context menu with two items to the tree */ CharSequence globalActionLabel0 = "global action label 0"; CharSequence globalActionLabel1 = "global action label 1"; GlobalActionNode globalNode0 = new GlobalActionNode(0, null, globalActionLabel0); GlobalActionNode globalNode1 = new GlobalActionNode(1, null, globalActionLabel1); ContextMenuNode contextMenu = new ContextMenuNode(globalNode0, globalNode1); mSelectionNode = new OptionScanSelectionNode(mSelectionNode, contextMenu); mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); verify(mOverlayController, times(0)).drawMenuButton(); } @Test public void testMoveToParent_hasParent_shouldRefocusOnParent() { OptionScanSelectionNode mockSelectionNode = mock(OptionScanSelectionNode.class); mSelectionNode.setParent(mockSelectionNode); mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); mOptionManager.moveToParent(false); verify(mockSelectionNode, times(1)).performAction(); } @Test public void testMoveToParent_nowhereToGoNoWrap_shouldClearFocus() { mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); mOptionManager.moveToParent(false); /* Confirm that we're on null selection by moving to a child */ mOptionManager.selectOption(0); assertEquals(0, mShadowInfo1.getPerformedActions().size()); mOptionManager.selectOption(0); assertEquals(1, mShadowInfo1.getPerformedActions().size()); } @Test public void testMoveToParent_nowhereToGoWithWrap_shouldClearFocus() { mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); mOptionManager.moveToParent(true); /* Confirm that we're on null selection by moving to a child */ mOptionManager.selectOption(0); assertEquals(0, mShadowInfo1.getPerformedActions().size()); mOptionManager.selectOption(0); assertEquals(1, mShadowInfo1.getPerformedActions().size()); } @Test public void testMoveToParent_withWrap_shouldCleaFocus() { OptionScanSelectionNode topSelectionNode = new OptionScanSelectionNode(mActionNode1, mSelectionNode); mOptionManager.clearFocusIfNewTree(topSelectionNode); mOptionManager.selectOption(0); mOptionManager.moveToParent(true); /* Confirm that we're on null selection by moving to a child */ mOptionManager.selectOption(0); assertEquals(0, mShadowInfo1.getPerformedActions().size()); mOptionManager.selectOption(0); assertEquals(1, mShadowInfo1.getPerformedActions().size()); } @Test public void testMoveToParent_fromNull_shouldWrap() { OptionScanSelectionNode topSelectionNode = new OptionScanSelectionNode(mActionNode1, mSelectionNode); mOptionManager.clearFocusIfNewTree(topSelectionNode); mOptionManager.moveToParent(true); /* Confirm that we're on mSelectionNode by moving to a child */ mOptionManager.selectOption(0); assertEquals(1, mShadowInfo1.getPerformedActions().size()); } @Test public void testLongClickWithOneOptionThatSucceeds_shouldLongClickAndClearFocus() { mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); mOptionManager.performLongClick(); List<Integer> info1Actions = mShadowInfo1.getPerformedActions(); assertEquals(1, info1Actions.size()); assertEquals(new Integer(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK), info1Actions.get(0)); verify(mOverlayController, times(3)).clearOverlay(); } @Test public void testLongClickWithTwoOptions_shouldDoNothing() { OptionScanSelectionNode superParentNode = new OptionScanSelectionNode(mSelectionNode, mSelectionNode); mOptionManager.clearFocusIfNewTree(superParentNode); mOptionManager.selectOption(0); mOptionManager.performLongClick(); assertEquals(0, mShadowInfo1.getPerformedActions().size()); assertEquals(0, mShadowInfo2.getPerformedActions().size()); } @Test public void testScrollWithOneOptionThatSucceeds_shouldScrollAndClearFocus() { mCompat1.setScrollable(true); mActionNode1.recycle(); mActionNode1 = new AccessibilityNodeActionNode(mCompat1, new AccessibilityNodeInfoCompat.AccessibilityActionCompat( AccessibilityNodeInfoCompat.ACTION_CLICK, "label1")); mSelectionNode = new OptionScanSelectionNode(mActionNode1, mActionNode2); mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); mOptionManager.performScrollAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); List<Integer> info1Actions = mShadowInfo1.getPerformedActions(); assertEquals(1, info1Actions.size()); assertEquals(new Integer(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD), info1Actions.get(0)); verify(mOverlayController, times(3)).clearOverlay(); } @Test public void testScrollWithParent_shouldScrollAndClearFocus() { AccessibilityNodeInfoCompat parent = AccessibilityNodeInfoCompat.obtain(); ShadowAccessibilityNodeInfo shadowParent = (ShadowAccessibilityNodeInfo) ShadowExtractor.extract(parent.getInfo()); shadowParent.addChild((AccessibilityNodeInfo) mCompat1.getInfo()); parent.setScrollable(true); mActionNode1.recycle(); mActionNode1 = new AccessibilityNodeActionNode(mCompat1, new AccessibilityNodeInfoCompat.AccessibilityActionCompat( AccessibilityNodeInfoCompat.ACTION_CLICK, "label1")); mSelectionNode = new OptionScanSelectionNode(mActionNode1, mActionNode2); mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); mOptionManager.performScrollAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); assertEquals(0, mShadowInfo1.getPerformedActions().size()); List<Integer> parentActions = shadowParent.getPerformedActions(); assertEquals(1, parentActions.size()); assertEquals(new Integer(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD), parentActions.get(0)); verify(mOverlayController, times(3)).clearOverlay(); parent.recycle(); } /** * This test isn't, strictly speaking, a test of OptionManager. Instead it verifies that the * xml arrays for highligh color are all the same length. The option manager assumes they * are, so verifying this condition reduces the chance of it crashing later on if we add * a new preference without adding a default. */ @Test public void testHighlightArraysHaveConsistentLength() { String[] colorPrefs = mContext.getResources() .getStringArray(R.array.switch_access_highlight_color_pref_keys); String[] colorDefaults = mContext.getResources() .getStringArray(R.array.switch_access_highlight_color_defaults); assertEquals(colorPrefs.length, colorDefaults.length); } @Test public void testHighlightStylesForOptionScanning_defaultsAreDifferent() { mOptionManager.onSharedPreferenceChanged(mSharedPreferences, null); mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); verify(mOverlayController, times(2)) .highlightPerimeterOfRects(mHighlightCaptor.capture(), mPaintCaptor.capture()); /* The two paint colors should be different */ List<Paint> capturedPaints = mPaintCaptor.getAllValues(); assertFalse(capturedPaints.get(0).getColor() == capturedPaints.get(1).getColor()); } @Test public void testStartScanningWithListener_shouldCallScanStart() { OptionManager.ScanListener mockListener = mock(OptionManager.ScanListener.class); mOptionManager.setScanListener(mockListener); mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); verify(mockListener, times(1)).onScanStart(); verify(mockListener, times(1)).onScanFocusChanged(); verifyNoMoreInteractions(mockListener); } @Test public void scanningReachesClearFocusNode_shouldCallNoSelection() { OptionManager.ScanListener mockListener = mock(OptionManager.ScanListener.class); mOptionManager.setScanListener(mockListener); mActionNode2.recycle(); mSelectionNode = new OptionScanSelectionNode(mActionNode1, new ClearFocusNode()); mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); mOptionManager.selectOption(1); verify(mockListener, times(1)).onScanStart(); verify(mockListener, times(1)).onScanFocusChanged(); verify(mockListener, times(1)).onScanCompletedWithNoSelection(); verifyNoMoreInteractions(mockListener); } @Test public void userAbortsScanWithExtraSwitch_shouldCallNoSelection() { OptionManager.ScanListener mockListener = mock(OptionManager.ScanListener.class); mOptionManager.setScanListener(mockListener); mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); mOptionManager.selectOption(2); verify(mockListener, times(1)).onScanStart(); verify(mockListener, times(1)).onScanFocusChanged(); verify(mockListener, times(1)).onScanCompletedWithNoSelection(); verifyNoMoreInteractions(mockListener); } @Test public void testSelectingWithListener_shouldCallSelected() { OptionManager.ScanListener mockListener = mock(OptionManager.ScanListener.class); mOptionManager.setScanListener(mockListener); mOptionManager.clearFocusIfNewTree(mSelectionNode); mOptionManager.selectOption(0); mOptionManager.selectOption(0); verify(mockListener, times(1)).onScanStart(); verify(mockListener, times(1)).onScanFocusChanged(); verify(mockListener, times(1)).onScanSelection(); verifyNoMoreInteractions(mockListener); } @Test public void testAutoStartScan_shouldAutomaticallySelectFirstItem() { mSharedPreferences.edit() .putBoolean(mContext.getString(R.string.switch_access_auto_start_scan_key), true) .commit(); mOptionManager.onSharedPreferenceChanged(mSharedPreferences, null); mOptionManager.clearFocusIfNewTree(mActionNode1); assertEquals(1, mShadowInfo1.getPerformedActions().size()); } @Test public void testAutoStartScan_shouldCallListener() { mOptionManager.addOptionManagerListener(mMockListener); mSharedPreferences.edit() .putBoolean(mContext.getString(R.string.switch_access_auto_start_scan_key), true) .commit(); mOptionManager.onSharedPreferenceChanged(mSharedPreferences, null); mOptionManager.clearFocusIfNewTree(mActionNode1); verify(mMockListener, times(1)).onOptionManagerStartedAutoScan(); } }