1 /*
   2  * Copyright (c) 2011, 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 javafx.collections.ObservableList;
  29 import com.sun.javafx.geom.BaseBounds;
  30 import com.sun.javafx.geom.transform.BaseTransform;
  31 import com.sun.javafx.jmx.MXNodeAlgorithm;
  32 import com.sun.javafx.jmx.MXNodeAlgorithmContext;
  33 import com.sun.javafx.sg.prism.NGGroup;
  34 import com.sun.javafx.sg.prism.NGNode;
  35 import javafx.scene.Group;
  36 import javafx.scene.Node;
  37 import javafx.scene.NodeShim;
  38 import javafx.scene.Parent;
  39 import javafx.scene.ParentShim;
  40 import javafx.scene.Scene;
  41 import org.junit.Rule;
  42 import org.junit.Test;
  43 import org.junit.rules.ExpectedException;
  44 import static org.junit.Assert.assertEquals;
  45 import static org.junit.Assert.assertNull;
  46 import static org.junit.Assert.assertSame;
  47 import static org.junit.Assert.assertTrue;
  48 import static org.junit.Assert.fail;
  49 import test.com.sun.javafx.scene.StubNodeHelper;
  50 import test.com.sun.javafx.scene.StubParentHelper;
  51 
  52 
  53 /**
  54  * Tests structural aspects of scene graph manipulation. See RT-4095.
  55  *
  56  * The following notation is used in test names to indicate various
  57  * relationships:
  58  *   CL = clip child
  59  *   G  = Group child
  60  *   S  = Scene child
  61  *
  62  * Several relationships are checked in each test, typically in
  63  * the following order:
  64  *
  65  * parent's clip
  66  * parent's children (for Group and Region)
  67  * relationships of second parent (if any)
  68  * child's clipParent
  69  * child's parent
  70  * child's scene
  71  */
  72 
  73 public class StructureTest {
  74     @Rule
  75     public ExpectedException thrown = ExpectedException.none();
  76 
  77     // TODO:
  78     //  - various nasty observableArrayList updates to Group.content and Scene.content.
  79     //  - various bind expressions.
  80 
  81     /////////////////////////
  82     // Setup and teardown. //
  83     /////////////////////////
  84 
  85     //////////////////////
  86     // Helper Functions //
  87     //////////////////////
  88 
  89     int occurs(Node child, ObservableList<Node> content) {
  90         int count = 0;
  91         if (content != null) {
  92             for (Node node : content) {
  93                 if (node == child) {
  94                     count++;
  95                 }
  96             }
  97         }
  98         return count;
  99     }
 100 
 101     /**
 102      * Checks whether child occurs exactly once in the content observableArrayList.
 103      */
 104     boolean occursOnce(Node child, ObservableList<Node> content) {
 105         return 1 == occurs(child, content);
 106     }
 107 
 108     /**
 109      * Checks whether child does not occur in the content observableArrayList.
 110      */
 111     boolean occursZero(Node child, ObservableList<Node> content) {
 112         return 0 == occurs(child, content);
 113     }
 114 
 115     /**
 116      * Checks whether the child node appears exactly once in the
 117      * Group's content observableArrayList.
 118      */
 119     boolean isChild(Node child, Group group) {
 120         return occursOnce(child, ParentShim.getChildren(group));
 121     }
 122 
 123     /**
 124      * Checks whether the child node is the root of the Scene.
 125      */
 126     boolean isRoot(Parent root, Scene scene) {
 127         return root == scene.getRoot();
 128     }
 129 
 130     /**
 131      * Checks whether the child node does not appear in the
 132      * Group's content observableArrayList.
 133      *
 134      * We use assertTrue(notChild(child, parent)) instead of
 135      * assertFalse(isChild(child, parent)) because we want
 136      * to catch cases where child occurs more than once in
 137      * the parent's content observableArrayList.
 138      */
 139     boolean notChild(Node child, Group group) {
 140         return occursZero(child, ParentShim.getChildren(group));
 141     }
 142 
 143     /**
 144      * Checks whether the child node does not appear as the root of the
 145      * Scene.
 146      */
 147     boolean notRoot(Parent root, Scene scene) {
 148         return root != scene.getRoot();
 149     }
 150 
 151     /////////////////////////////////////
 152     // Simple Structural Relationships //
 153     /////////////////////////////////////
 154 
 155     @Test
 156     public void testOrphan() {
 157         Node n = new StubNode();
 158 
 159         assertNull("clipParent is null", NodeShim.getClipParent(n));
 160         assertNull("parent is null", n.getParent());
 161         assertNull("scene is null", n.getScene());
 162     }
 163 
 164     @Test
 165     public void testSimpleCL() {
 166         StubNode parent = new StubNode();
 167         StubNode child = new StubNode();
 168         parent.setClip(child);
 169 
 170         assertSame("parent.clip is child", child, parent.getClip());
 171         assertSame("child.clipParent is parent", parent, NodeShim.getClipParent(child));
 172         assertNull("child.parent is null", child.getParent());
 173         assertNull("scene is null", child.getScene());
 174     }
 175 
 176     @Test
 177     public void testSimpleG() {
 178         StubNode child = new StubNode();
 179         Group group = new Group(child);
 180 
 181         assertNull("group.clip is null", group.getClip());
 182         assertTrue("isChild of group", isChild(child, group));
 183         assertNull("child.clipParent is null", NodeShim.getClipParent(child));
 184         assertSame("child.parent is parent", group, child.getParent());
 185         assertNull("child.getScene() is null", child.getScene());
 186     }
 187 
 188     @Test
 189     public void testSimpleS() {
 190         StubParent root = new StubParent();
 191         Scene scene = new Scene(root);
 192 
 193         assertTrue("isChild of scene", isRoot(root, scene));
 194         assertNull("child.clipParent is null", NodeShim.getClipParent(root));
 195         assertSame("child.getScene() is scene", scene, root.getScene());
 196     }
 197 
 198     @Test
 199     public void testSceneInsertGroup1() {
 200         StubNode child = new StubNode();
 201         Group group = new Group();
 202         Scene scene = new Scene(group);
 203         ParentShim.getChildren(group).add(child);
 204 
 205         assertSame("group.getScene() is scene", scene, group.getScene());
 206         assertSame("child.getScene() is scene", scene, child.getScene());
 207     }
 208 
 209     @Test
 210     public void testSceneInsertGroup2() {
 211         StubNode child = new StubNode();
 212         Group group = new Group();
 213         Scene scene = new Scene(group);
 214         ParentShim.getChildren(group).add(child);
 215 
 216 
 217         assertSame("group.getScene() is scene", scene, group.getScene());
 218         assertSame("child.getScene() is scene", scene, child.getScene());
 219     }
 220 
 221     @Test public void testUnparentCL() {
 222         StubNode child = new StubNode();
 223         StubNode parent = new StubNode();
 224         parent.setClip(child);
 225         parent.setClip(null);
 226 
 227         assertNull("parent.clip is null", parent.getClip());
 228         assertNull("child.clipParent is null", NodeShim.getClipParent(child));
 229     }
 230 
 231     @Test public void testUnparentG() {
 232         StubNode child = new StubNode();
 233         Group parent = new Group(child);
 234 
 235         ParentShim.getChildren(parent).remove(child);
 236 
 237 
 238         assertEquals("parent.content is zero size", 0, ParentShim.getChildren(parent).size());
 239         assertNull("child.parent is null", child.getParent());
 240     }
 241 
 242     ////////////////////////////////////
 243     // Illegal Structure Change Tests //
 244     ////////////////////////////////////
 245 
 246     // Test attempts to switch from one part of the scene graph to another.
 247     // This is the cross product: {CL,CU,G,S}x{CL,CU,G,S} so there
 248     // are sixteen cases.
 249 
 250     @Test public void testSwitchCLCL() {
 251         StubNode child = new StubNode();
 252         StubNode p1 = new StubNode();
 253         p1.setClip(child);
 254         StubNode p2 = new StubNode();
 255         thrown.expect(IllegalArgumentException.class);
 256         try {
 257             p2.setClip(child);
 258         } catch (final IllegalArgumentException e) {
 259             assertSame("p1.clip is child", child, p1.getClip());
 260             assertNull("p2.clip is null", p2.getClip());
 261             assertSame("child.clipParent is p1",
 262                        p1, NodeShim.getClipParent(child));
 263             assertNull("child.parent is null", child.getParent());
 264             assertNull("child.getScene() is null", child.getScene());
 265             throw e;
 266         }
 267     }
 268 
 269     @Test public void testSwitchCLG() {
 270         StubNode child = new StubNode();
 271         StubNode p1 = new StubNode();
 272         p1.setClip(child);
 273         Group p2 = new Group();
 274         ObservableList<Node> content = ParentShim.getChildren(p2);
 275         try {
 276             content.add(child);
 277             fail("IllegalArgument should have been thrown.");
 278         } catch (IllegalArgumentException iae) {
 279             // expected
 280         }
 281 
 282         assertSame("p1.clip is child", child, p1.getClip());
 283         assertNull("p2.clip is null", p2.getClip());
 284         assertTrue("notChild of p2", notChild(child, p2));
 285         assertSame("child.clipParent is p1", p1, NodeShim.getClipParent(child));
 286         assertNull("child.parent is null", child.getParent());
 287         assertNull("child.getScene() is null", child.getScene());
 288     }
 289 
 290     @Test public void testSwitchCLS() {
 291         StubParent clipNode = new StubParent();
 292         StubNode p1 = new StubNode();
 293         p1.setClip(clipNode);
 294         try {
 295             Scene p2 = new Scene(clipNode);
 296             fail("IllegalArgument should have been thrown.");
 297         } catch (Throwable t) {
 298             //expected
 299         }
 300         assertSame("p1.clip is child", clipNode, p1.getClip());
 301         assertSame("child.clipParent is p1", p1, NodeShim.getClipParent(clipNode));
 302         assertNull("child.parent is null", clipNode.getParent());
 303         assertNull("child.getScene() is null", clipNode.getScene());
 304     }
 305 
 306     @Test public void testSwitchGCL() {
 307         StubNode child = new StubNode();
 308         Group p1 = new Group(child);
 309         StubNode p2 = new StubNode();
 310         thrown.expect(IllegalArgumentException.class);
 311         try {
 312             p2.setClip(child);
 313         } catch (final IllegalArgumentException e) {
 314             assertNull("p1.clip is null", p1.getClip());
 315             assertTrue("isChild of p1", isChild(child, p1));
 316             assertNull("p2.clip is null", p2.getClip());
 317             assertNull("child.clipParent is null", NodeShim.getClipParent(child));
 318             assertSame("child.parent is p1", p1, child.getParent());
 319             assertNull("child.getScene() is null", child.getScene());
 320             throw e;
 321         }
 322     }
 323 
 324 // TODO XXX TEMPORARY STOPGAP POLICY RT-4095 -- TEST DISABLED
 325 
 326 //    @Test public void testSwitchGG() {
 327 //        var child = new StubNode();
 328 //        var p1 = Group { content: [ child ] };
 329 //        setHandler();
 330 //        var p2 = Group { content: [ child ] };
 331 //
 332 //        assertTrue("caught IllegalArgumentException", caught instanceof IllegalArgumentException);
 333 //        assertNull("p1.clip is null", p1.clip);
 334 //        assertTrue("isChild of p1", isChild(child, p1));
 335 //        assertNull("p2.clip is null", p2.clip);
 336 //        assertTrue("notChild of p2", notChild(child, p2));
 337 //        assertNull("child.clipParent is null", NodeShim.getClipParent(child));
 338 //        assertSame("child.parent is p1", p1, child.parent);
 339 //        assertNull("child.getScene() is null", child.getScene());
 340 //    }
 341 
 342 // TODO XXX TEMPORARY STOPGAP POLICY RT-4095 -- TEST DISABLED
 343 
 344 //    @Test public void testSwitchGS() {
 345 //        var child = new StubNode();
 346 //        var p1 = Group { content: [ child ] };
 347 //        setHandler();
 348 //        var p2 = Scene { content: [ child ] };
 349 //
 350 //        assertTrue("caught IllegalArgumentException", caught instanceof IllegalArgumentException);
 351 //        assertNull("p1.clip is null", p1.clip);
 352 //        assertTrue("isChild of p1", isChild(child, p1));
 353 //        assertTrue("notChild of p2", notChild(child, p2));
 354 //        assertNull("child.clipParent is null", NodeShim.getClipParent(child));
 355 //        assertSame("child.parent is p1", p1, child.parent);
 356 //        assertNull("child.getScene() is null", child.getScene());
 357 //    }
 358 
 359 // TODO XXX TEMPORARY STOPGAP POLICY RT-4095 -- TEST OF STOPGAP POLICY
 360 
 361     @Test public void testSwitchGGStopgap() {
 362         StubNode child = new StubNode();
 363         Group p1 = new Group(child);
 364         Group p2 = new Group(child);
 365 
 366         assertTrue("notChild of p1", notChild(child, p1));
 367         assertTrue("isChild of p2", isChild(child, p2));
 368         assertSame("child.parent is p2", p2, child.getParent());
 369     }
 370 
 371     @Test public void testSwitchSCL() {
 372         StubParent root = new StubParent();
 373         Scene scene = new Scene(root);
 374         StubNode p2 = new StubNode();
 375         thrown.expect(IllegalArgumentException.class);
 376         try {
 377             p2.setClip(root);
 378         } catch (final IllegalArgumentException e) {
 379             assertTrue("isRoot of scene", isRoot(root, scene));
 380             assertNull("p2.clip is null", p2.getClip());
 381             assertNull("root.clipParent is null", NodeShim.getClipParent(root));
 382             assertSame("root.getScene() is scene", scene, root.getScene());
 383             throw e;
 384         }
 385     }
 386 
 387 
 388 // TODO XXX TEMPORARY STOPGAP POLICY RT-4095 -- TEST DISABLED
 389 
 390 //    @Test public void testSwitchSG() {
 391 //        var child = new StubNode();
 392 //        var p1 = Scene { content: [ child ] };
 393 //        setHandler();
 394 //        var p2 = Group { content: [ child ] };
 395 //
 396 //        assertTrue("caught IllegalArgumentException", caught instanceof IllegalArgumentException);
 397 //        assertTrue("isChild of p1", isChild(child, p1));
 398 //        assertNull("p2.clip is null", p2.clip);
 399 //        assertTrue("notChild of p2", notChild(child, p2));
 400 //        assertNull("child.clipParent is null", NodeShim.getClipParent(child));
 401 //        assertSame("child.parent is p1.impl_root", p1.impl_root, child.parent);
 402 //        assertSame("child.getScene() is p1", p1, child.getScene());
 403 //    }
 404 
 405 // TODO XXX TEMPORARY STOPGAP POLICY RT-4095 -- TEST DISABLED
 406 
 407 //    @Test public void testSwitchSS() {
 408 //        var child = new StubNode();
 409 //        var p1 = Scene { content: [ child ] };
 410 //        setHandler();
 411 //        var p2 = Scene { content: [ child ] };
 412 //
 413 //        assertTrue("isChild of p1", isChild(child, p1));
 414 //        assertTrue("notChild of p2", notChild(child, p2));
 415 //        assertNull("child.clipParent is null", NodeShim.getClipParent(child));
 416 //        assertSame("child.parent is p1.impl_root", p1.impl_root, child.parent);
 417 //        assertSame("child.getScene() is p1", p1, child.getScene());
 418 //    }
 419 
 420     @Test public void testGroupInsert() {
 421         StubNode n0 = new StubNode();
 422         n0.setId("n0");
 423         StubNode n1 = new StubNode();
 424         n1.setId("n1");
 425         StubNode n2 = new StubNode();
 426         n2.setId("n2");
 427         Group g = new Group(n0, n1, n2);
 428 
 429         ObservableList<Node> content = ParentShim.getChildren(g);
 430         try {
 431             content.add(n1);
 432             fail("IllegalArgument should have been thrown.");
 433         } catch (IllegalArgumentException iae) {
 434             // expected
 435         }
 436 
 437         assertEquals("g.content is size 3", 3, ParentShim.getChildren(g).size());
 438         assertSame("g.content[0] is n0", n0, ParentShim.getChildren(g).get(0));
 439         assertSame("g.content[1] is n1", n1, ParentShim.getChildren(g).get(1));
 440         assertSame("g.content[2] is n2", n2, ParentShim.getChildren(g).get(2));
 441 
 442     }
 443 
 444 
 445     @Test public void testGroupReplace1() {
 446         StubNode n0 = new StubNode();
 447         n0.setId("n0");
 448         StubNode n1 = new StubNode();
 449         n1.setId("n1");
 450         StubNode n2 = new StubNode();
 451         n2.setId("n2");
 452         Group g = new Group(n0, n1, n2);
 453 
 454         ParentShim.getChildren(g).remove(1);
 455         ObservableList<Node> n = javafx.collections.FXCollections.<Node>observableArrayList();
 456         n.addAll(n1,n1);
 457         ObservableList<Node> content = ParentShim.getChildren(g);
 458         try {
 459             content.addAll(1, n);
 460             fail("IllegalArgument should have been thrown.");
 461         } catch (IllegalArgumentException iae) {
 462             // expected
 463         }
 464 
 465         assertEquals("g.content is size 2", 2, ParentShim.getChildren(g).size());
 466         assertSame("g.content[0] is n0", n0, ParentShim.getChildren(g).get(0));
 467         assertSame("g.content[1] is n2", n2, ParentShim.getChildren(g).get(1));
 468     }
 469 
 470     @Test public void testGroupReplace2() {
 471         StubNode n0 = new StubNode();
 472         n0.setId("n0");
 473         StubNode n1 = new StubNode();
 474         n1.setId("n1");
 475         StubNode n2 = new StubNode();
 476         n2.setId("n2");
 477         Group g = new Group(n0, n1, n2);
 478 
 479         try {
 480             ParentShim.getChildren(g).set(1, n0);
 481             fail("No exception thrown.");
 482         } catch (IllegalArgumentException e) {
 483             //Expected
 484         }
 485 
 486         assertEquals("g.content is size 3", 3, ParentShim.getChildren(g).size());
 487         assertSame("g.content[0] is n0", n0, ParentShim.getChildren(g).get(0));
 488         assertSame("g.content[1] is n1", n1, ParentShim.getChildren(g).get(1));
 489         assertSame("g.content[2] is n2", n2, ParentShim.getChildren(g).get(2));
 490     }
 491 
 492     @Test public void testGroupReplace3() {
 493         StubNode n0 = new StubNode();
 494         n0.setId("n0");
 495         StubNode n1 = new StubNode();
 496         n1.setId("n1");
 497         StubNode n2 = new StubNode();
 498         n2.setId("n2");
 499         Group g = new Group(n0, n1, n2);
 500         ParentShim.getChildren(g).set(1, n1);
 501         ParentShim.getChildren(g).set(2, n2);
 502 
 503         assertEquals("g.content is size 3", 3, ParentShim.getChildren(g).size());
 504         assertSame("g.content[0] is n0", n0, ParentShim.getChildren(g).get(0));
 505         assertSame("g.content[1] is n1", n1, ParentShim.getChildren(g).get(1));
 506         assertSame("g.content[2] is n2", n2, ParentShim.getChildren(g).get(2));
 507     }
 508 
 509     ///////////////////////
 510     // Circularity Tests //
 511     ///////////////////////
 512 
 513     // General form is: given an existing relationship of one kind, add
 514     // another relationship of some kind that would cause a circularity.
 515     // This is the cross product: {CL,CU,G}x{CL,CU,G}. Also test degenerate
 516     // circularities where a node is its own clip, its own child.
 517     // The Scene relationship does not occur here, because a Scene cannot
 518     // participate in any circular relationship as it has no parent.
 519 
 520     // Test only {CL,G}x{CL,G} for now.
 521 
 522     @Test public void testCircularCLCL() {
 523         StubNode node1 = new StubNode();
 524         StubNode node2 = new StubNode();
 525         node2.setClip(node1);
 526         thrown.expect(IllegalArgumentException.class);
 527         try {
 528             node1.setClip(node2);
 529         } catch (final IllegalArgumentException e) {
 530             assertNull("node1.clip is null", node1.getClip());
 531             assertSame("node1.clipParent is node2",
 532                        node2,
 533                        NodeShim.getClipParent(node1));
 534             assertSame("node2.clip is node1", node1, node2.getClip());
 535             assertNull("node2.clipParent is null", NodeShim.getClipParent(node2));
 536             throw e;
 537         }
 538     }
 539 
 540     @Test public void testCircularCLG() {
 541         StubNode node1 = new StubNode();
 542         Group node2 = new Group(node1);
 543         thrown.expect(IllegalArgumentException.class);
 544         try {
 545             node1.setClip(node2);
 546         } catch (final IllegalArgumentException e) {
 547             assertNull("node1.clip is null", node1.getClip());
 548             assertNull("node1.clipParent is null", NodeShim.getClipParent(node1));
 549             assertSame("node1.parent is node2", node2, node1.getParent());
 550             assertNull("node2.clip is null", node2.getClip());
 551             assertNull("node2.clipParent is null", NodeShim.getClipParent(node2));
 552             assertTrue("node1 is child of node2", isChild(node1, node2));
 553             throw e;
 554         }
 555     }
 556 
 557     @Test public void testCircularGCL() {
 558         Group node1 = new Group();
 559         StubNode node2 = new StubNode();
 560         node2.setClip(node1);
 561 
 562         ObservableList<Node> content = ParentShim.getChildren(node1);
 563         try {
 564             content.add(node2);
 565             fail("IllegalArgument should have been thrown.");
 566         } catch (IllegalArgumentException iae) {
 567             // expected
 568         }
 569 
 570         assertNull("node1.clip is null", node1.getClip());
 571         assertSame("node1.clipParent is node2", node2, NodeShim.getClipParent(node1));
 572         assertTrue("node2 is not child of node1", notChild(node2, node1));
 573         assertSame("node2.clip is node1", node1, node2.getClip());
 574         assertNull("node2.clipParent is null", NodeShim.getClipParent(node2));
 575         assertNull("node2.parent is null", node2.getParent());
 576     }
 577 
 578     @Test public void testCircularGG() {
 579         Group node1 = new Group();
 580         Group node2 = new Group(node1);
 581 
 582         ObservableList<Node> content = ParentShim.getChildren(node1);
 583         try {
 584             content.add(node2);
 585             fail("IllegalArgument should have been thrown.");
 586         } catch (IllegalArgumentException iae) {
 587             // expected
 588         }
 589 
 590         assertSame("node1.parent is node2", node2, node1.getParent());
 591         assertTrue("node2 is not a child of node1", notChild(node2, node1));
 592         assertNull("node2.parent is null", node2.getParent());
 593         assertTrue("node1 is child of node2", isChild(node1, node2));
 594     }
 595 
 596     @Test public void testCircularSelfCL() {
 597         StubNode node1 = new StubNode();
 598         thrown.expect(IllegalArgumentException.class);
 599         try {
 600             node1.setClip(node1);
 601         } catch (final IllegalArgumentException e) {
 602             assertNull("node1.clip is null", node1.getClip());
 603             assertNull("node1.clipParent is null", NodeShim.getClipParent(node1));
 604             throw e;
 605         }
 606     }
 607 
 608     @Test public void testCircularSelfG() {
 609         Group node1 = new Group();
 610 
 611         ObservableList<Node> content = ParentShim.getChildren(node1);
 612         try {
 613             content.add(node1);
 614             fail("IllegalArgument should have been thrown.");
 615         } catch (IllegalArgumentException iae) {
 616             // expected
 617         }
 618 
 619         assertTrue("node1 is not a child of itself", notChild(node1, node1));
 620         assertNull("node1.parent is null", node1.getParent());
 621     }
 622 
 623     //////////////////////////
 624     // Bound Variable Tests //
 625     //////////////////////////
 626 
 627     // Test various cases where a structure variable (Node.clip,
 628     // Group.content, Scene.content) is initialized to a bind-expression.
 629     // If the trigger attempts to roll back a change to a variable
 630     // initialized this way, the attempt will fail and will throw an
 631     // exception. This will leave the invariant violation in place!
 632     // We can't do anything about this without language support, so
 633     // don't test these cases for now.
 634 
 635 // FAILS:
 636 //    @Test public void testBindClip() {
 637 //        var c:Node = null;
 638 //        var p1 = StubNode { clip: bind c id: "p1" };
 639 //        var p2 = StubNode { clip: bind c id: "p2" };
 640 //        c = StubNode { id: "c" };
 641 //
 642 //        println("testBindClip");
 643 //        println("p1 = {p1}");
 644 //        println("p2 = {p2}");
 645 //        println("c = {c}");
 646 //        println("p1.clip = {p1.clip}");
 647 //        println("p2.clip = {p2.clip}");
 648 //        println("c.clipParent = {c.getClipParent()}");
 649 //    }
 650 
 651     ////////////////////
 652     // Helper Classes //
 653     ////////////////////
 654 
 655     //
 656     // * A stub node that contains as little functionality as possible.
 657     // *
 658     public static final class StubNode extends Node {
 659         static {
 660             StubNodeHelper.setStubNodeAccessor(new StubNodeHelper.StubNodeAccessor() {
 661                 @Override
 662                 public NGNode doCreatePeer(Node node) {
 663                     return ((StubNode) node).doCreatePeer();
 664                 }
 665             });
 666         }
 667 
 668         public StubNode() {
 669             super();
 670             StubNodeHelper.initHelper(this);
 671         }
 672 
 673         // * Returning null causes crashes so return a NGGroup.
 674         private NGNode doCreatePeer() {
 675             return new NGGroup();
 676         }
 677 
 678         @Override
 679         public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
 680             return bounds;
 681         }
 682 
 683         @Override
 684         public boolean impl_computeContains(double localX, double localY) {
 685             // TODO: Missing code.
 686             return false;
 687         }
 688 
 689         @Override
 690         public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) {
 691             return null;
 692         }
 693     }
 694 
 695     public static final class StubParent extends Parent {
 696         static {
 697             StubParentHelper.setStubParentAccessor(new StubParentHelper.StubParentAccessor() {
 698                 @Override
 699                 public NGNode doCreatePeer(Node node) {
 700                     return ((StubParent) node).doCreatePeer();
 701                 }
 702             });
 703         }
 704 
 705         public StubParent() {
 706             super();
 707             StubParentHelper.initHelper(this);
 708         }
 709 
 710         // * Returning null causes crashes so return a PGGroup.
 711         private NGNode doCreatePeer() {
 712             return new NGGroup();
 713         }
 714 
 715         @Override
 716         public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
 717             return bounds;
 718         }
 719 
 720         @Override
 721         public boolean impl_computeContains(double localX, double localY) {
 722             // TODO: Missing code.
 723             return false;
 724         }
 725     }
 726 
 727 }