Repository: flex-sdk Updated Branches: refs/heads/FLEX-34119 0a2c3bd83 -> 75e8db424
FLEX-34119 Adding two unit tests for HierarchicalCollectionViewCursor (HCVC). HierarchicalCollectionViewCursor_Basics_Test is meant to ensure that HCVC works as expected in non-corner cases (it's definitely incomplete, and could use more tests. I created it mainly to test the few functions I needed when working with FLEX-34119), while HierarchicalCollectionViewCursor_FLEX_34119_Test reproduces the FLEX-34119 bug (and, incidentally, also uncovered FLEX-34424). FYI The latter test lasts just under 3 minutes on my machine. Project: http://git-wip-us.apache.org/repos/asf/flex-sdk/repo Commit: http://git-wip-us.apache.org/repos/asf/flex-sdk/commit/0c4c9c00 Tree: http://git-wip-us.apache.org/repos/asf/flex-sdk/tree/0c4c9c00 Diff: http://git-wip-us.apache.org/repos/asf/flex-sdk/diff/0c4c9c00 Branch: refs/heads/FLEX-34119 Commit: 0c4c9c00d09eab81e6c43340df245f4c9ca239b0 Parents: 0a2c3bd Author: Mihai Chira <mihai.ch...@gmail.com> Authored: Tue Jul 22 09:18:18 2014 +0100 Committer: Mihai Chira <mihai.ch...@gmail.com> Committed: Tue Jul 22 09:18:18 2014 +0100 ---------------------------------------------------------------------- .../tests/unitTests/mx/collections/DataNode.as | 62 ++++ ...rarchicalCollectionViewCursor_Basics_Test.as | 164 ++++++++++ ...hicalCollectionViewCursor_FLEX_34119_Test.as | 325 +++++++++++++++++++ .../HierarchicalCollectionViewTestUtils.as | 172 ++++++++++ 4 files changed, 723 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/0c4c9c00/frameworks/tests/unitTests/mx/collections/DataNode.as ---------------------------------------------------------------------- diff --git a/frameworks/tests/unitTests/mx/collections/DataNode.as b/frameworks/tests/unitTests/mx/collections/DataNode.as new file mode 100644 index 0000000..da4f7fd --- /dev/null +++ b/frameworks/tests/unitTests/mx/collections/DataNode.as @@ -0,0 +1,62 @@ +package { +import mx.collections.ArrayCollection; + +public class DataNode { + private var _label:String; + private var _children:ArrayCollection; + private var _isSelected:Boolean = false; + private var _isPreviousSiblingRemoved:Boolean = false; + + public function DataNode(label:String) + { + _label = label; + } + + public function get children():ArrayCollection { + return _children; + } + + public function set children(value:ArrayCollection):void { + _children = value; + } + + public function get label():String { + return _label + (_isSelected ? " [SEL]" : "") + (_isPreviousSiblingRemoved ? " [PREV ITEM REMOVED]" : ""); + } + + public function toString():String + { + return label; + } + + public function addChild(node:DataNode):void { + if(!_children) + _children = new ArrayCollection(); + + _children.addItem(node); + } + + public function set isSelected(value:Boolean):void { + _isSelected = value; + } + + public function get isSelected():Boolean { + return _isSelected; + } + + public function clone():DataNode + { + var newNode:DataNode = new DataNode(_label); + for each(var childNode:DataNode in children) + { + newNode.addChild(childNode.clone()); + } + + return newNode; + } + + public function set isPreviousSiblingRemoved(value:Boolean):void { + _isPreviousSiblingRemoved = value; + } +} +} http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/0c4c9c00/frameworks/tests/unitTests/mx/collections/HierarchicalCollectionViewCursor_Basics_Test.as ---------------------------------------------------------------------- diff --git a/frameworks/tests/unitTests/mx/collections/HierarchicalCollectionViewCursor_Basics_Test.as b/frameworks/tests/unitTests/mx/collections/HierarchicalCollectionViewCursor_Basics_Test.as new file mode 100644 index 0000000..65e2cb9 --- /dev/null +++ b/frameworks/tests/unitTests/mx/collections/HierarchicalCollectionViewCursor_Basics_Test.as @@ -0,0 +1,164 @@ +package +{ + import flash.events.UncaughtErrorEvent; + + import mx.collections.ArrayCollection; + import mx.collections.CursorBookmark; + import mx.collections.HierarchicalCollectionView; + import mx.collections.HierarchicalCollectionViewCursor; + import mx.core.FlexGlobals; + + import spark.components.WindowedApplication; + + import org.flexunit.asserts.assertEquals; + + public class HierarchicalCollectionViewCursor_Basics_Test + { + private static var _utils:HierarchicalCollectionViewTestUtils = new HierarchicalCollectionViewTestUtils(); + private static var _currentHierarchy:HierarchicalCollectionView; + private static var _noErrorsThrown:Boolean = true; + private var _level0:ArrayCollection; + + private var _sut:HierarchicalCollectionViewCursor; + + [BeforeClass] + public static function setUpBeforeClass():void + { + (FlexGlobals.topLevelApplication as WindowedApplication).loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, handleUncaughtClientError); + } + + [AfterClass] + public static function tearDownAfterClass():void + { + (FlexGlobals.topLevelApplication as WindowedApplication).loaderInfo.uncaughtErrorEvents.removeEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, handleUncaughtClientError); + } + + [Before] + public function setUp():void + { + _currentHierarchy = generateHierarchyViewWithOpenNodes(); + _level0 = _utils.getRoot(_currentHierarchy) as ArrayCollection; + _sut = _currentHierarchy.createCursor() as HierarchicalCollectionViewCursor; + + _noErrorsThrown = true; + } + + [After] + public function tearDown():void + { + _sut = null; + _currentHierarchy = null; + _level0 = null; + } + + + + [Test] + public function testMovingAround():void + { + //given + var lastCompany:DataNode = _level0.getItemAt(_level0.length - 1) as DataNode; + var firstCompany:DataNode = _level0.getItemAt(0) as DataNode; + var firstLocation:DataNode = firstCompany.children.getItemAt(0) as DataNode; + var secondLocation:DataNode = firstCompany.children.getItemAt(1) as DataNode; + var firstDepartment:DataNode = firstLocation.children.getItemAt(0) as DataNode; + var secondDepartment:DataNode = firstLocation.children.getItemAt(1) as DataNode; + + //when + _sut.moveNext(); + + //then + assertEquals(firstLocation, _sut.current); + + //when + _sut.moveNext(); + + //then + assertEquals(firstDepartment, _sut.current); + + //when + _sut.moveNext(); + + //then + assertEquals(secondDepartment, _sut.current); + + //when + _sut.movePrevious(); + + //then + assertEquals(firstDepartment, _sut.current); + + //when + _sut.moveToLast(); + + //then + assertEquals(lastCompany, _sut.current); + + //when + _sut.seek(new CursorBookmark(4)); + + //then + assertEquals(secondLocation, _sut.current); + } + + [Test] + public function testCollectionChangeInRootDoesNotChangeCurrent():void + { + //given + var lastCompany:DataNode = _level0.getItemAt(_level0.length - 1) as DataNode; + + //when + _sut.moveToLast(); + + var newFirstCompany:DataNode = _utils.createSimpleNode("[INS] Company"); + _level0.addItemAt(newFirstCompany, 0); + + var newLastCompany:DataNode = _utils.createSimpleNode("[INS] Company"); + _level0.addItemAt(newLastCompany, _level0.length); + + //then + assertEquals(lastCompany, _sut.current); + + //when + _sut.moveToLast(); + + //then + assertEquals(newLastCompany, _sut.current); + } + + + private static function handleUncaughtClientError(event:UncaughtErrorEvent):void + { + event.preventDefault(); + event.stopImmediatePropagation(); + _noErrorsThrown = false; + + trace("\n" + event.error); + _utils.printHCollectionView(_currentHierarchy); + } + + + private static function generateHierarchyViewWithOpenNodes():HierarchicalCollectionView + { + return _utils.generateOpenHierarchyFromRootList(_utils.generateHierarchySourceFromString(HIERARCHY_STRING)); + } + + private static const HIERARCHY_STRING:String = (<![CDATA[ + Company(1) + Company(1)->Location(1) + Company(1)->Location(1)->Department(1) + Company(1)->Location(1)->Department(2) + Company(1)->Location(2) + Company(1)->Location(2)->Department(1) + Company(1)->Location(2)->Department(2) + Company(1)->Location(2)->Department(3) + Company(1)->Location(3) + Company(2) + Company(2)->Location(1) + Company(2)->Location(2) + Company(2)->Location(2)->Department(1) + Company(2)->Location(3) + Company(3) + ]]>).toString(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/0c4c9c00/frameworks/tests/unitTests/mx/collections/HierarchicalCollectionViewCursor_FLEX_34119_Test.as ---------------------------------------------------------------------- diff --git a/frameworks/tests/unitTests/mx/collections/HierarchicalCollectionViewCursor_FLEX_34119_Test.as b/frameworks/tests/unitTests/mx/collections/HierarchicalCollectionViewCursor_FLEX_34119_Test.as new file mode 100644 index 0000000..6f52139 --- /dev/null +++ b/frameworks/tests/unitTests/mx/collections/HierarchicalCollectionViewCursor_FLEX_34119_Test.as @@ -0,0 +1,325 @@ +package +{ + import flash.events.UncaughtErrorEvent; + + import mx.collections.ArrayCollection; + import mx.collections.CursorBookmark; + import mx.collections.HierarchicalCollectionView; + import mx.collections.HierarchicalCollectionViewCursor; + import mx.collections.IViewCursor; + import mx.core.FlexGlobals; + + import spark.components.WindowedApplication; + + import flexunit.framework.AssertionFailedError; + + import org.flexunit.assertThat; + import org.flexunit.asserts.assertEquals; + import org.flexunit.asserts.assertNotNull; + import org.flexunit.asserts.assertTrue; + import org.flexunit.runners.Parameterized; + + [RunWith("org.flexunit.runners.Parameterized")] + public class HierarchicalCollectionViewCursor_FLEX_34119_Test + { + private static const OP_ADD:int = 0; + private static const OP_REMOVE:int = 1; + private static const OP_SET:int = 2; + private static const OPERATIONS:Array = [OP_ADD, OP_REMOVE, OP_SET]; + private static var _generatedHierarchy:HierarchicalCollectionView; + private static var _utils:HierarchicalCollectionViewTestUtils = new HierarchicalCollectionViewTestUtils(); + public static var positionAndOperation:Array = []; + + { + _generatedHierarchy = _utils.generateOpenHierarchyFromRootList(_utils.generateHierarchySourceFromString(HIERARCHY_STRING)); + NO_ITEMS_IN_HIERARCHY = _generatedHierarchy.length; + + private static var SELECTED_INDEX:int = 0; + private static var OPERATION_LOCATION:int = 0; + private static var OPERATION_INDEX:int = 0; + for(SELECTED_INDEX = 0; SELECTED_INDEX < NO_ITEMS_IN_HIERARCHY; SELECTED_INDEX++) + for(OPERATION_LOCATION = SELECTED_INDEX -1; OPERATION_LOCATION >= 0; OPERATION_LOCATION--) + for(OPERATION_INDEX = 0; OPERATION_INDEX < OPERATIONS.length; OPERATION_INDEX++) + positionAndOperation.push([SELECTED_INDEX, OPERATION_LOCATION, OPERATIONS[OPERATION_INDEX]]); + } + + private static var NO_ITEMS_IN_HIERARCHY:int = NaN; + private static var _noErrorsThrown:Boolean = true; + private static var _operationPerformedInLastStep:Boolean = false; + private static var _currentHierarchy:HierarchicalCollectionView; + private static var _sut:HierarchicalCollectionViewCursor; + private static var _operationCursor:HierarchicalCollectionViewCursor; + private static var _mirrorCursor:IViewCursor; + + private static var foo:Parameterized; + + [BeforeClass] + public static function setUpBeforeClass():void + { + (FlexGlobals.topLevelApplication as WindowedApplication).loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, onUncaughtClientError); + } + + [AfterClass] + public static function tearDownAfterClass():void + { + (FlexGlobals.topLevelApplication as WindowedApplication).loaderInfo.uncaughtErrorEvents.removeEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, onUncaughtClientError); + } + + [Before] + public function setUp():void + { + if(_operationPerformedInLastStep || !_currentHierarchy) + { + _operationPerformedInLastStep = false; + + _currentHierarchy = _utils.clone(_generatedHierarchy); + _utils.openAllNodes(_currentHierarchy); + _sut = _currentHierarchy.createCursor() as HierarchicalCollectionViewCursor; + } + } + + [After] + public function tearDown():void + { + if(_operationPerformedInLastStep) + { + _sut = null; + _currentHierarchy = null; + } + + _operationCursor = null; + _mirrorCursor = null; + } + + [Test] + public function reproduce_FLEX_34119_WithADDSimple():void + { + //given + var _level0:ArrayCollection = _utils.getRoot(_currentHierarchy) as ArrayCollection; + + var secondRegion:DataNode = _level0.getItemAt(1) as DataNode; + var firstCity:DataNode = secondRegion.children.getItemAt(0) as DataNode; + var secondCompany:DataNode = firstCity.children.getItemAt(1) as DataNode; + + //when + _sut.seek(new CursorBookmark(4)); //Region(2)->City(1)->Company(2) + secondRegion.children.addItemAt(_utils.createSimpleNode("City [INS]"), 0); //RTE should be thrown here + + //then + assertEquals(secondCompany, _sut.current); + assertTrue(_noErrorsThrown); + } + + [Test(dataProvider="positionAndOperation")] + public function testReproduce_FLEX_34119_Comprehensive(selectedItemIndex:int, operationIndex:int, operation:int):void + { + assertThat(operationIndex < selectedItemIndex); + + try { + //WHEN + //1. Select a random node + _sut.seek(new CursorBookmark(selectedItemIndex)); + + var selectedNode:DataNode = DataNode(_sut.current); + assertNotNull(selectedNode); + + //2. Make sure FLEX-34119 can be reproduced with the current indexes + if(!isFLEX_34119_Reproducible(operationIndex, selectedNode, operation)) + { + //trace("can't reproduce " + operation + "; " + operationIndex + "; " + selectedNode); + return; + } + + selectedNode.isSelected = true; + + //3. Perform operation + if (operation == OP_ADD) + testAddition(_operationCursor); + else if (operation == OP_REMOVE) + testRemoval(_operationCursor); + + //THEN 1 + assertTrue(_noErrorsThrown); + + + + //4. Create mirror HierarchicalCollectionView from the changed root, as the source of truth + _mirrorCursor = _utils.navigateToItem(_currentHierarchy.createCursor() as HierarchicalCollectionViewCursor, selectedNode); + + //5. Navigate somewhere in both HierarchicalCollectionViews and make sure they do the same thing + _sut.moveNext(); + _mirrorCursor.moveNext(); + + //THEN 2 + assertEquals(_mirrorCursor.current, _sut.current); + } + catch(error:AssertionFailedError) + { + trace("FAIL ("+selectedItemIndex + "," + operationIndex + "," + operation + "): " + error.message); + _utils.printHCollectionView(_currentHierarchy); + throw(error); + } + } + + private function isFLEX_34119_Reproducible(where:int, selectedNode:DataNode, operation:int):Boolean + { + var hasParent:Boolean = _currentHierarchy.getParentItem(selectedNode) != null; + if(!hasParent) + return false; + + _operationCursor = _currentHierarchy.createCursor() as HierarchicalCollectionViewCursor; + _operationCursor.seek(new CursorBookmark(where)); + var itemToPerformOperationOn:DataNode = _operationCursor.current as DataNode; + + switch(operation) + { + case OP_ADD: + return _utils.nodesHaveCommonAncestor(itemToPerformOperationOn, selectedNode, _currentHierarchy); + case OP_REMOVE: + return !_utils.isAncestor(itemToPerformOperationOn, selectedNode, _currentHierarchy); + case OP_SET: + return false; + default: + return false; + } + } + + private function testRemoval(where:HierarchicalCollectionViewCursor):void + { + var itemToDelete:DataNode = where.current as DataNode; + assertNotNull(itemToDelete); + + //mark the next item, so we know which item disappeared + where.moveNext(); + var nextItem:DataNode = where.current as DataNode; + if (nextItem) + nextItem.isPreviousSiblingRemoved = true; + + //remove the item + var parentOfItemToRemove:DataNode = _currentHierarchy.getParentItem(itemToDelete) as DataNode; + var collectionToChange:ArrayCollection = parentOfItemToRemove ? parentOfItemToRemove.children : _utils.getRoot(_currentHierarchy) as ArrayCollection; + //trace("REM: sel=" + selectedNode + "; before=" + itemToDelete); + _operationPerformedInLastStep = true; + collectionToChange.removeItem(itemToDelete); + } + + + /** + * @return true when the where parameter designates an item on the root collection (last ancestor of modified node) + */ + private function testAddition(where:HierarchicalCollectionViewCursor):void + { + var itemBeforeWhichWereAdding:DataNode = where.current as DataNode; + assertNotNull(itemBeforeWhichWereAdding); + + var parentOfAdditionLocation:DataNode = _currentHierarchy.getParentItem(itemBeforeWhichWereAdding) as DataNode; + var collectionToChange:ArrayCollection = parentOfAdditionLocation ? parentOfAdditionLocation.children : _utils.getRoot(_currentHierarchy) as ArrayCollection; + var positionOfItemBeforeWhichWereAdding:int = collectionToChange.getItemIndex(itemBeforeWhichWereAdding); + + _operationPerformedInLastStep = true; + collectionToChange.addItemAt(_utils.createSimpleNode(itemBeforeWhichWereAdding.label + " [INSERTED NODE]"), positionOfItemBeforeWhichWereAdding); + //trace("ADD: sel=" + selectedNode + "; before=" + itemBeforeWhichWereAdding); + } + + + + + + private static function onUncaughtClientError(event:UncaughtErrorEvent):void + { + event.preventDefault(); + event.stopImmediatePropagation(); + _noErrorsThrown = false; + + trace("\n" + event.error); + _utils.printHCollectionView(_currentHierarchy); + } + + + private static const HIERARCHY_STRING:String = (<![CDATA[ + Region(1) + Region(2) + Region(2)->City(1) + Region(2)->City(1)->Company(1) + Region(2)->City(1)->Company(2) + Region(2)->City(1)->Company(2)->Department(1) + Region(2)->City(1)->Company(2)->Department(1)->Employee(1) + Region(2)->City(1)->Company(2)->Department(1)->Employee(2) + Region(2)->City(1)->Company(2)->Department(2) + Region(2)->City(1)->Company(2)->Department(2)->Employee(1) + Region(2)->City(1)->Company(2)->Department(2)->Employee(2) + Region(2)->City(1)->Company(2)->Department(2)->Employee(3) + Region(2)->City(1)->Company(2)->Department(3) + Region(2)->City(1)->Company(2)->Department(3)->Employee(1) + Region(2)->City(1)->Company(2)->Department(3)->Employee(2) + Region(2)->City(1)->Company(2)->Department(3)->Employee(3) + Region(2)->City(1)->Company(2)->Department(3)->Employee(4) + Region(2)->City(1)->Company(3) + Region(2)->City(1)->Company(3)->Department(1) + Region(2)->City(1)->Company(3)->Department(1)->Employee(1) + Region(2)->City(1)->Company(3)->Department(2) + Region(2)->City(1)->Company(3)->Department(2)->Employee(1) + Region(2)->City(1)->Company(3)->Department(2)->Employee(2) + Region(2)->City(1)->Company(3)->Department(3) + Region(2)->City(1)->Company(3)->Department(3)->Employee(1) + Region(2)->City(1)->Company(3)->Department(3)->Employee(2) + Region(2)->City(1)->Company(3)->Department(3)->Employee(3) + Region(2)->City(1)->Company(3)->Department(3)->Employee(4) + Region(2)->City(1)->Company(3)->Department(3)->Employee(5) + Region(2)->City(1)->Company(3)->Department(4) + Region(2)->City(1)->Company(3)->Department(4)->Employee(1) + Region(2)->City(1)->Company(3)->Department(4)->Employee(2) + Region(2)->City(1)->Company(3)->Department(4)->Employee(3) + Region(2)->City(1)->Company(3)->Department(4)->Employee(4) + Region(2)->City(1)->Company(4) + Region(2)->City(1)->Company(4)->Department(1) + Region(2)->City(1)->Company(4)->Department(1)->Employee(1) + Region(2)->City(1)->Company(4)->Department(1)->Employee(2) + Region(2)->City(1)->Company(4)->Department(1)->Employee(3) + Region(3) + Region(3)->City(1) + Region(3)->City(1)->Company(1) + Region(3)->City(1)->Company(1)->Department(1) + Region(3)->City(1)->Company(1)->Department(1)->Employee(1) + Region(3)->City(1)->Company(1)->Department(1)->Employee(2) + Region(3)->City(1)->Company(1)->Department(1)->Employee(3) + Region(3)->City(1)->Company(1)->Department(1)->Employee(4) + Region(3)->City(1)->Company(1)->Department(2) + Region(3)->City(1)->Company(1)->Department(2)->Employee(1) + Region(3)->City(1)->Company(1)->Department(2)->Employee(2) + Region(3)->City(1)->Company(1)->Department(2)->Employee(3) + Region(3)->City(1)->Company(1)->Department(3) + Region(3)->City(1)->Company(1)->Department(3)->Employee(1) + Region(3)->City(1)->Company(1)->Department(3)->Employee(2) + Region(3)->City(1)->Company(1)->Department(3)->Employee(3) + Region(3)->City(1)->Company(2) + Region(3)->City(1)->Company(2)->Department(1) + Region(3)->City(1)->Company(2)->Department(1)->Employee(1) + Region(3)->City(1)->Company(2)->Department(2) + Region(3)->City(1)->Company(2)->Department(2)->Employee(1) + Region(3)->City(1)->Company(2)->Department(2)->Employee(2) + Region(3)->City(1)->Company(2)->Department(3) + Region(3)->City(1)->Company(2)->Department(4) + Region(3)->City(1)->Company(3) + Region(3)->City(1)->Company(4) + Region(3)->City(1)->Company(4)->Department(1) + Region(3)->City(1)->Company(4)->Department(1)->Employee(1) + Region(3)->City(1)->Company(4)->Department(1)->Employee(2) + Region(3)->City(1)->Company(4)->Department(1)->Employee(3) + Region(3)->City(1)->Company(4)->Department(1)->Employee(4) + Region(3)->City(1)->Company(4)->Department(2) + Region(3)->City(1)->Company(4)->Department(2)->Employee(1) + Region(3)->City(1)->Company(4)->Department(2)->Employee(2) + Region(3)->City(1)->Company(4)->Department(2)->Employee(3) + Region(3)->City(1)->Company(4)->Department(3) + Region(3)->City(1)->Company(5) + Region(3)->City(2) + Region(3)->City(3) + Region(3)->City(4) + Region(3)->City(4)->Company(1) + Region(4) + Region(4)->City(1) + Region(4)->City(1)->Company(1) + ]]>).toString(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/0c4c9c00/frameworks/tests/unitTests/mx/collections/HierarchicalCollectionViewTestUtils.as ---------------------------------------------------------------------- diff --git a/frameworks/tests/unitTests/mx/collections/HierarchicalCollectionViewTestUtils.as b/frameworks/tests/unitTests/mx/collections/HierarchicalCollectionViewTestUtils.as new file mode 100644 index 0000000..82d00c7 --- /dev/null +++ b/frameworks/tests/unitTests/mx/collections/HierarchicalCollectionViewTestUtils.as @@ -0,0 +1,172 @@ +package +{ + import com.adobe.utils.StringUtil; + + import mx.collections.ArrayCollection; + import mx.collections.HierarchicalCollectionView; + import mx.collections.HierarchicalCollectionViewCursor; + import mx.collections.HierarchicalData; + import mx.collections.IViewCursor; + + public class HierarchicalCollectionViewTestUtils + { + //assumes the root is an ArrayCollection of DataNodes + public function clone(hcv:HierarchicalCollectionView):HierarchicalCollectionView + { + var oldRoot:ArrayCollection = ArrayCollection(getRoot(hcv)); + var newRoot:ArrayCollection = new ArrayCollection(); + + for each(var rootNode:DataNode in oldRoot) + { + newRoot.addItem(rootNode.clone()); + } + + return generateHCV(newRoot); + } + + public function createNodes(level:String, no:int):ArrayCollection + { + var nodes:ArrayCollection = new ArrayCollection(); + for(var i:int = 0; i < no; i++) + { + nodes.addItem(createSimpleNode(level)); + } + + return nodes; + } + + public function generateOpenHierarchyFromRootList(root:ArrayCollection):HierarchicalCollectionView + { + var hcv:HierarchicalCollectionView = generateHCV(root); + openAllNodes(hcv); + return hcv; + } + + public function generateHCV(rootCollection:ArrayCollection):HierarchicalCollectionView + { + return new HierarchicalCollectionView(new HierarchicalData(rootCollection)); + } + + public function openAllNodes(hcv:HierarchicalCollectionView):void + { + var cursor:HierarchicalCollectionViewCursor = hcv.createCursor() as HierarchicalCollectionViewCursor; + while(!cursor.afterLast) + { + hcv.openNode(cursor.current); + cursor.moveNext(); + } + } + + public function getRoot(hcv:HierarchicalCollectionView):Object + { + return hcv.source.getRoot(); + } + + public function printHCollectionView(hcv:HierarchicalCollectionView):void + { + trace(""); + var cursor:HierarchicalCollectionViewCursor = hcv.createCursor() as HierarchicalCollectionViewCursor; + while(!cursor.afterLast) + { + trace(DataNode(cursor.current).label); + cursor.moveNext(); + } + } + + public function createSimpleNode(label:String):DataNode + { + return new DataNode(label); + } + + public function isAncestor(node:DataNode, forNode:DataNode, hcv:HierarchicalCollectionView):Boolean + { + do + { + forNode = hcv.getParentItem(forNode) as DataNode; + } while(forNode && forNode != node) + + return forNode == node; + } + + public function nodesHaveCommonAncestor(node:DataNode, withNode:DataNode, hcv:HierarchicalCollectionView):Boolean + { + var nodeAndAncestors:Array = [node].concat(getNodeAncestors(node, hcv)); + var otherNodeAndAncestors:Array = [withNode].concat(getNodeAncestors(withNode, hcv)); + for each(var ancestor:DataNode in nodeAndAncestors) + if(otherNodeAndAncestors.indexOf(ancestor) != -1) + return true; + + return false; + } + + public function getNodeAncestors(node:DataNode, hcv:HierarchicalCollectionView):Array + { + var nodeParents:Array = []; + + // Make a list of parents of the node. + var parent:Object = hcv.getParentItem(node); + while (parent) + { + nodeParents.push(parent); + parent = hcv.getParentItem(parent); + } + + return nodeParents; + } + + public function navigateToItem(cursor:IViewCursor, item:DataNode):IViewCursor + { + while(!cursor.afterLast && cursor.current != item) + { + cursor.moveNext(); + } + + return cursor; + } + + public function generateHierarchySourceFromString(source:String):ArrayCollection + { + var rootCollection:ArrayCollection = new ArrayCollection(); + var alreadyCreatedNodes:Array = []; + var node:DataNode; + + var lines:Array = source.split("\n"); + for each(var line:String in lines) + { + if(!line) + continue; + + var currentLabel:String = ""; + var previousNode:DataNode = null; + var nodesOnThisLine:Array = StringUtil.trim(line).split("->"); + for each(var nodeName:String in nodesOnThisLine) + { + if(!nodeName) + continue; + + currentLabel += currentLabel ? "->" + nodeName : nodeName; + + var nodeAlreadyCreated:Boolean = alreadyCreatedNodes[currentLabel] != undefined; + + if(nodeAlreadyCreated) + node = alreadyCreatedNodes[currentLabel]; + else { + node = createSimpleNode(currentLabel); + alreadyCreatedNodes[currentLabel] = node; + } + + if(!nodeAlreadyCreated) { + if (previousNode) + previousNode.addChild(node); + else + rootCollection.addItem(node); + } + + previousNode = node; + } + } + + return rootCollection; + } + } +} \ No newline at end of file