FLEX-35031 FLEX-33058
CAUSE:
-FLEX-33058: HierarchicalCollectionViewCursor assumed, in findAny() and 
findLast(), that all the items in the collection will have all the properties 
that the parameter does. That seems like a safe assumption to make when 
findAny() is called with current as the parameter (as it is in 
collectionChangeHandler()), but even that isn't always true, as the example in 
the ticket demonstrates: when the HierarchicalCollectionViewCursor works on a 
HierarchicalCollectionView with a GroupingCollection2 as its source, some of 
the objects it will traverse will be the anonymous objects created by 
GroupingCollection2 to represent the groups (which have the "mx_internal_uid" 
property seen in the RTE, and also "children" and "GroupLabel"), and others 
will be the grouped items, which are often sealed class instances which we 
can't expect to have those properties.

-FLEX-35031: The (other) assumption in findAny() and findLast() was that the 
parameter was NOT a sealed class instance. Its properties were being inspected 
using a regular for(var i:String in values) loop, which only yields anything if 
values's properties are enumerable, i.e. are defined on a dynamic object. So 
when 'values' was a sealed class instance (as often happens in 
collectionChangeHandler(), where the parameter is current, which is often a 
sealed class instance), the algorithm incorrectly assumed that there was no 
difference between 'values' and 'current', because 'values' seemed to have no 
properties at all.

