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