You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flex.apache.org by mi...@apache.org on 2014/08/11 13:41:44 UTC
[02/19] git commit: [flex-sdk] [refs/heads/develop] - 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 defi
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/develop
Commit: 0c4c9c00d09eab81e6c43340df245f4c9ca239b0
Parents: 0a2c3bd
Author: Mihai Chira <mi...@gmail.com>
Authored: Tue Jul 22 09:18:18 2014 +0100
Committer: Mihai Chira <mi...@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