StateNode implements Serializable { public Registration addDetachListener(Command detachListener) { assert detachListener != null; if (detachListeners == null) { detachListeners = new ArrayList<>(1); } detachListeners.add(detachListener); return () -> removeDetachListener(detachListener); } @SafeVarargs  StateNode(Class<? extends NodeFeature>... featureTypes); @SuppressWarnings("unchecked")  StateNode(StateNode node); @SafeVarargs  StateNode(List<Class<? extends NodeFeature>> reportableFeatureTypes,
            Class<? extends NodeFeature>... additionalFeatureTypes); NodeOwner getOwner(); StateNode getParent(); void setParent(StateNode parent); void removeFromTree(); T getFeature(Class<T> featureType); Optional<T> getFeatureIfInitialized(
            Class<T> featureType); boolean hasFeature(Class<? extends NodeFeature> featureType); int getId(); void markAsDirty(); boolean isAttached(); void collectChanges(Consumer<NodeChange> collector); void clearChanges(); void visitNodeTree(Consumer<StateNode> visitor); Registration addAttachListener(Command attachListener); Registration addDetachListener(Command detachListener); @SuppressWarnings("unchecked") T getChangeTracker(NodeFeature feature,
            Supplier<T> factory); void runWhenAttached(SerializableConsumer<UI> command); boolean isReportedFeature(Class<? extends NodeFeature> featureType); void updateActiveState(); boolean isInactive(); boolean hasBeforeClientResponseEntries(); List<StateTree.BeforeClientResponseEntry> dumpBeforeClientResponseEntries(); ExecutionRegistration addBeforeClientResponseEntry(
            BeforeClientResponseEntry entry); void setEnabled(boolean enabled); boolean isEnabled(); boolean isEnabledSelf();  }
@Test public void detachParent_detachFirstChildOnDetachLast_oneDetachEvent() { TestStateTree tree = new TestStateTree(); StateNode childA = createEmptyNode("a"); StateNode childB = createEmptyNode("b"); StateNode parent = createParentNode("parent"); addChild(parent, childA); addChild(parent, childB); addChild(tree.getRootNode(), parent); AtomicInteger detachEvents = new AtomicInteger(); childB.addDetachListener(() -> removeFromParent(childA)); childA.addDetachListener(() -> detachEvents.incrementAndGet()); removeFromParent(parent); Assert.assertEquals(1, detachEvents.get()); }
@Test public void detachParent_detachLastChildOnDetachFirst_oneDetachEvent() { TestStateTree tree = new TestStateTree(); StateNode childA = createEmptyNode("a"); StateNode childB = createEmptyNode("b"); StateNode parent = createParentNode("parent"); addChild(parent, childA); addChild(parent, childB); addChild(tree.getRootNode(), parent); AtomicInteger detachEvents = new AtomicInteger(); childA.addDetachListener(() -> removeFromParent(childA)); childB.addDetachListener(() -> detachEvents.incrementAndGet()); removeFromParent(parent); Assert.assertEquals(1, detachEvents.get()); }