StateTree implements NodeOwner { public void collectChanges(Consumer<NodeChange> collector) { Set<StateNode> allDirtyNodes = new LinkedHashSet<>(); boolean evaluateNewDirtyNodes = true; while (evaluateNewDirtyNodes) { Set<StateNode> dirtyNodesSet = doCollectDirtyNodes(true); dirtyNodesSet.forEach(StateNode::updateActiveState); evaluateNewDirtyNodes = allDirtyNodes.addAll(dirtyNodesSet); } allDirtyNodes.forEach(node -> node.collectChanges(collector)); } @SafeVarargs  StateTree(UIInternals uiInternals,
            Class<? extends NodeFeature>... features); StateNode getRootNode(); @Override int register(StateNode node); @Override void unregister(StateNode node); @Override boolean hasNode(StateNode node); StateNode getNodeById(int id); void collectChanges(Consumer<NodeChange> collector); @Override void markAsDirty(StateNode node); Set<StateNode> collectDirtyNodes(); boolean hasDirtyNodes(); UI getUI(); ExecutionRegistration beforeClientResponse(StateNode context,
            SerializableConsumer<ExecutionContext> execution); void runExecutionsBeforeClientResponse(); boolean isDirty(); void prepareForResync();  }
@Test public void collectChanges_parentIsInactive_childrenAreCollected() { StateNode node1 = new CollectableNode(); StateNode node2 = new CollectableNode(); StateNode node3 = new CollectableNode(); node1.setTree(tree); node2.setTree(tree); node3.setTree(tree); node1.getFeature(ElementChildrenList.class).add(0, node2); node2.getFeature(ElementChildrenList.class).add(0, node3); tree.collectChanges(node -> { }); node1.getFeature(ElementData.class).setVisible(false); List<StateNode> collectedNodes = new ArrayList<>(3); tree.collectChanges(change -> collectedNodes.add(change.getNode())); Assert.assertEquals(3, collectedNodes.size()); Assert.assertTrue(collectedNodes.contains(node1)); Assert.assertTrue(collectedNodes.contains(node2)); Assert.assertTrue(collectedNodes.contains(node3)); }