1 /*
   2  * Copyright (c) 2010, 2013, 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 javafx.scene;
  27 
  28 import com.sun.javafx.geom.BoxBounds;
  29 import com.sun.javafx.geom.PickRay;
  30 import com.sun.javafx.geom.transform.Affine2D;
  31 import com.sun.javafx.geom.transform.Affine3D;
  32 import com.sun.javafx.geom.transform.BaseTransform;
  33 import com.sun.javafx.geom.transform.Translate2D;
  34 import com.sun.javafx.pgstub.StubStage;
  35 import com.sun.javafx.pgstub.StubToolkit;
  36 import com.sun.javafx.scene.DirtyBits;
  37 import com.sun.javafx.scene.input.PickResultChooser;
  38 import com.sun.javafx.sg.prism.NGGroup;
  39 import com.sun.javafx.sg.prism.NGNode;
  40 import com.sun.javafx.sg.prism.NGRectangle;
  41 import com.sun.javafx.test.objects.TestScene;
  42 import com.sun.javafx.test.objects.TestStage;
  43 import com.sun.javafx.tk.Toolkit;
  44 import javafx.beans.property.*;
  45 import javafx.geometry.BoundingBox;
  46 import javafx.geometry.Bounds;
  47 import javafx.geometry.NodeOrientation;
  48 import javafx.geometry.Point2D;
  49 import javafx.geometry.Point3D;
  50 import javafx.scene.effect.DropShadow;
  51 import javafx.scene.effect.Effect;
  52 import javafx.scene.shape.*;
  53 import javafx.scene.transform.Rotate;
  54 import javafx.scene.transform.Transform;
  55 import org.junit.Rule;
  56 import org.junit.Test;
  57 import org.junit.rules.ExpectedException;
  58 
  59 import java.lang.reflect.Method;
  60 import java.util.Comparator;
  61 import javafx.scene.layout.AnchorPane;
  62 import javafx.scene.transform.Affine;
  63 import javafx.scene.transform.Scale;
  64 import javafx.scene.transform.Shear;
  65 import javafx.scene.transform.Translate;
  66 import javafx.stage.Stage;
  67 
  68 import static org.junit.Assert.*;
  69 import org.junit.Ignore;
  70 /**
  71  * Tests various aspects of Node.
  72  *
  73  */
  74 public class NodeTest {
  75     @Rule
  76     public ExpectedException thrown = ExpectedException.none();
  77 
  78     // Things to test:
  79         // When parent is changed, should cursor on toolkit change as well if
  80         // the current node has the mouse over it and didn't explicitly set a
  81         // cursor??
  82 
  83         // Test CSS integration
  84 
  85         // Events:
  86             // Events should *not* be delivered to invisible nodes as per the
  87             // specification for visible
  88 
  89         // A Node must lose focus when it is no longer visible
  90 
  91         // A node made invisible must cause the cursor to be updated
  92 
  93         // Setting the cursor should override the parent cursor when hover
  94         // (test that this happens both when the node already has hover set and
  95         // when hover is changed to true)
  96 
  97         // Setting the cursor to null should revert to parent cursor when hover
  98         // (test that this happens both when the node already has hover set and
  99         // when hover is changed to true)
 100 
 101         // Clip:
 102             // Test setting/clearing the clip affects the bounds
 103             // Test changing bounds / smooth / etc on clip updates bounds of clipped Node
 104 
 105         // Effect:
 106             // Test setting/clearing the effect affects the bounds
 107             // Test changing state on Effect updates bounds of Node
 108 
 109         // Test that a disabled Group affects the disabled property of child nodes
 110 
 111         // Test contains, intersects methods
 112         // Test parentToLocal/localToStage/etc
 113 
 114         // Test computeCompleteBounds
 115         // (other bounds test situtations explicitly tested in BoundsTest)
 116 
 117         // Test transforms end up setting the correct matrix on the peer
 118         // In particular, test that pivots are taken correctly into account
 119 
 120         // Test hover is updated when mouse enters
 121         // Test hover is updated when mouse exists
 122         // Test hover is updated when mouse was over but a higher node then
 123         // turns on blocks mouse
 124         // Test hover is updated when node moves out from under the cursor
 125         // TODO most of these cases cannot be handled until/unless we update
 126         // the list of nodes under the cursor on pulse events
 127 
 128         // Test pressed is updated when mouse is pressed
 129         // Test pressed is updated when mouse is released
 130         // TODO shoudl pressed obey the semantics of a button that is armed & pressed?
 131         // Or should "armed" be put on Node? What to do here?
 132 
 133         // Test various onMouseXXX event handlers
 134 
 135         // Test onKeyXXX handlers
 136 
 137         // Test focused is updated?
 138         // Test nodes which are not focusable are not focused!
 139         // Test focus... (SHOULD NOT DEPEND ON KEY LISTENERS BEING INSTALLED!!)
 140 
 141         // Test that clip is taken into account for both "contains" and
 142         // "intersects". See http://javafx-jira.kenai.com/browse/RT-646
 143 
 144 
 145 
 146     /***************************************************************************
 147      *                                                                         *
 148      *                              Basic Node Tests                           *
 149      *                                                                         *
 150      **************************************************************************/
 151 
 152 // TODO disable this because it depends on TestNode
 153 //    @Test public void testPeerNotifiedOfVisibilityChanges() {
 154 //        Rectangle rect = new Rectangle();
 155 //        Node peer = rect.impl_getPGNode();
 156 //        assertEquals(peer.visible, rect.visible);
 157 //
 158 //        rect.visible = false;
 159 //        assertEquals(peer.visible, rect.visible);
 160 //
 161 //        rect.visible = true;
 162 //        assertEquals(peer.visible, rect.visible);
 163 //    }
 164 
 165     /***************************************************************************
 166      *                                                                         *
 167      *                            Testing Node Bounds                          *
 168      *                                                                         *
 169      **************************************************************************/
 170 
 171 // TODO disable this because it depends on TestNode
 172 //     public function testContainsCallsPeer():Void {
 173 //         var rect = Rectangle { };
 174 //         var peer = rect.impl_getPGNode() as TestNode;
 175 //         peer.numTimesContainsCalled = 0;
 176 //
 177 //         rect.contains(0, 0);
 178 //         assertEquals(1, peer.numTimesContainsCalled);
 179 //
 180 //         rect.contains(Point2D { x:10, y:10 });
 181 //         assertEquals(2, peer.numTimesContainsCalled);
 182 //     }
 183 
 184 // TODO disable this because it depends on TestNode
 185 //     public function testIntersectsCallsPeer():Void {
 186 //         var rect = Rectangle { };
 187 //         var peer = rect.impl_getPGNode() as TestNode;
 188 //         peer.numTimesIntersectsCalled = 0;
 189 //
 190 //         rect.intersects(0, 0, 10, 10);
 191 //         assertEquals(1, peer.numTimesIntersectsCalled);
 192 //
 193 //         rect.intersects(BoundingBox { minX:10, minY:10, width:100, height:100 });
 194 //         assertEquals(2, peer.numTimesIntersectsCalled);
 195 //     }
 196 
 197     /***************************************************************************
 198      *                                                                         *
 199      *                          Testing Node transforms                        *
 200      *                                                                         *
 201      **************************************************************************/
 202 
 203     /**
 204      * Tests that the function which converts a com.sun.javafx.geom.Point2D
 205      * in parent coords to local coords works properly.
 206      */
 207     @Test public void testParentToLocalGeomPoint() {
 208         Rectangle rect = new Rectangle();
 209         rect.setTranslateX(10);
 210         rect.setTranslateY(10);
 211         rect.setWidth(100);
 212         rect.setHeight(100);
 213         rect.getTransforms().clear();
 214         rect.getTransforms().addAll(Transform.scale(2, 2), Transform.translate(30, 30));
 215 
 216         Point2D pt = new Point2D(0, 0);
 217         pt = rect.parentToLocal(pt);
 218         assertEquals(new Point2D(-35, -35), pt);
 219     }
 220 
 221     // TODO need to test with some observableArrayList of transforms which cannot be
 222     // cleanly inverted so that we can test that code path
 223 
 224     @Test public void testLocalToParentGeomPoint() {
 225         Rectangle rect = new Rectangle();
 226         rect.setTranslateX(10);
 227         rect.setTranslateY(10);
 228         rect.setWidth(100);
 229         rect.setHeight(100);
 230         rect.getTransforms().clear();
 231         rect.getTransforms().addAll(Transform.scale(2, 2), Transform.translate(30, 30));
 232 
 233         Point2D pt = new Point2D(0, 0);
 234         pt = rect.localToParent(pt);
 235         assertEquals(new Point2D(70, 70), pt);
 236     }
 237 
 238     @Test public void testPickingNodeDirectlyNoTransforms() {
 239         Rectangle rect = new Rectangle();
 240         rect.setX(10);
 241         rect.setY(10);
 242         rect.setWidth(100);
 243         rect.setHeight(100);
 244 
 245         // needed since picking doesn't work unless rooted in a scene and visible
 246         Scene scene = new Scene(new Group());
 247         scene.getRoot().getChildren().add(rect);
 248 
 249         PickResultChooser res = new PickResultChooser();
 250         rect.impl_pickNode(new PickRay(50, 50, 1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY), res);
 251         assertSame(rect, res.getIntersectedNode());
 252         res = new PickResultChooser();
 253         rect.impl_pickNode(new PickRay(0, 0, 1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY), res);
 254         assertNull(res.getIntersectedNode());
 255     }
 256 
 257     @Test public void testPickingNodeDirectlyWithTransforms() {
 258         Rectangle rect = new Rectangle();
 259         rect.setTranslateX(10);
 260         rect.setTranslateY(10);
 261         rect.setWidth(100);
 262         rect.setHeight(100);
 263 
 264         // needed since picking doesn't work unless rooted in a scene and visible
 265         Scene scene = new Scene(new Group());
 266         scene.getRoot().getChildren().add(rect);
 267 
 268         PickResultChooser res = new PickResultChooser();
 269         rect.impl_pickNode(new PickRay(50, 50, 1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY), res);
 270         assertSame(rect, res.getIntersectedNode());
 271         res = new PickResultChooser();
 272         rect.impl_pickNode(new PickRay(0, 0, 1, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY), res);
 273         assertNull(res.getIntersectedNode());
 274     }
 275 
 276     @Test public void testEffectSharedOnNodes() {
 277         Effect effect = new DropShadow();
 278         Rectangle node = new Rectangle();
 279         node.setEffect(effect);
 280 
 281         Rectangle node2 = new Rectangle();
 282         node2.setEffect(effect);
 283 
 284         assertEquals(effect, node.getEffect());
 285         assertEquals(effect, node2.getEffect());
 286     }
 287 
 288     public static void testBooleanPropertyPropagation(
 289         final Node node,
 290         final String propertyName,
 291         final boolean initialValue,
 292         final boolean newValue) throws Exception {
 293 
 294         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 295         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 296         final String setterName = new StringBuilder("set").append(propertyNameBuilder).toString();
 297         final String getterName = new StringBuilder("is").append(propertyNameBuilder).toString();
 298 
 299         final Class<? extends Node> nodeClass = node.getClass();
 300         final Method setter = nodeClass.getMethod(setterName, boolean.class);
 301         final Method getter = nodeClass.getMethod(getterName);
 302 
 303         final NGNode peer = node.impl_getPeer();
 304         final Class<? extends NGNode> impl_class = peer.getClass();
 305         final Method impl_getter = impl_class.getMethod(getterName);
 306 
 307 
 308         // 1. Create test scene
 309         final Scene scene = new Scene(new Group());
 310         scene.getRoot().getChildren().add(node);
 311 
 312         // 2. Initial setup
 313         setter.invoke(node, initialValue);
 314         node.impl_syncPeer();
 315         assertEquals(initialValue, getter.invoke(node));
 316         assertEquals(initialValue, impl_getter.invoke(peer));
 317 
 318         // 3. Change value of the property
 319         setter.invoke(node, newValue);
 320 
 321         // 4. Check that the property value has changed but has not propagated to PGNode
 322         assertEquals(newValue, getter.invoke(node));
 323         assertEquals(initialValue, impl_getter.invoke(peer));
 324 
 325         // 5. Propagate the property value to PGNode
 326         node.impl_syncPeer();
 327 
 328         // 6. Check that the value has been propagated to PGNode
 329         assertEquals(newValue, impl_getter.invoke(peer));
 330     }
 331 
 332 
 333     public static void testFloatPropertyPropagation(
 334         final Node node,
 335         final String propertyName,
 336         final float initialValue,
 337         final float newValue) throws Exception {
 338 
 339         testFloatPropertyPropagation(node, propertyName, propertyName, initialValue, newValue);
 340     }
 341 
 342     public static void syncNode(Node node) {
 343         node.updateBounds();
 344         node.impl_syncPeer();
 345     }
 346 
 347     public static void assertBooleanPropertySynced(
 348             final Node node,
 349             final String propertyName,
 350             final String pgPropertyName,
 351             final boolean value) throws Exception {
 352 
 353         final Scene scene = new Scene(new Group(), 500, 500);
 354 
 355         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 356         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 357         final String getterName = new StringBuilder("is").append(propertyNameBuilder).toString();
 358         Method getterMethod = node.getClass().getMethod(getterName, new Class[]{});
 359         Boolean defaultValue = (Boolean)getterMethod.invoke(node);
 360         BooleanProperty v = new SimpleBooleanProperty(defaultValue);
 361 
 362         Method modelMethod = node.getClass().getMethod(
 363                 propertyName + "Property",
 364                 new Class[]{});
 365         BooleanProperty model = (BooleanProperty)modelMethod.invoke(node);
 366         model.bind(v);
 367 
 368         ((Group)scene.getRoot()).getChildren().add(node);
 369 
 370         NodeTest.syncNode(node);
 371         assertEquals(defaultValue, TestUtils.getBooleanValue(node, pgPropertyName));
 372 
 373         v.set(value);
 374         NodeTest.syncNode(node);
 375         assertEquals(value, TestUtils.getBooleanValue(node, pgPropertyName));
 376     }
 377 
 378     public static void assertIntPropertySynced(
 379             final Node node,
 380             final String propertyName,
 381             final String pgPropertyName,
 382             final int value) throws Exception {
 383 
 384         final Scene scene = new Scene(new Group(), 500, 500);
 385 
 386         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 387         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 388         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 389         Method getterMethod = node.getClass().getMethod(getterName, new Class[]{});
 390         Integer defaultValue = (Integer)getterMethod.invoke(node);
 391         IntegerProperty v = new SimpleIntegerProperty(defaultValue);
 392 
 393         Method modelMethod = node.getClass().getMethod(
 394                 propertyName + "Property",
 395                 new Class[]{});
 396         IntegerProperty model = (IntegerProperty)modelMethod.invoke(node);
 397         model.bind(v);
 398 
 399         ((Group)scene.getRoot()).getChildren().add(node);
 400 
 401         NodeTest.syncNode(node);
 402         assertTrue(numbersEquals(defaultValue,
 403                 (Number)TestUtils.getObjectValue(node, pgPropertyName)));
 404 
 405         v.set(value);
 406         NodeTest.syncNode(node);
 407         assertTrue(numbersEquals(new Integer(value),
 408                 (Number)TestUtils.getObjectValue(node, pgPropertyName)));
 409     }
 410 
 411     public static boolean numbersEquals(Number expected, Number value) {
 412         return numbersEquals(expected, value, 0.001);
 413     }
 414 
 415     public static boolean numbersEquals(Number expected, Number value, double delta) {
 416         boolean res = (Math.abs(expected.doubleValue() - value.doubleValue()) < delta);
 417         if (!res) {
 418             System.err.println("expected=" + expected + ", value=" + value);
 419         }
 420         return res;
 421     }
 422 
 423     public static void assertDoublePropertySynced(
 424             final Node node,
 425             final String propertyName,
 426             final String pgPropertyName,
 427             final double value) throws Exception {
 428 
 429         final Scene scene = new Scene(new Group(), 500, 500);
 430 
 431         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 432         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 433         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 434         Method getterMethod = node.getClass().getMethod(getterName, new Class[]{});
 435         Double defaultValue = (Double)getterMethod.invoke(node);
 436         DoubleProperty v = new SimpleDoubleProperty(defaultValue);
 437 
 438         Method modelMethod = node.getClass().getMethod(
 439                 propertyName + "Property",
 440                 new Class[]{});
 441         DoubleProperty model = (DoubleProperty)modelMethod.invoke(node);
 442         model.bind(v);
 443 
 444         scene.getRoot().getChildren().add(node);
 445 
 446         NodeTest.syncNode(node);
 447         assertTrue(numbersEquals(defaultValue,
 448                 (Number)TestUtils.getObjectValue(node, pgPropertyName)));
 449 
 450         v.set(value);
 451         NodeTest.syncNode(node);
 452         assertTrue(numbersEquals(new Double(value),
 453                 (Number)TestUtils.getObjectValue(node, pgPropertyName)));
 454     }
 455 
 456 
 457     public static void assertObjectPropertySynced(
 458             final Node node,
 459             final String propertyName,
 460             final String pgPropertyName,
 461             final Object value) throws Exception {
 462 
 463         final Scene scene = new Scene(new Group(), 500, 500);
 464 
 465         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 466         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 467         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 468         Method getterMethod = node.getClass().getMethod(getterName, new Class[]{});
 469         Object defaultValue = getterMethod.invoke(node);
 470         ObjectProperty v = new SimpleObjectProperty(defaultValue);
 471 
 472         Method modelMethod = node.getClass().getMethod(
 473                 propertyName + "Property",
 474                 new Class[]{});
 475         ObjectProperty model = (ObjectProperty)modelMethod.invoke(node);
 476         model.bind(v);
 477 
 478         ((Group)scene.getRoot()).getChildren().add(node);
 479 
 480         NodeTest.syncNode(node);
 481         // sometimes enum is used on node but int on PGNode
 482         Object result1 = TestUtils.getObjectValue(node, pgPropertyName);
 483         if (result1 instanceof Integer) {
 484             assertTrue(((Enum)defaultValue).ordinal() == ((Integer)result1).intValue());
 485         } else {
 486             assertEquals(defaultValue, TestUtils.getObjectValue(node, pgPropertyName));
 487         }
 488 
 489         v.set(value);
 490         NodeTest.syncNode(node);
 491 
 492         Object result2 = TestUtils.getObjectValue(node, pgPropertyName);
 493         if (result2 instanceof Integer) {
 494             assertTrue(((Enum)value).ordinal() == ((Integer)result2).intValue());
 495         } else {
 496             assertEquals(value, TestUtils.getObjectValue(node, pgPropertyName));
 497         }
 498     }
 499 
 500 
 501 
 502     public static void assertObjectProperty_AsStringSynced(
 503             final Node node,
 504             final String propertyName,
 505             final String pgPropertyName,
 506             final Object value) throws Exception {
 507 
 508         final Scene scene = new Scene(new Group(), 500, 500);
 509 
 510         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 511         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 512         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 513         Method getterMethod = node.getClass().getMethod(getterName, new Class[]{});
 514         Object defaultValue = getterMethod.invoke(node);
 515         ObjectProperty v = new SimpleObjectProperty(defaultValue);
 516 
 517         Method modelMethod = node.getClass().getMethod(
 518                 propertyName + "Property",
 519                 new Class[]{});
 520         ObjectProperty model = (ObjectProperty)modelMethod.invoke(node);
 521         model.bind(v);
 522 
 523         scene.getRoot().getChildren().add(node);
 524 
 525         NodeTest.syncNode(node);
 526         assertEquals(
 527                 defaultValue.toString(),
 528                 TestUtils.getObjectValue(node, pgPropertyName).toString());
 529 
 530         v.set(value);
 531         NodeTest.syncNode(node);
 532 
 533         assertEquals(
 534                 value.toString(),
 535                 TestUtils.getObjectValue(node, pgPropertyName).toString());
 536     }
 537 
 538     public static void assertStringPropertySynced(
 539             final Node node,
 540             final String propertyName,
 541             final String pgPropertyName,
 542             final String value) throws Exception {
 543 
 544         final Scene scene = new Scene(new Group(), 500, 500);
 545 
 546         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 547         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 548         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 549         Method getterMethod = node.getClass().getMethod(getterName, new Class[]{});
 550         String defaultValue = (String)getterMethod.invoke(node);
 551         StringProperty v = new SimpleStringProperty(defaultValue);
 552 
 553         Method modelMethod = node.getClass().getMethod(
 554                 propertyName + "Property",
 555                 new Class[]{});
 556         StringProperty model = (StringProperty)modelMethod.invoke(node);
 557         model.bind(v);
 558 
 559         ((Group)scene.getRoot()).getChildren().add(node);
 560 
 561         NodeTest.syncNode(node);
 562         assertEquals(
 563                 defaultValue,
 564                 TestUtils.getStringValue(node, pgPropertyName));
 565 
 566         v.set(value);
 567         NodeTest.syncNode(node);
 568 
 569         assertEquals(
 570                 value,
 571                 TestUtils.getStringValue(node, pgPropertyName));
 572     }
 573 
 574     public static void testFloatPropertyPropagation(
 575         final Node node,
 576         final String propertyName,
 577         final String pgPropertyName,
 578         final float initialValue,
 579         final float newValue) throws Exception {
 580 
 581         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 582         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 583 
 584         final StringBuilder pgPropertyNameBuilder = new StringBuilder(pgPropertyName);
 585         pgPropertyNameBuilder.setCharAt(0, Character.toUpperCase(pgPropertyName.charAt(0)));
 586 
 587         final String setterName = new StringBuilder("set").append(propertyNameBuilder).toString();
 588         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 589         final String pgGetterName = new StringBuilder("get").append(pgPropertyNameBuilder).toString();
 590 
 591         final Class<? extends Node> nodeClass = node.getClass();
 592         final Method setter = nodeClass.getMethod(setterName, float.class);
 593         final Method getter = nodeClass.getMethod(getterName);
 594 
 595         final NGNode peer = node.impl_getPeer();
 596         final Class<? extends NGNode> impl_class = peer.getClass();
 597         final Method impl_getter = impl_class.getMethod(pgGetterName);
 598 
 599 
 600         // 1. Create test scene
 601         final Scene scene = new Scene(new Group());
 602         scene.getRoot().getChildren().add(node);
 603 
 604         // 2. Initial setup
 605         setter.invoke(node, initialValue);
 606         node.impl_syncPeer();
 607         assertEquals(initialValue, (Float) getter.invoke(node), 1e-100);
 608         assertEquals(initialValue, (Float) impl_getter.invoke(peer), 1e-100);
 609 
 610         // 3. Change value of the property
 611         setter.invoke(node, newValue);
 612 
 613         // 4. Check that the property value has changed but has not propagated to PGNode
 614         assertEquals(newValue, (Float) getter.invoke(node), 1e-100);
 615         assertEquals(initialValue, (Float) impl_getter.invoke(peer), 1e-100);
 616 
 617         // 5. Propagate the property value to PGNode
 618         node.impl_syncPeer();
 619 
 620         // 6. Check that the value has been propagated to PGNode
 621         assertEquals(newValue, (Float) impl_getter.invoke(peer), 1e-100);
 622     }
 623 
 624     public static void testDoublePropertyPropagation(
 625         final Node node,
 626         final String propertyName,
 627         final double initialValue,
 628         final double newValue) throws Exception {
 629 
 630         testDoublePropertyPropagation(node, propertyName, propertyName, initialValue, newValue);
 631     }
 632 
 633 
 634     public static void testDoublePropertyPropagation(
 635         final Node node,
 636         final String propertyName,
 637         final String pgPropertyName,
 638         final double initialValue,
 639         final double newValue) throws Exception {
 640 
 641         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 642         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 643 
 644         final StringBuilder pgPropertyNameBuilder = new StringBuilder(pgPropertyName);
 645         pgPropertyNameBuilder.setCharAt(0, Character.toUpperCase(pgPropertyName.charAt(0)));
 646 
 647         final String setterName = new StringBuilder("set").append(propertyNameBuilder).toString();
 648         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 649         final String pgGetterName = new StringBuilder("get").append(pgPropertyNameBuilder).toString();
 650 
 651         final Class<? extends Node> nodeClass = node.getClass();
 652         final Method setter = nodeClass.getMethod(setterName, double.class);
 653         final Method getter = nodeClass.getMethod(getterName);
 654 
 655         final NGNode peer = node.impl_getPeer();
 656         final Class<? extends NGNode> impl_class = peer.getClass();
 657         final Method impl_getter = impl_class.getMethod(pgGetterName);
 658 
 659 
 660         // 1. Create test scene
 661         final Scene scene = new Scene(new Group());
 662         scene.getRoot().getChildren().add(node);
 663 
 664         // 2. Initial setup
 665         setter.invoke(node, initialValue);
 666         node.impl_syncPeer();
 667         assertEquals(initialValue, (Double) getter.invoke(node), 1e-100);
 668         assertEquals((float) initialValue, (Float) impl_getter.invoke(peer), 1e-100);
 669 
 670         // 3. Change value of the property
 671         setter.invoke(node, newValue);
 672 
 673         // 4. Check that the property value has changed but has not propagated to PGNode
 674         assertEquals(newValue, (Double) getter.invoke(node), 1e-100);
 675         assertEquals((float) initialValue, (Float) impl_getter.invoke(peer), 1e-100);
 676 
 677         // 5. Propagate the property value to PGNode
 678         node.impl_syncPeer();
 679 
 680         // 6. Check that the value has been propagated to PGNode
 681         assertEquals((float) newValue, (Float) impl_getter.invoke(peer), 1e-100);
 682     }
 683 
 684     public interface ObjectValueConvertor {
 685         Object toSg(Object pgValue);
 686     }
 687 
 688     public static final Comparator DEFAULT_OBJ_COMPARATOR =
 689             new Comparator() {
 690                 @Override
 691                 public int compare(final Object sgValue, final Object pgValue) {
 692                     assertEquals(sgValue, pgValue);
 693                     return 0;
 694                 }
 695             };
 696 
 697     public static void testObjectPropertyPropagation(
 698         final Node node,
 699         final String propertyName,
 700         final Object initialValue,
 701         final Object newValue) throws Exception {
 702 
 703         testObjectPropertyPropagation(node, propertyName, propertyName, initialValue, newValue);
 704     }
 705 
 706     public static void testObjectPropertyPropagation(
 707             final Node node,
 708             final String propertyName,
 709             final String pgPropertyName,
 710             final Object initialValue,
 711             final Object newValue) throws Exception {
 712         testObjectPropertyPropagation(node, propertyName, pgPropertyName,
 713                 initialValue, newValue, DEFAULT_OBJ_COMPARATOR);
 714     }
 715 
 716     public static void testObjectPropertyPropagation(
 717             final Node node,
 718             final String propertyName,
 719             final String pgPropertyName,
 720             final Object initialValue,
 721             final Object newValue,
 722             final ObjectValueConvertor convertor) throws Exception {
 723         testObjectPropertyPropagation(
 724                 node, propertyName, pgPropertyName,
 725                 initialValue, newValue,
 726                 new Comparator() {
 727                     @Override
 728                     public int compare(final Object sgValue,
 729                                        final Object pgValue) {
 730                         assertEquals(sgValue, convertor.toSg(pgValue));
 731                         return 0;
 732                     }
 733                 });
 734     }
 735 
 736     public static void testObjectPropertyPropagation(
 737             final Node node,
 738             final String propertyName,
 739             final String pgPropertyName,
 740             final Object initialValue,
 741             final Object newValue,
 742             final Comparator comparator) throws Exception {
 743         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 744         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 745 
 746         final StringBuilder pgPropertyNameBuilder = new StringBuilder(pgPropertyName);
 747         pgPropertyNameBuilder.setCharAt(0, Character.toUpperCase(pgPropertyName.charAt(0)));
 748 
 749         final String setterName = new StringBuilder("set").append(propertyNameBuilder).toString();
 750         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 751         final String pgGetterName = new StringBuilder("get").append(pgPropertyNameBuilder).toString();
 752 
 753         final Class<? extends Node> nodeClass = node.getClass();
 754         final Method getter = nodeClass.getMethod(getterName);
 755         final Method setter = nodeClass.getMethod(setterName, getter.getReturnType());
 756 
 757         final NGNode peer = node.impl_getPeer();
 758         final Class<? extends NGNode> impl_class = peer.getClass();
 759         final Method impl_getter = impl_class.getMethod(pgGetterName);
 760 
 761 
 762         // 1. Create test scene
 763         final Scene scene = new Scene(new Group());
 764         scene.getRoot().getChildren().add(node);
 765 
 766         // 2. Initial setup
 767         setter.invoke(node, initialValue);
 768         node.impl_syncPeer();
 769         assertEquals(initialValue, getter.invoke(node));
 770         assertEquals(0, comparator.compare(initialValue,
 771                                            impl_getter.invoke(peer)));
 772 
 773         // 3. Change value of the property
 774         setter.invoke(node, newValue);
 775 
 776         // 4. Check that the property value has changed but has not propagated to PGNode
 777         assertEquals(newValue, getter.invoke(node));
 778         assertEquals(0, comparator.compare(initialValue,
 779                                            impl_getter.invoke(peer)));
 780 
 781         // 5. Propagate the property value to PGNode
 782         node.impl_syncPeer();
 783 
 784         // 6. Check that the value has been propagated to PGNode
 785         assertEquals(0, comparator.compare(newValue,
 786                                            impl_getter.invoke(peer)));
 787     }
 788 
 789 
 790     public static void testIntPropertyPropagation(
 791         final Node node,
 792         final String propertyName,
 793         final int initialValue,
 794         final int newValue) throws Exception {
 795 
 796         testIntPropertyPropagation(node, propertyName, propertyName, initialValue, newValue);
 797     }
 798 
 799 
 800     public static void testIntPropertyPropagation(
 801         final Node node,
 802         final String propertyName,
 803         final String pgPropertyName,
 804         final int initialValue,
 805         final int newValue) throws Exception {
 806 
 807         final StringBuilder propertyNameBuilder = new StringBuilder(propertyName);
 808         propertyNameBuilder.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
 809 
 810         final StringBuilder pgPropertyNameBuilder = new StringBuilder(pgPropertyName);
 811         pgPropertyNameBuilder.setCharAt(0, Character.toUpperCase(pgPropertyName.charAt(0)));
 812 
 813         final String setterName = new StringBuilder("set").append(propertyNameBuilder).toString();
 814         final String getterName = new StringBuilder("get").append(propertyNameBuilder).toString();
 815         final String pgGetterName = new StringBuilder("get").append(pgPropertyNameBuilder).toString();
 816 
 817         final Class<? extends Node> nodeClass = node.getClass();
 818         final Method getter = nodeClass.getMethod(getterName);
 819         final Method setter = nodeClass.getMethod(setterName, getter.getReturnType());
 820 
 821         final NGNode peer = node.impl_getPeer();
 822         final Class<? extends NGNode> impl_class = peer.getClass();
 823         final Method impl_getter = impl_class.getMethod(pgGetterName);
 824 
 825 
 826         // 1. Create test scene
 827         final Scene scene = new Scene(new Group());
 828         scene.getRoot().getChildren().add(node);
 829 
 830         // 2. Initial setup
 831         setter.invoke(node, initialValue);
 832         assertEquals(initialValue, getter.invoke(node));
 833         node.impl_syncPeer();
 834         assertEquals(initialValue, ((Number) impl_getter.invoke(peer)).intValue());
 835 
 836         // 3. Change value of the property
 837         setter.invoke(node, newValue);
 838 
 839         // 4. Check that the property value has changed but has not propagated to PGNode
 840         assertEquals(newValue, getter.invoke(node));
 841         assertEquals(initialValue, ((Number) impl_getter.invoke(peer)).intValue());
 842 
 843         // 5. Propagate the property value to PGNode
 844         node.impl_syncPeer();
 845 
 846         // 6. Check that the value has been propagated to PGNode
 847         assertEquals(newValue, ((Number) impl_getter.invoke(peer)).intValue());
 848     }
 849 
 850     public static void callSyncPGNode(final Node node) {
 851         node.impl_syncPeer();
 852     }
 853 
 854     @Test
 855     public void testToFront() {
 856         Rectangle rect1 = new Rectangle();
 857         Rectangle rect2 = new Rectangle();
 858         Group g = new Group();
 859 
 860         Scene scene = new Scene(g);
 861         g.getChildren().add(rect1);
 862         g.getChildren().add(rect2);
 863 
 864         rect1.toFront();
 865         rect2.toFront();
 866 
 867         // toFront should not remove rectangle from scene
 868         assertEquals(scene, rect2.getScene());
 869         assertEquals(scene, rect1.getScene());
 870         // test corect order of scene content
 871         assertEquals(rect2, g.getChildren().get(1));
 872         assertEquals(rect1, g.getChildren().get(0));
 873 
 874         rect1.toFront();
 875         assertEquals(scene, rect2.getScene());
 876         assertEquals(scene, rect1.getScene());
 877         assertEquals(rect1, g.getChildren().get(1));
 878         assertEquals(rect2, g.getChildren().get(0));
 879     }
 880 
 881     @Test
 882     public void testClip() {
 883         Rectangle rect1 = new Rectangle();
 884         Rectangle rect2 = new Rectangle();
 885         rect1.setClip(rect2);
 886 
 887         Scene scene = new Scene(new Group());
 888         scene.getRoot().getChildren().add(rect1);
 889         assertEquals(rect2, rect1.getClip());
 890         assertEquals(scene, rect2.getScene());
 891 
 892     }
 893 
 894     @Test
 895     public void testInvalidClip() {
 896         Rectangle rectA = new Rectangle(300, 300);
 897         Rectangle clip1 = new Rectangle(10, 10);
 898         Rectangle clip2 = new Rectangle(100, 100);
 899         clip2.setClip(rectA);
 900         rectA.setClip(clip1);
 901         assertEquals(rectA.getClip(), clip1);
 902         thrown.expect(IllegalArgumentException.class);
 903         try {
 904             rectA.setClip(clip2);
 905         } catch (final IllegalArgumentException e) {
 906             assertNotSame(rectA.getClip(), clip2);
 907             throw e;
 908         }
 909     }
 910 
 911     @Test public void testProperties() {
 912         Rectangle node = new Rectangle();
 913         javafx.collections.ObservableMap<Object, Object> properties = node.getProperties();
 914 
 915         /* If we ask for it, we should get it.
 916          */
 917         assertNotNull(properties);
 918 
 919         /* What we put in, we should get out.
 920          */
 921         properties.put("MyKey", "MyValue");
 922         assertEquals("MyValue", properties.get("MyKey"));
 923 
 924         /* If we ask for it again, we should get the same thing.
 925          */
 926         javafx.collections.ObservableMap<Object, Object> properties2 = node.getProperties();
 927         assertEquals(properties2, properties);
 928 
 929         /* What we put in to the other one, we should get out of this one because
 930          * they should be the same thing.
 931          */
 932         assertEquals("MyValue", properties2.get("MyKey"));
 933     }
 934 
 935     public static boolean isDirty(Node node, DirtyBits[] dbs) {
 936         for(DirtyBits db:dbs) {
 937             if (!node.impl_isDirty(db)) {
 938                 System.out.printf("@NodeTest:check dirty: %s [%d]\n",db,db.ordinal());
 939                 return false;
 940             }
 941         }
 942         return true;
 943     }
 944 
 945     @Test
 946     public void testDefaultValueForOpacityIsOneWhenReadFromGetter() {
 947         final Node node = new Rectangle();
 948         assertEquals(1, node.getOpacity(), .005);
 949     }
 950 
 951     @Test
 952     public void testDefaultValueForOpacityIsOneWhenReadFromProperty() {
 953         final Node node = new Rectangle();
 954         assertEquals(1, node.opacityProperty().get(), .005);
 955     }
 956 
 957     @Test
 958     public void settingOpacityThroughSetterShouldAffectBothGetterAndProperty() {
 959         final Node node = new Rectangle();
 960         node.setOpacity(.5);
 961         assertEquals(.5, node.getOpacity(), .005);
 962         assertEquals(.5, node.opacityProperty().get(), .005);
 963     }
 964 
 965     @Test
 966     public void settingOpacityThroughPropertyShouldAffectBothGetterAndProperty() {
 967         final Node node = new Rectangle();
 968         node.opacityProperty().set(.5);
 969         assertEquals(.5, node.getOpacity(), .005);
 970         assertEquals(.5, node.opacityProperty().get(), .005);
 971     }
 972 
 973     @Test
 974     public void testDefaultValueForVisibleIsTrueWhenReadFromGetter() {
 975         final Node node = new Rectangle();
 976         assertTrue(node.isVisible());
 977     }
 978 
 979     @Test
 980     public void testDefaultValueForVisibleIsTrueWhenReadFromProperty() {
 981         final Node node = new Rectangle();
 982         assertTrue(node.visibleProperty().get());
 983     }
 984 
 985     @Test
 986     public void settingVisibleThroughSetterShouldAffectBothGetterAndProperty() {
 987         final Node node = new Rectangle();
 988         node.setVisible(false);
 989         assertFalse(node.isVisible());
 990         assertFalse(node.visibleProperty().get());
 991     }
 992 
 993     @Test
 994     public void settingVisibleThroughPropertyShouldAffectBothGetterAndProperty() {
 995         final Node node = new Rectangle();
 996         node.visibleProperty().set(false);
 997         assertFalse(node.isVisible());
 998         assertFalse(node.visibleProperty().get());
 999     }
1000 
1001     @Test
1002     public void testDefaultStyleIsEmptyString() {
1003         final Node node = new Rectangle();
1004         assertEquals("", node.getStyle());
1005         assertEquals("", node.styleProperty().get());
1006         node.setStyle(null);
1007         assertEquals("", node.styleProperty().get());
1008         assertEquals("", node.getStyle());
1009     }
1010 
1011     @Test
1012     public void testSynchronizationOfInvisibleNodes() {
1013         final Group g = new Group();
1014         final Circle c = new CircleTest.StubCircle(50);
1015         final NGGroup sg = g.impl_getPeer();
1016         final CircleTest.StubNGCircle sc = c.impl_getPeer();
1017         g.getChildren().add(c);
1018 
1019         syncNode(g);
1020         syncNode(c);
1021         assertFalse(sg.getChildren().isEmpty());
1022         assertEquals(50.0, sc.getRadius(), 0.01);
1023 
1024         g.setVisible(false);
1025 
1026         syncNode(g);
1027         syncNode(c);
1028         assertFalse(sg.isVisible());
1029 
1030         final Rectangle r = new Rectangle();
1031         g.getChildren().add(r);
1032         c.setRadius(100);
1033 
1034         syncNode(g);
1035         syncNode(c);
1036         assertEquals(1, sg.getChildren().size());
1037         assertEquals(50.0, sc.getRadius(), 0.01);
1038 
1039         g.setVisible(true);
1040 
1041         syncNode(g);
1042         syncNode(c);
1043         assertEquals(2, sg.getChildren().size());
1044         assertEquals(100.0, sc.getRadius(), 0.01);
1045 
1046     }
1047     
1048     @Test
1049     public void testSynchronizationOfInvisibleNodes_2() {
1050         final Group g = new Group();
1051         final Circle c = new CircleTest.StubCircle(50);
1052         
1053         Scene s = new Scene(g);
1054         Stage st = new Stage();
1055         st.show();
1056         st.setScene(s);
1057 
1058         final NGGroup sg = g.impl_getPeer();
1059         final CircleTest.StubNGCircle sc = c.impl_getPeer();
1060         
1061         g.getChildren().add(c);
1062 
1063         s.scenePulseListener.pulse();
1064 
1065         g.setVisible(false);
1066 
1067         s.scenePulseListener.pulse();
1068 
1069         assertFalse(sg.isVisible());
1070         assertTrue(sc.isVisible());
1071 
1072         c.setCenterX(10);             // Make the circle dirty. It won't be synchronized as it is practically invisible (through the parent)
1073 
1074         s.scenePulseListener.pulse();
1075 
1076         c.setVisible(false);         // As circle is invisible and dirty, this won't trigger a synchronization
1077 
1078         s.scenePulseListener.pulse();
1079 
1080         assertFalse(sg.isVisible());
1081         assertTrue(sc.isVisible()); // This has not been synchronized, as it's not necessary
1082                                     // The rendering will stop at the Group, which is invisible
1083 
1084         g.setVisible(true);
1085 
1086         s.scenePulseListener.pulse();
1087         
1088         assertTrue(sg.isVisible());
1089         assertFalse(sc.isVisible()); // Now the group is visible again, we need to synchronize also 
1090                                      // the Circle
1091     }
1092     
1093     @Test
1094     public void testSynchronizationOfInvisibleNodes_2_withClip() {
1095         final Group g = new Group();
1096         final Circle c = new CircleTest.StubCircle(50);
1097         
1098         Scene s = new Scene(g);
1099         Stage st = new Stage();
1100         st.show();
1101         st.setScene(s);
1102 
1103         final NGGroup sg = g.impl_getPeer();
1104         final CircleTest.StubNGCircle sc = c.impl_getPeer();
1105         
1106         g.setClip(c);
1107 
1108         s.scenePulseListener.pulse();
1109 
1110         g.setVisible(false);
1111 
1112         s.scenePulseListener.pulse();
1113 
1114         assertFalse(sg.isVisible());
1115         assertTrue(sc.isVisible());
1116 
1117         c.setCenterX(10);             // Make the circle dirty. It won't be synchronized as it is practically invisible (through the parent)
1118 
1119         s.scenePulseListener.pulse();
1120 
1121         c.setVisible(false);         // As circle is invisible and dirty, this won't trigger a synchronization
1122 
1123         s.scenePulseListener.pulse();
1124 
1125         assertFalse(sg.isVisible());
1126         assertTrue(sc.isVisible()); // This has not been synchronized, as it's not necessary
1127                                     // The rendering will stop at the Group, which is invisible
1128 
1129         g.setVisible(true);
1130 
1131         s.scenePulseListener.pulse();
1132         
1133         assertTrue(sg.isVisible());
1134         assertFalse(sc.isVisible()); // Now the group is visible again, we need to synchronize also 
1135                                      // the Circle
1136     }
1137 
1138     @Test
1139     public void testLocalToScreen() {
1140         Rectangle rect = new Rectangle();
1141 
1142         rect.setTranslateX(10);
1143         rect.setTranslateY(20);
1144 
1145         TestScene scene = new TestScene(new Group(rect));
1146         final TestStage testStage = new TestStage("");
1147         testStage.setX(100);
1148         testStage.setY(200);
1149         scene.set_window(testStage);
1150         Point2D p = rect.localToScreen(new Point2D(1, 2));
1151         assertEquals(111.0, p.getX(), 0.0001);
1152         assertEquals(222.0, p.getY(), 0.0001);
1153         Bounds b = rect.localToScreen(new BoundingBox(1, 2, 3, 4));
1154         assertEquals(111.0, b.getMinX(), 0.0001);
1155         assertEquals(222.0, b.getMinY(), 0.0001);
1156         assertEquals(3.0, b.getWidth(), 0.0001);
1157         assertEquals(4.0, b.getHeight(), 0.0001);
1158     }
1159 
1160     @Test
1161     public void testLocalToScreen3D() {
1162         Box box = new Box(10, 10, 10);
1163 
1164         box.setTranslateX(10);
1165         box.setTranslateY(20);
1166 
1167         TestScene scene = new TestScene(new Group(box));
1168         scene.setCamera(new PerspectiveCamera());
1169         final TestStage testStage = new TestStage("");
1170         testStage.setX(100);
1171         testStage.setY(200);
1172         scene.set_window(testStage);
1173 
1174         Point2D p = box.localToScreen(new Point3D(1, 2, -5));
1175         assertEquals(111.42, p.getX(), 0.1);
1176         assertEquals(223.14, p.getY(), 0.1);
1177         Bounds b = box.localToScreen(new BoundingBox(1, 2, -5, 1, 2, 10));
1178         assertEquals(110.66, b.getMinX(), 0.1);
1179         assertEquals(221.08, b.getMinY(), 0.1);
1180         assertEquals(1.88, b.getWidth(), 0.1);
1181         assertEquals(4.3, b.getHeight(), 0.1);
1182         assertEquals(0.0, b.getDepth(), 0.0001);
1183     }
1184 
1185     @Test
1186     public void testScreenToLocal() {
1187         Rectangle rect = new Rectangle();
1188 
1189         rect.setTranslateX(10);
1190         rect.setTranslateY(20);
1191 
1192         TestScene scene = new TestScene(new Group(rect));
1193         final TestStage testStage = new TestStage("");
1194         testStage.setX(100);
1195         testStage.setY(200);
1196         scene.set_window(testStage);
1197 
1198         assertEquals(new Point2D(1, 2), rect.screenToLocal(new Point2D(111, 222)));
1199         assertEquals(new BoundingBox(1, 2, 3, 4), rect.screenToLocal(new BoundingBox(111, 222, 3, 4)));
1200     }
1201 
1202     @Test
1203     public void testLocalToScreenWithTranslatedCamera() {
1204         Rectangle rect = new Rectangle();
1205 
1206         rect.setTranslateX(10);
1207         rect.setTranslateY(20);
1208 
1209         ParallelCamera cam = new ParallelCamera();
1210         TestScene scene = new TestScene(new Group(rect, cam));
1211         scene.setCamera(cam);
1212         final TestStage testStage = new TestStage("");
1213         testStage.setX(100);
1214         testStage.setY(200);
1215         cam.setTranslateX(30);
1216         cam.setTranslateY(20);
1217         scene.set_window(testStage);
1218 
1219         Point2D p = rect.localToScreen(new Point2D(1, 2));
1220         assertEquals(81.0, p.getX(), 0.0001);
1221         assertEquals(202.0, p.getY(), 0.0001);
1222         Bounds b = rect.localToScreen(new BoundingBox(1, 2, 3, 4));
1223         assertEquals(81.0, b.getMinX(), 0.0001);
1224         assertEquals(202.0, b.getMinY(), 0.0001);
1225         assertEquals(3.0, b.getWidth(), 0.0001);
1226         assertEquals(4.0, b.getHeight(), 0.0001);
1227     }
1228 
1229     @Test
1230     public void testScreenToLocalWithTranslatedCamera() {
1231         Rectangle rect = new Rectangle();
1232 
1233         rect.setTranslateX(10);
1234         rect.setTranslateY(20);
1235 
1236         ParallelCamera cam = new ParallelCamera();
1237         TestScene scene = new TestScene(new Group(rect, cam));
1238         scene.setCamera(cam);
1239         final TestStage testStage = new TestStage("");
1240         testStage.setX(100);
1241         testStage.setY(200);
1242         cam.setTranslateX(30);
1243         cam.setTranslateY(20);
1244         scene.set_window(testStage);
1245 
1246         assertEquals(new Point2D(31, 22), rect.screenToLocal(new Point2D(111, 222)));
1247         assertEquals(new BoundingBox(31, 22, 3, 4), rect.screenToLocal(new BoundingBox(111, 222, 3, 4)));
1248     }
1249 
1250     @Test
1251     public void testLocalToScreenInsideSubScene() {
1252         Rectangle rect = new Rectangle();
1253         rect.setTranslateX(4);
1254         rect.setTranslateY(9);
1255         SubScene subScene = new SubScene(new Group(rect), 100, 100);
1256         subScene.setTranslateX(6);
1257         subScene.setTranslateY(11);
1258 
1259         TestScene scene = new TestScene(new Group(subScene));
1260         final TestStage testStage = new TestStage("");
1261         testStage.setX(100);
1262         testStage.setY(200);
1263         scene.set_window(testStage);
1264 
1265         Point2D p = rect.localToScreen(new Point2D(1, 2));
1266         assertEquals(111.0, p.getX(), 0.0001);
1267         assertEquals(222.0, p.getY(), 0.0001);
1268         Bounds b = rect.localToScreen(new BoundingBox(1, 2, 3, 4));
1269         assertEquals(111.0, b.getMinX(), 0.0001);
1270         assertEquals(222.0, b.getMinY(), 0.0001);
1271         assertEquals(3.0, b.getWidth(), 0.0001);
1272         assertEquals(4.0, b.getHeight(), 0.0001);
1273     }
1274 
1275     @Test
1276     public void testScreenToLocalInsideSubScene() {
1277         Rectangle rect = new Rectangle();
1278         rect.setTranslateX(4);
1279         rect.setTranslateY(9);
1280         SubScene subScene = new SubScene(new Group(rect), 100, 100);
1281         subScene.setTranslateX(6);
1282         subScene.setTranslateY(11);
1283 
1284         TestScene scene = new TestScene(new Group(subScene));
1285         final TestStage testStage = new TestStage("");
1286         testStage.setX(100);
1287         testStage.setY(200);
1288         scene.set_window(testStage);
1289 
1290         assertEquals(new Point2D(1, 2), rect.screenToLocal(new Point2D(111, 222)));
1291         assertEquals(new BoundingBox(1, 2, 3, 4), rect.screenToLocal(new BoundingBox(111, 222, 3, 4)));
1292     }
1293 
1294     @Test
1295     public void test2DLocalToScreenOn3DRotatedSubScene() {
1296         Rectangle rect = new Rectangle();
1297         rect.setTranslateX(5);
1298         rect.setTranslateY(10);
1299         SubScene subScene = new SubScene(new Group(rect), 100, 100);
1300         subScene.setTranslateX(5);
1301         subScene.setTranslateY(10);
1302         subScene.setRotationAxis(Rotate.Y_AXIS);
1303         subScene.setRotate(40);
1304 
1305         TestScene scene = new TestScene(new Group(subScene));
1306         scene.setCamera(new PerspectiveCamera());
1307         final TestStage testStage = new TestStage("");
1308         testStage.setX(100);
1309         testStage.setY(200);
1310         scene.set_window(testStage);
1311 
1312         Point2D p = rect.localToScreen(new Point2D(1, 2));
1313         assertEquals(124.36, p.getX(), 0.1);
1314         assertEquals(226.0, p.getY(), 0.1);
1315         Bounds b = rect.localToScreen(new BoundingBox(1, 2, 3, 4));
1316         assertEquals(124.36, b.getMinX(), 0.1);
1317         assertEquals(225.75, b.getMinY(), 0.1);
1318         assertEquals(1.85, b.getWidth(), 0.1);
1319         assertEquals(3.76, b.getHeight(), 0.1);
1320     }
1321 
1322     @Test
1323     public void test2DScreenToLocalTo3DRotatedSubScene() {
1324         Rectangle rect = new Rectangle();
1325         rect.setTranslateX(5);
1326         rect.setTranslateY(10);
1327         SubScene subScene = new SubScene(new Group(rect), 100, 100);
1328         subScene.setTranslateX(5);
1329         subScene.setTranslateY(10);
1330         subScene.setRotationAxis(Rotate.Y_AXIS);
1331         subScene.setRotate(40);
1332 
1333         TestScene scene = new TestScene(new Group(subScene));
1334         scene.setCamera(new PerspectiveCamera());
1335         final TestStage testStage = new TestStage("");
1336         testStage.setX(100);
1337         testStage.setY(200);
1338         scene.set_window(testStage);
1339 
1340         Point2D p = rect.screenToLocal(new Point2D(124.36, 226.0));
1341         assertEquals(1, p.getX(), 0.1);
1342         assertEquals(2, p.getY(), 0.1);
1343         Bounds b = rect.screenToLocal(new BoundingBox(124.36, 225.75, 1.85, 3.76));
1344         assertEquals(1, b.getMinX(), 0.1);
1345         assertEquals(1.72, b.getMinY(), 0.1);
1346         assertEquals(3, b.getWidth(), 0.1);
1347         assertEquals(4.52, b.getHeight(), 0.1);
1348     }
1349 
1350     @Test
1351     public void testScreenToLocalWithNonInvertibleTransform() {
1352         Rectangle rect = new Rectangle();
1353 
1354         rect.setScaleX(0.0);
1355 
1356         TestScene scene = new TestScene(new Group(rect));
1357         final TestStage testStage = new TestStage("");
1358         testStage.setX(100);
1359         testStage.setY(200);
1360         scene.set_window(testStage);
1361 
1362         assertNull(rect.screenToLocal(new Point2D(111, 222)));
1363         assertNull(rect.screenToLocal(new BoundingBox(111, 222, 3, 4)));
1364     }
1365 
1366     @Test
1367     public void testScreenToLocalInsideNonInvertibleSubScene() {
1368         Rectangle rect = new Rectangle();
1369         rect.setTranslateX(4);
1370         rect.setTranslateY(9);
1371         SubScene subScene = new SubScene(new Group(rect), 100, 100);
1372         subScene.setScaleX(0.0);
1373 
1374         TestScene scene = new TestScene(new Group(subScene));
1375         final TestStage testStage = new TestStage("");
1376         testStage.setX(100);
1377         testStage.setY(200);
1378         scene.set_window(testStage);
1379 
1380         assertNull(rect.screenToLocal(new Point2D(111, 222)));
1381         assertNull(rect.screenToLocal(new BoundingBox(111, 222, 3, 4)));
1382     }
1383 
1384     @Test
1385     public void testRootMirroringWithTranslate() {
1386         final Group rootGroup = new Group();
1387         rootGroup.setTranslateX(20);
1388         rootGroup.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
1389         final Scene scene = new Scene(rootGroup, 200, 200);
1390 
1391         final Point2D trPoint = scene.getRoot().localToScene(0, 0);
1392         assertEquals(180, trPoint.getX(), 0.1);
1393     }
1394 
1395 
1396     @Test
1397     public void testLayoutXYTriggersParentSizeChange() {
1398         final Group rootGroup = new Group();
1399         final Group subGroup = new Group();
1400         rootGroup.getChildren().add(subGroup);
1401 
1402         Rectangle r = new Rectangle(50,50);
1403         r.setManaged(false);
1404         Rectangle staticR = new Rectangle(1,1);
1405         subGroup.getChildren().addAll(r, staticR);
1406 
1407         assertEquals(50,subGroup.getLayoutBounds().getWidth(), 1e-10);
1408         assertEquals(50,subGroup.getLayoutBounds().getHeight(), 1e-10);
1409 
1410         r.setLayoutX(50);
1411 
1412         rootGroup.layout();
1413 
1414         assertEquals(100,subGroup.getLayoutBounds().getWidth(), 1e-10);
1415         assertEquals(50,subGroup.getLayoutBounds().getHeight(), 1e-10);
1416 
1417     }
1418 
1419     @Test
1420     public void testLayoutXYWontBreakLayout() {
1421         final Group rootGroup = new Group();
1422         final AnchorPane pane = new AnchorPane();
1423         rootGroup.getChildren().add(pane);
1424 
1425         Rectangle r = new Rectangle(50,50);
1426         pane.getChildren().add(r);
1427 
1428         AnchorPane.setLeftAnchor(r, 10d);
1429         AnchorPane.setTopAnchor(r, 10d);
1430 
1431         rootGroup.layout();
1432 
1433         assertEquals(10, r.getLayoutX(), 1e-10);
1434         assertEquals(10, r.getLayoutY(), 1e-10);
1435 
1436         r.setLayoutX(50);
1437 
1438         assertEquals(50, r.getLayoutX(), 1e-10);
1439         assertEquals(10, r.getLayoutY(), 1e-10);
1440 
1441         rootGroup.layout();
1442 
1443         assertEquals(10, r.getLayoutX(), 1e-10);
1444         assertEquals(10, r.getLayoutY(), 1e-10);
1445 
1446     }
1447 
1448     @Test
1449     public void clipShouldUpdateAfterParentVisibilityChange() {
1450 
1451         final Group root = new Group();
1452         Scene scene = new Scene(root, 300, 300);
1453 
1454         final Group parent = new Group();
1455         parent.setVisible(false);
1456 
1457         final Circle circle = new Circle(100, 100, 100);
1458         parent.getChildren().add(circle);
1459 
1460         final Rectangle clip = new Rectangle(100, 100) {
1461             @Override protected NGNode impl_createPeer() {
1462                 return new MockNGRect();
1463             }
1464         };
1465         circle.setClip(clip);
1466 
1467         root.getChildren().add(parent);
1468         parent.setVisible(true);
1469 
1470         Stage stage = new Stage();
1471         stage.setScene(scene);
1472         stage.show();
1473 
1474         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1475 
1476         clip.setWidth(300);
1477 
1478         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1479 
1480         assertEquals(300, ((MockNGRect) clip.impl_getPeer()).w, 1e-10);
1481     }
1482 
1483     @Test
1484     public void untransformedNodeShouldSyncIdentityTransform() {
1485         final Node node = createTestRect();
1486         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1487         assertSame(BaseTransform.IDENTITY_TRANSFORM,
1488                 ((MockNGRect) node.impl_getPeer()).t);
1489     }
1490 
1491     @Test
1492     public void nodeTransfomedByIdentitiesShouldSyncIdentityTransform() {
1493         final Node node = createTestRect();
1494         node.setRotationAxis(Rotate.X_AXIS);
1495         node.getTransforms().add(new Translate());
1496         node.getTransforms().add(new Scale());
1497         node.getTransforms().add(new Affine());
1498         node.getTransforms().add(new Rotate(0, Rotate.Y_AXIS));
1499         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1500         assertSame(BaseTransform.IDENTITY_TRANSFORM,
1501                 ((MockNGRect) node.impl_getPeer()).t);
1502     }
1503 
1504     @Test
1505     public void translatedNodeShouldSyncTranslateTransform1() {
1506         final Node node = createTestRect();
1507         node.setTranslateX(30);
1508         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1509         assertSame(Translate2D.class,
1510                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1511     }
1512 
1513     @Test
1514     public void translatedNodeShouldSyncTranslateTransform2() {
1515         final Node node = createTestRect();
1516         node.getTransforms().add(new Translate(20, 10));
1517         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1518         assertSame(Translate2D.class,
1519                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1520     }
1521 
1522     @Test
1523     public void multitranslatedNodeShouldSyncTranslateTransform() {
1524         final Node node = createTestRect();
1525         node.setTranslateX(30);
1526         node.getTransforms().add(new Translate(20, 10));
1527         node.getTransforms().add(new Translate(10, 20));
1528         node.getTransforms().add(new Translate(5, 5, 0));
1529         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1530         assertSame(Translate2D.class,
1531                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1532     }
1533 
1534     @Test
1535     public void mirroringShouldSyncAffine2DTransform() {
1536         final Node node = createTestRect();
1537         node.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
1538         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1539         assertSame(Affine2D.class,
1540                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1541     }
1542 
1543     @Test
1544     public void rotatedNodeShouldSyncAffine2DTransform1() {
1545         final Node node = createTestRect();
1546         node.setRotate(20);
1547         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1548         assertSame(Affine2D.class,
1549                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1550     }
1551 
1552     @Test
1553     public void rotatedNodeShouldSyncAffine2DTransform2() {
1554         final Node node = createTestRect();
1555         node.getTransforms().add(new Rotate(20));
1556         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1557         assertSame(Affine2D.class,
1558                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1559     }
1560 
1561     @Test
1562     public void multiRotatedNodeShouldSyncAffine2DTransform() {
1563         final Node node = createTestRect();
1564         node.setRotate(20);
1565         node.getTransforms().add(new Rotate(20));
1566         node.getTransforms().add(new Rotate(0, Rotate.X_AXIS));
1567         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1568         assertSame(Affine2D.class,
1569                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1570     }
1571 
1572     @Test
1573     public void scaledNodeShouldSyncAffine2DTransform1() {
1574         final Node node = createTestRect();
1575         node.setScaleX(2);
1576         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1577         assertSame(Affine2D.class,
1578                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1579     }
1580 
1581     @Test
1582     public void scaledNodeShouldSyncAffine2DTransform2() {
1583         final Node node = createTestRect();
1584         node.getTransforms().add(new Scale(2, 1));
1585         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1586         assertSame(Affine2D.class,
1587                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1588     }
1589 
1590     @Test
1591     public void multiScaledNodeShouldSyncAffine2DTransform() {
1592         final Node node = createTestRect();
1593         node.setScaleX(20);
1594         node.getTransforms().add(new Scale(2, 1));
1595         node.getTransforms().add(new Scale(0.5, 2, 1));
1596         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1597         assertSame(Affine2D.class,
1598                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1599     }
1600 
1601     @Test
1602     public void shearedNodeShouldSyncAffine2DTransform() {
1603         final Node node = createTestRect();
1604         node.getTransforms().add(new Shear(2, 1));
1605         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1606         assertSame(Affine2D.class,
1607                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1608     }
1609 
1610     @Test
1611     public void ztranslatedNodeShouldSyncAffine3DTransform1() {
1612         final Node node = createTestRect();
1613         node.setTranslateZ(30);
1614         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1615         assertSame(Affine3D.class,
1616                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1617     }
1618 
1619     @Test
1620     public void ztranslatedNodeShouldSyncAffine3DTransform2() {
1621         final Node node = createTestRect();
1622         node.getTransforms().add(new Translate(0, 0, 10));
1623         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1624         assertSame(Affine3D.class,
1625                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1626     }
1627 
1628     @Test
1629     public void zscaledNodeShouldSyncAffine3DTransform1() {
1630         final Node node = createTestRect();
1631         node.setScaleZ(0.5);
1632         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1633         assertSame(Affine3D.class,
1634                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1635     }
1636 
1637     @Test
1638     public void zscaledNodeShouldSyncAffine3DTransform2() {
1639         final Node node = createTestRect();
1640         node.getTransforms().add(new Scale(1, 1, 2));
1641         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1642         assertSame(Affine3D.class,
1643                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1644     }
1645 
1646     @Test
1647     public void nonZRotatedNodeShouldSyncAffine3DTransform1() {
1648         final Node node = createTestRect();
1649         node.setRotationAxis(Rotate.Y_AXIS);
1650         node.setRotate(10);
1651         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1652         assertSame(Affine3D.class,
1653                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1654     }
1655 
1656     @Test
1657     public void nonZRotatedNodeShouldSyncAffine3DTransform2() {
1658         final Node node = createTestRect();
1659         node.getTransforms().add(new Rotate(10, Rotate.X_AXIS));
1660         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1661         assertSame(Affine3D.class,
1662                 ((MockNGRect) node.impl_getPeer()).t.getClass());
1663     }
1664 
1665     @Test
1666     public void translateTransformShouldBeReusedWhenPossible() {
1667         final Node node = createTestRect();
1668         node.setTranslateX(10);
1669         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1670 
1671         BaseTransform t = ((MockNGRect) node.impl_getPeer()).t;
1672 
1673         ((MockNGRect) node.impl_getPeer()).t = null;
1674         node.setTranslateX(20);
1675         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1676 
1677         assertSame(t, ((MockNGRect) node.impl_getPeer()).t);
1678     }
1679 
1680     @Test
1681     public void affine2DTransformShouldBeReusedWhenPossible() {
1682         final Node node = createTestRect();
1683         node.setScaleX(10);
1684         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1685 
1686         BaseTransform t = ((MockNGRect) node.impl_getPeer()).t;
1687 
1688         ((MockNGRect) node.impl_getPeer()).t = null;
1689         node.setRotate(20);
1690         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1691 
1692         assertSame(t, ((MockNGRect) node.impl_getPeer()).t);
1693     }
1694 
1695     @Test
1696     public void affine3DTransformShouldBeReusedWhenPossible() {
1697         final Node node = createTestRect();
1698         node.setScaleZ(10);
1699         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1700 
1701         BaseTransform t = ((MockNGRect) node.impl_getPeer()).t;
1702 
1703         ((MockNGRect) node.impl_getPeer()).t = null;
1704         node.setRotate(20);
1705         ((StubToolkit) Toolkit.getToolkit()).firePulse();
1706 
1707         assertSame(t, ((MockNGRect) node.impl_getPeer()).t);
1708     }
1709 
1710     @Test
1711     public void rtlSceneSizeShouldBeComputedCorrectly() {
1712         Scene scene = new Scene(new Group(new Rectangle(100, 100)));
1713         scene.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
1714         Stage stage = new Stage();
1715         stage.setScene(scene);
1716         stage.show();
1717         assertEquals(100.0, scene.getWidth(), 0.00001);
1718     }
1719 
1720     private Node createTestRect() {
1721         final Rectangle rect = new Rectangle() {
1722             @Override protected NGNode impl_createPeer() {
1723                 return new MockNGRect();
1724             }
1725         };
1726         Scene scene = new Scene(new Group(rect));
1727         Stage stage = new Stage();
1728         stage.setScene(scene);
1729         stage.show();
1730         return rect;
1731     }
1732 
1733     private class MockNGRect extends NGRectangle {
1734         double w = 0;
1735         BaseTransform t = null;
1736 
1737         @Override public void updateRectangle(float x, float y, float width,
1738                 float height, float arcWidth, float arcHeight) {
1739             w = width;
1740         }
1741 
1742         @Override
1743         public void setTransformMatrix(BaseTransform tx) {
1744             t = tx;
1745         }
1746     }
1747 }