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