1 /*
   2  * Copyright (c) 2013, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.scene.control.behavior;
  27 
  28 import javafx.scene.control.Button;
  29 import javafx.scene.control.Skin;
  30 import javafx.scene.input.KeyCode;
  31 import javafx.scene.input.MouseEvent;
  32 import java.util.ArrayList;
  33 import java.util.Collections;
  34 import java.util.HashSet;
  35 import java.util.List;
  36 import java.util.Set;
  37 import com.sun.javafx.scene.control.infrastructure.KeyEventFirer;
  38 import com.sun.javafx.scene.control.infrastructure.KeyModifier;
  39 import com.sun.javafx.scene.control.infrastructure.MouseEventFirer;
  40 import com.sun.javafx.scene.control.skin.BehaviorSkinBase;
  41 import org.junit.Before;
  42 import org.junit.After;
  43 import org.junit.Test;
  44 import static org.junit.Assert.assertEquals;
  45 import static org.junit.Assert.assertFalse;
  46 import static org.junit.Assert.assertTrue;
  47 
  48 /**
  49  * Unit tests for BehaviorBase
  50  */
  51 public class BehaviorBaseTest {
  52     private ControlMock button;
  53     private Skin<Button> skin;
  54     private BehaviorBaseMock behavior;
  55     private ArrayList<KeyBinding> bindings;
  56     private KeyEventFirer keyboard;
  57     private MouseEventFirer mouse;
  58 
  59     @Before
  60     public void setup() {
  61         button = new ControlMock();
  62         bindings = new ArrayList<>();
  63         bindings.addAll(BehaviorBase.TRAVERSAL_BINDINGS);
  64         behavior = new BehaviorBaseMock(button, bindings);
  65         skin = new BehaviorSkinBase(button, behavior) {};
  66         button.setSkin(skin);
  67         keyboard = new KeyEventFirer(button);
  68         mouse = new MouseEventFirer(button);
  69     }
  70 
  71     @After
  72     public void after() {
  73         mouse.dispose();
  74     }
  75 
  76     /**
  77      * We don't accept null for the control of a behavior
  78      */
  79     @Test(expected = NullPointerException.class)
  80     public void creatingBehaviorWithNullControlThrowsNPE() {
  81         new BehaviorBase<Button>(null, Collections.EMPTY_LIST);
  82     }
  83 
  84     /**
  85      * We do accept null for the key bindings of a behavior, treating it as empty
  86      */
  87     @Test
  88     public void creatingBehaviorWithNullKeyBindingsResultsInEmptyKeyBindings() {
  89         BehaviorBaseMock b = new BehaviorBaseMock(button, null);
  90         skin = new BehaviorSkinBase(button, b) {};
  91         button.setSkin(skin);
  92         keyboard.doKeyPress(KeyCode.TAB);
  93         assertFalse(b.someActionCalled());
  94     }
  95 
  96     /**
  97      * This is a hard-and-fast rule, you must always have the control set to the
  98      * expected value after the constructor was called
  99      */
 100     @Test
 101     public void controlMustBeSet() {
 102         assertEquals(button, behavior.getControl());
 103     }
 104 
 105     /**
 106      * This is a little fishy. Right now we store the control in a final field, so we
 107      * know that we want this to continue returning "button" after dispose, but really
 108      * it seems better to clear the control after dispose, although this mean we can
 109      * no longer store the control in a final field.
 110      */
 111     @Test
 112     public void controlMustNotBeClearedOnDispose() {
 113         button.setSkin(null);
 114         assertEquals(button, behavior.getControl());
 115     }
 116 
 117     /**
 118      * Modifying the list passed to BehaviorBase should have no effect on what
 119      * key bindings are used.
 120      */
 121     @Test
 122     public void changingKeyBindingsDynamicallyMustHaveNoEffect() {
 123         bindings.set(0, new KeyBinding(KeyCode.TAB, "action2"));
 124         keyboard.doKeyPress(KeyCode.TAB);
 125         assertTrue(behavior.actionCalled(BehaviorBase.TRAVERSE_NEXT));
 126         assertFalse(behavior.actionCalled("action2"));
 127     }
 128 
 129     /**
 130      * Test that if the default traversal bindings are installed, that the "tab" event is
 131      * handled correctly
 132      */
 133     @Test
 134     public void tabCalls_TraverseNext() {
 135         keyboard.doKeyPress(KeyCode.TAB);
 136         assertTrue(behavior.actionCalled(BehaviorBase.TRAVERSE_NEXT));
 137     }
 138 
 139     /**
 140      * Test that if the default traversal bindings are installed, that the "shift+tab" event is
 141      * handled correctly
 142      */
 143     @Test
 144     public void shiftTabCalls_TraversePrevious() {
 145         keyboard.doKeyPress(KeyCode.TAB, KeyModifier.SHIFT);
 146         assertTrue(behavior.actionCalled(BehaviorBase.TRAVERSE_PREVIOUS));
 147     }
 148 
 149     /**
 150      * Test that if the default traversal bindings are installed, that the "up" event is
 151      * handled correctly
 152      */
 153     @Test
 154     public void upCalls_TraverseUp() {
 155         keyboard.doKeyPress(KeyCode.UP);
 156         assertTrue(behavior.actionCalled(BehaviorBase.TRAVERSE_UP));
 157     }
 158 
 159     /**
 160      * Test that if the default traversal bindings are installed, that the "down" event is
 161      * handled correctly
 162      */
 163     @Test
 164     public void downCalls_TraverseDown() {
 165         keyboard.doKeyPress(KeyCode.DOWN);
 166         assertTrue(behavior.actionCalled(BehaviorBase.TRAVERSE_DOWN));
 167     }
 168 
 169     /**
 170      * Test that if the default traversal bindings are installed, that the "left" event is
 171      * handled correctly
 172      */
 173     @Test
 174     public void leftCalls_TraverseLeft() {
 175         keyboard.doKeyPress(KeyCode.LEFT);
 176         assertTrue(behavior.actionCalled(BehaviorBase.TRAVERSE_LEFT));
 177     }
 178 
 179     /**
 180      * Test that if the default traversal bindings are installed, that the "right" event is
 181      * handled correctly
 182      */
 183     @Test
 184     public void rightCalls_TraverseRight() {
 185         keyboard.doKeyPress(KeyCode.RIGHT);
 186         assertTrue(behavior.actionCalled(BehaviorBase.TRAVERSE_RIGHT));
 187     }
 188 
 189     /**
 190      * Make sure that key listeners are removed after the behavior is disposed, which should
 191      * always happen when the skin is disposed, which should always happen when the skin is
 192      * removed from a Control.
 193      */
 194     @Test
 195     public void keyListenerShouldBeRemovedAfterBehaviorIsDisposed() {
 196         // Send a key event
 197         keyboard.doKeyPress(KeyCode.TAB);
 198         assertTrue(behavior.someActionCalled());
 199         behavior.reset();
 200 
 201         // Remove the skin, which should result in dispose being called, and then send another event
 202         button.setSkin(null);
 203         keyboard.doKeyPress(KeyCode.TAB);
 204         assertFalse(behavior.someActionCalled());
 205     }
 206 
 207     /**
 208      * Test to make sure that the focusChanged() method is called when focus is gained.
 209      */
 210     @Test
 211     public void focusListenerShouldBeCalledWhenFocusGained() {
 212         assertFalse(behavior.focusCalled);
 213         button.focus();
 214         assertTrue(behavior.focusCalled);
 215     }
 216 
 217     /**
 218      * Test to make sure that the focusChanged() method is called when focus is lost
 219      */
 220     @Test
 221     public void focusListenerShouldBeCalledWhenFocusLost() {
 222         assertFalse(behavior.focusCalled);
 223         button.focus();
 224         behavior.reset();
 225         button.blur();
 226         assertTrue(behavior.focusCalled);
 227     }
 228 
 229     /**
 230      * Make sure that the behavior is no longer called on focus changes after disposal
 231      */
 232     @Test
 233     public void focusListenerShouldBeRemovedAfterBehaviorIsDisposed() {
 234         button.setSkin(null);
 235         button.focus();
 236         assertFalse(behavior.focusCalled);
 237     }
 238 
 239     /**
 240      * mousePressed method should be called when the behavior is wired up properly
 241      */
 242     @Test
 243     public void mousePressedCalledWhenMousePressedOverControl() {
 244         assertFalse(behavior.mousePressCalled);
 245         mouse.fireMousePressed();
 246         assertTrue(behavior.mousePressCalled);
 247     }
 248 
 249     /**
 250      * The mouse handlers should never be called when the skin / behavior has been
 251      * disposed of.
 252      */
 253     @Test
 254     public void mouseListenerShouldBeRemovedAfterBehaviorIsDisposed() {
 255         button.setSkin(null);
 256         mouse.fireMousePressed();
 257         assertFalse(behavior.mousePressCalled);
 258     }
 259 
 260 
 261     // Test the matchActionForEvent
 262     //      Make sure paired events are consumed / allowed together
 263     //
 264 
 265     private static final class ControlMock extends Button {
 266         public void focus() {
 267             setFocused(true);
 268         }
 269         public void blur() {
 270             setFocused(false);
 271         }
 272     }
 273 
 274     private static final class BehaviorBaseMock extends BehaviorBase<Button> {
 275         private Set<String> actionsCalled = new HashSet<>();
 276         private boolean focusCalled;
 277         private boolean mousePressCalled;
 278 
 279         public BehaviorBaseMock(Button control, List<KeyBinding> keyBindings) {
 280             super(control, keyBindings);
 281         }
 282 
 283         @Override protected void callAction(String name) {
 284             actionsCalled.add(name);
 285         }
 286 
 287         public boolean someActionCalled() {
 288             return !actionsCalled.isEmpty();
 289         }
 290 
 291         public boolean actionCalled(String name) {
 292             return actionsCalled.contains(name);
 293         }
 294 
 295         @Override protected void focusChanged() {
 296             focusCalled = true;
 297         }
 298 
 299         @Override
 300         public void mousePressed(MouseEvent e) {
 301             mousePressCalled = true;
 302         }
 303 
 304         public void reset() {
 305             actionsCalled.clear();
 306             focusCalled = false;
 307             mousePressCalled = false;
 308         }
 309     }
 310 }