SOLUTION:
Now we assume we have a match if and only if the objects are the same, or if 
the parameter is dynamic and 'current' shares all its properties and values 
(even if it contains other properties and values, it's still a match).

NOTE:
-the solution implies, as documented in the unit tests, that findAny({}) and 
findLast({}) will return the first and, respectively, the last item in the 
collection. This seems to be how it should work.
-we're now using non-strict equality checks, which implies, again as documented 
in the unit tests, that searching by the string equivalent of a number property 
will still work.


Project: http://git-wip-us.apache.org/repos/asf/flex-sdk/repo
Commit: http://git-wip-us.apache.org/repos/asf/flex-sdk/commit/94d038a6
Tree: http://git-wip-us.apache.org/repos/asf/flex-sdk/tree/94d038a6
Diff: http://git-wip-us.apache.org/repos/asf/flex-sdk/diff/94d038a6

Branch: refs/heads/develop
Commit: 94d038a617c889f83b72e1510692e1d9c57d1d9b
Parents: 7a0e9eb
Author: Mihai Chira <[email protected]>
Authored: Wed Feb 17 15:06:17 2016 +0100
Committer: Mihai Chira <[email protected]>
Committed: Wed Feb 17 15:06:17 2016 +0100

----------------------------------------------------------------------
 .../HierarchicalCollectionViewCursor.as         | 58 +++++++++---------
 ...rchicalCollectionViewCursor_FindAny_Tests.as | 64 ++++++++++++++++++++
 2 files changed, 94 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/94d038a6/frameworks/projects/advancedgrids/src/mx/collections/HierarchicalCollectionViewCursor.as
----------------------------------------------------------------------
diff --git 
a/frameworks/projects/advancedgrids/src/mx/collections/HierarchicalCollectionViewCursor.as
 
b/frameworks/projects/advancedgrids/src/mx/collections/HierarchicalCollectionViewCursor.as
index bb5a19b..a55db72 100644
--- 
a/frameworks/projects/advancedgrids/src/mx/collections/HierarchicalCollectionViewCursor.as
+++ 
b/frameworks/projects/advancedgrids/src/mx/collections/HierarchicalCollectionViewCursor.as
@@ -21,7 +21,6 @@ package mx.collections
 {
 
 import flash.events.EventDispatcher;
-import flash.utils.Dictionary;
 
 import mx.collections.errors.ChildItemPendingError;
 import mx.collections.errors.ItemPendingError;
@@ -29,6 +28,7 @@ import mx.core.mx_internal;
 import mx.events.CollectionEvent;
 import mx.events.CollectionEventKind;
 import mx.events.FlexEvent;
+import mx.utils.ObjectUtil;
 import mx.utils.UIDUtil;
 
 use namespace mx_internal;
@@ -324,19 +324,7 @@ public class HierarchicalCollectionViewCursor extends 
EventDispatcher
         var done:Boolean = false;
         while (!done)
         {
-            var currentData:Object = hierarchicalData.getData(current);
-            
-            var matches:Boolean = true;
-            for (var property:String in valuesToMatch)
-            {
-                if (valuesToMatch[property] != currentData[property])
-                {
-                    matches = false;
-                    break;
-                }
-            }
-
-            if (matches)
+            if (objectMatchesValues(hierarchicalData.getData(current), 
valuesToMatch))
                 return true;
 
             done = !moveNext();
@@ -345,6 +333,32 @@ public class HierarchicalCollectionViewCursor extends 
EventDispatcher
         return false;
     }
 
+    private static function objectMatchesValues(object:Object, 
values:Object):Boolean
+    {
+        if(!object && !values)
+            return true;
+
+        if(!object || !values)
+            return false;
+
+        if(object === values)
+            return true;
+
+        var enumerableProperties:Array = 
ObjectUtil.getEnumerableProperties(values);
+        var matches:Boolean = enumerableProperties.length > 0 || 
ObjectUtil.isDynamicObject(values);
+
+        for each(var property:String in enumerableProperties)
+        {
+            if (!object.hasOwnProperty(property) || object[property] != 
values[property])
+            {
+                matches = false;
+                break;
+            }
+        }
+
+        return matches;
+    }
+
     /**
      *  @inheritDoc
      *  
@@ -379,19 +393,7 @@ public class HierarchicalCollectionViewCursor extends 
EventDispatcher
         var done:Boolean = false;
         while (!done)
         {
-            var currentData:Object = current;
-            
-            var matches:Boolean = true;
-            for (var property:String in valuesToMatch)
-            {
-                if (currentData[property] != valuesToMatch[property])
-                {
-                    matches = false;
-                    break;
-                }
-            }
-            
-            if (matches)
+            if (objectMatchesValues(hierarchicalData.getData(current), 
valuesToMatch))
                 return true;
 
             done = !movePrevious();
@@ -412,7 +414,7 @@ public class HierarchicalCollectionViewCursor extends 
EventDispatcher
     public function moveNext():Boolean 
     {
         var currentNode:Object = current;
-        //if there is no currentNode then we cant move forward and must be off 
the ends
+        //if there is no currentNode then we can't move forward and must be 
off the ends
         if (currentNode == null) 
             return false; 
         

http://git-wip-us.apache.org/repos/asf/flex-sdk/blob/94d038a6/frameworks/projects/advancedgrids/tests/mx/collections/HierarchicalCollectionViewCursor_FindAny_Tests.as
----------------------------------------------------------------------
diff --git 
a/frameworks/projects/advancedgrids/tests/mx/collections/HierarchicalCollectionViewCursor_FindAny_Tests.as
 
b/frameworks/projects/advancedgrids/tests/mx/collections/HierarchicalCollectionViewCursor_FindAny_Tests.as
index 33c1269..230fa91 100644
--- 
a/frameworks/projects/advancedgrids/tests/mx/collections/HierarchicalCollectionViewCursor_FindAny_Tests.as
+++ 
b/frameworks/projects/advancedgrids/tests/mx/collections/HierarchicalCollectionViewCursor_FindAny_Tests.as
@@ -242,6 +242,34 @@ package mx.collections {
         }
 
         [Test]
+        public function 
test_findAny_finds_the_first_item_when_its_argument_is_an_empty_anonymous_object():void
+        {
+            //given
+            _utils.openAllNodes(_collectionView);
+
+            //when
+            var found:Boolean = _sut.findAny({});
+
+            //then
+            assertTrue(found);
+            assertEquals(_level0.getItemAt(0), _sut.current);
+        }
+
+        [Test]
+        public function 
test_findLast_finds_the_last_item_when_its_argument_is_an_empty_anonymous_object():void
+        {
+            //given
+            _utils.openAllNodes(_collectionView);
+
+            //when
+            var found:Boolean = _sut.findLast({});
+
+            //then
+            assertTrue(found);
+            assertEquals(_employeesByID[_employeesByID.length - 1], 
_sut.current);
+        }
+
+        [Test]
         public function 
test_findAny_finds_sealed_class_instance_via_anonymous_object_with_subset_of_properties():void
         {
             //given
@@ -260,6 +288,24 @@ package mx.collections {
         }
 
         [Test]
+        public function 
test_findAny_finds_sealed_class_instance_via_anonymous_object_with_subset_of_properties_and_string_values_instead_of_int():void
+        {
+            //given
+            const ID_TO_FIND:int = NO_EMPLOYEES_PER_DEPARTMENT;
+            _utils.openAllNodes(_collectionView);
+
+            //when
+            var found:Boolean = _sut.findAny({department:DEPARTMENT_SALES, 
uniqueID:ID_TO_FIND.toString()});
+
+            //then
+            assertTrue(found);
+            var currentEmployee:EmployeeVO = _sut.current as EmployeeVO;
+            assertNotNull(currentEmployee);
+            assertEquals(DEPARTMENT_SALES, currentEmployee.department);
+            assertEquals(ID_TO_FIND, currentEmployee.uniqueID);
+        }
+
+        [Test]
         public function 
test_findLast_finds_sealed_class_instance_via_anonymous_object_with_subset_of_properties():void
         {
             //given
@@ -278,6 +324,24 @@ package mx.collections {
         }
 
         [Test]
+        public function 
test_findLast_finds_sealed_class_instance_via_anonymous_object_with_subset_of_properties_and_string_values_instead_of_int():void
+        {
+            //given
+            const ID_TO_FIND:int = 0;
+            _utils.openAllNodes(_collectionView);
+
+            //when
+            var found:Boolean = 
_sut.findLast({department:DEPARTMENT_DEVELOPMENT, 
uniqueID:ID_TO_FIND.toString()});
+
+            //then
+            assertTrue(found);
+            var currentEmployee:EmployeeVO = _sut.current as EmployeeVO;
+            assertNotNull(currentEmployee);
+            assertEquals(DEPARTMENT_DEVELOPMENT, currentEmployee.department);
+            assertEquals(ID_TO_FIND, currentEmployee.uniqueID);
+        }
+
+        [Test]
         public function 
test_findAny_finds_sealed_class_instance_via_dynamic_class_instance_with_subset_of_properties():void
         {
             //given

Reply via email to