Merge remote-tracking branch 'origin/develop' into develop
diff --git a/frameworks/projects/framework/src/mx/managers/LayoutManager.as b/frameworks/projects/framework/src/mx/managers/LayoutManager.as
index feebeff..d070203 100644
--- a/frameworks/projects/framework/src/mx/managers/LayoutManager.as
+++ b/frameworks/projects/framework/src/mx/managers/LayoutManager.as
@@ -846,11 +846,14 @@
 			var obj:ILayoutManagerClient = ILayoutManagerClient(updateCompleteQueue.removeLargest());
             while (obj)
             {
-                if (!obj.initialized && obj.processedDescriptors)
-                    obj.initialized = true;
-                if (obj.hasEventListener(FlexEvent.UPDATE_COMPLETE))
-                    obj.dispatchEvent(new FlexEvent(FlexEvent.UPDATE_COMPLETE));
-                obj.updateCompletePendingFlag = false;
+                if(obj.nestLevel)
+                {
+                    if (!obj.initialized && obj.processedDescriptors)
+                        obj.initialized = true;
+                    if (obj.hasEventListener(FlexEvent.UPDATE_COMPLETE))
+                        obj.dispatchEvent(new FlexEvent(FlexEvent.UPDATE_COMPLETE));
+                    obj.updateCompletePendingFlag = false;
+                }
                 obj = ILayoutManagerClient(updateCompleteQueue.removeLargest());
             }
 
@@ -1094,12 +1097,15 @@
                 obj = ILayoutManagerClient(updateCompleteQueue.removeLargestChild(target));
                 while (obj)
                 {
-                    if (!obj.initialized)
-                        obj.initialized = true;
-                    
-                    if (obj.hasEventListener(FlexEvent.UPDATE_COMPLETE))
-                        obj.dispatchEvent(new FlexEvent(FlexEvent.UPDATE_COMPLETE));
-                    obj.updateCompletePendingFlag = false;
+                    if(obj.nestLevel)
+                    {
+                        if (!obj.initialized)
+                            obj.initialized = true;
+
+                        if (obj.hasEventListener(FlexEvent.UPDATE_COMPLETE))
+                            obj.dispatchEvent(new FlexEvent(FlexEvent.UPDATE_COMPLETE));
+                        obj.updateCompletePendingFlag = false;
+                    }
                     obj = ILayoutManagerClient(updateCompleteQueue.removeLargestChild(target));
                 }
             }
diff --git a/frameworks/projects/framework/tests/mx/managers/LayoutManager_FLEX_35321_Tests.as b/frameworks/projects/framework/tests/mx/managers/LayoutManager_FLEX_35321_Tests.as
index 47d5f14..9b0b38c 100644
--- a/frameworks/projects/framework/tests/mx/managers/LayoutManager_FLEX_35321_Tests.as
+++ b/frameworks/projects/framework/tests/mx/managers/LayoutManager_FLEX_35321_Tests.as
@@ -6,7 +6,10 @@
     import mx.core.mx_internal;
     import mx.events.FlexEvent;
 
-    import org.flexunit.asserts.assertFalse;
+    import org.flexunit.assertThat;
+
+    import org.flexunit.asserts.assertEquals;
+    import org.flexunit.asserts.assertNotNull;
     import org.flexunit.asserts.assertNull;
     import org.flexunit.async.Async;
     import org.fluint.uiImpersonation.UIImpersonator;
@@ -18,63 +21,177 @@
         private static var noEnterFramesRemaining:int = NaN;
         private static const _finishNotifier:EventDispatcher = new EventDispatcher();
 
-        private var _objectWhichIsRemovedOnSizeValidation:SomeComponent;
-        private var _creationCompleteCalled:Boolean;
+        private var _objectWhichIsRemovedAtValidation:SomeComponent;
+        private var _creationCompleteCalls:int;
 
         [Before]
         public function setUp():void
         {
-            _creationCompleteCalled = false;
-            _objectWhichIsRemovedOnSizeValidation = new SomeComponent();
-            UIImpersonator.addChild(_objectWhichIsRemovedOnSizeValidation);
+            _creationCompleteCalls = 0;
+            _objectWhichIsRemovedAtValidation = new SomeComponent();
+            _objectWhichIsRemovedAtValidation.addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
+            UIImpersonator.addElement(_objectWhichIsRemovedAtValidation);
         }
 
         [After]
         public function tearDown():void
         {
             UIImpersonator.removeAllChildren();
-            _objectWhichIsRemovedOnSizeValidation = null;
+            _objectWhichIsRemovedAtValidation = null;
         }
 
+
+        //--------------------------------------------------------------------------
+        //
+        //  Test method
+        //
+        //--------------------------------------------------------------------------
+
         [Test]
         public function test_object_removed_from_stage_via_code_is_not_initialized():void
         {
             //given
             UIComponentGlobals.mx_internal::layoutManager.usePhasedInstantiation = false;
-            _objectWhichIsRemovedOnSizeValidation.addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
+            _objectWhichIsRemovedAtValidation.removeFromStageOnValidateProperties = true;
 
             //when
-            _objectWhichIsRemovedOnSizeValidation.validateNow();
+            _objectWhichIsRemovedAtValidation.invalidateProperties();
+            _objectWhichIsRemovedAtValidation.invalidateSize();
+            _objectWhichIsRemovedAtValidation.invalidateDisplayList();
+            _objectWhichIsRemovedAtValidation.validateNow();
 
             //then
-            assertNull("The object was actually not removed from stage. Huh?", _objectWhichIsRemovedOnSizeValidation.parent);
-            assertFalse("Yep, this is the bug. Why call initialized=true on an object that's not on stage?", _creationCompleteCalled);
+            then_assert_not_initialized();
+            assert_validation_count(1, 0, 0);
         }
 
+
+
+        //--------------------------------------------------------------------------
+        //
+        //  Test method
+        //
+        //--------------------------------------------------------------------------
+
         [Test(async, timeout=500)]
         public function test_object_removed_from_stage_via_user_action_is_not_initialized():void
         {
             //given
             UIComponentGlobals.mx_internal::layoutManager.usePhasedInstantiation = true;
-            _objectWhichIsRemovedOnSizeValidation.addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
+            _objectWhichIsRemovedAtValidation.removeFromStageOnValidateProperties = false;
 
             //when
-            _objectWhichIsRemovedOnSizeValidation.validateNow();
-            _objectWhichIsRemovedOnSizeValidation.pretendUserAskedForComponentRemoval();
+            _objectWhichIsRemovedAtValidation.invalidateDisplayList();
+            _objectWhichIsRemovedAtValidation.invalidateProperties();
+            _objectWhichIsRemovedAtValidation.invalidateSize();
 
-            //then wait 2 frames
-            noEnterFramesRemaining = 2;
+            //then wait 1 frame
+            noEnterFramesRemaining = 1;
             UIImpersonator.testDisplay.addEventListener(Event.ENTER_FRAME, onEnterFrame);
-            Async.handleEvent(this, _finishNotifier, Event.COMPLETE, then_assert, 300);
+            Async.handleEvent(this, _finishNotifier, Event.COMPLETE, then_remove_from_stage_via_callLater, 300, {nextStep:then_assert_not_initialized_but_partially_validated, afterNumFrames:3});
         }
 
-        private function then_assert(event:Event, passThroughData:Object):void
+
+
+
+        //--------------------------------------------------------------------------
+        //
+        //  Test method
+        //
+        //--------------------------------------------------------------------------
+
+        [Test(async, timeout=750)]
+        public function test_object_removed_from_stage_then_readded_is_initialized_once():void
+        {
+            //given
+            UIComponentGlobals.mx_internal::layoutManager.usePhasedInstantiation = true;
+            _objectWhichIsRemovedAtValidation.removeFromStageOnValidateProperties = false;
+
+            //when
+            _objectWhichIsRemovedAtValidation.invalidateDisplayList();
+            _objectWhichIsRemovedAtValidation.invalidateProperties();
+            _objectWhichIsRemovedAtValidation.invalidateSize();
+
+            //then wait 1 frame
+            noEnterFramesRemaining = 1;
+            UIImpersonator.testDisplay.addEventListener(Event.ENTER_FRAME, onEnterFrame);
+            Async.handleEvent(this, _finishNotifier, Event.COMPLETE, then_remove_from_stage_via_callLater, 300, {nextStep:then_readd_object, afterNumFrames:1});
+        }
+
+        private function then_readd_object(event:Event, passThroughData:Object):void
         {
             //then
-            assertNull("The object was actually not removed from stage. Huh?", _objectWhichIsRemovedOnSizeValidation.parent);
-            assertFalse("Yep, this is the bug. Why call initialized=true on an object that's not on stage?", _creationCompleteCalled);
+            assertNull("The object was actually not removed from stage. Huh?", _objectWhichIsRemovedAtValidation.parent);
+
+            //when
+            UIImpersonator.addElement(_objectWhichIsRemovedAtValidation);
+
+            //then wait 3 frames, to make sure validation is done
+            noEnterFramesRemaining = 3;
+            UIImpersonator.testDisplay.addEventListener(Event.ENTER_FRAME, onEnterFrame);
+            Async.handleEvent(this, _finishNotifier, Event.COMPLETE, then_assert_one_initialization_only, 400);
         }
 
+        private function then_assert_one_initialization_only(event:Event, passThroughData:Object):void
+        {
+            //then
+            assertNotNull("The object should be on stage...", _objectWhichIsRemovedAtValidation.parent);
+            assertThat("If it's on stage, the nestLevel should be positive", _objectWhichIsRemovedAtValidation.nestLevel > 0);
+            assertEquals("When validation is interrupted half-way it should be complete once the object is re-added to stage", 1, _creationCompleteCalls);
+            assert_validation_count(2, 2, 1);
+        }
+
+
+        //--------------------------------------------------------------------------
+        //
+        //  Shared test methods
+        //
+        //--------------------------------------------------------------------------
+
+        private function then_remove_from_stage_via_callLater(event:Event, passThroughData:Object):void
+        {
+            //then
+            assertEquals("The first validation step should have completed by now", 1, _objectWhichIsRemovedAtValidation.numValidatePropertiesCalls);
+            assertEquals("But not validateSize()", 0, _objectWhichIsRemovedAtValidation.numValidateSizeCalls);
+            assertEquals("Nor validateDisplayList()", 0, _objectWhichIsRemovedAtValidation.numValidateDisplayListCalls);
+
+            //given
+            const whereToGoNext:Function = passThroughData.nextStep as Function;
+            const afterHowManyFrames:int = passThroughData.afterNumFrames as int;
+
+            //when
+            _objectWhichIsRemovedAtValidation.pretendUserAskedForComponentRemovalInNextFrame();
+
+            //then wait
+            noEnterFramesRemaining = afterHowManyFrames;
+            UIImpersonator.testDisplay.addEventListener(Event.ENTER_FRAME, onEnterFrame);
+            Async.handleEvent(this, _finishNotifier, Event.COMPLETE, whereToGoNext, 300);
+        }
+
+        private function then_assert_not_initialized(event:Event = null, passThroughData:Object = null):void
+        {
+            //then
+            assertNull("The object was actually not removed from stage. Huh?", _objectWhichIsRemovedAtValidation.parent);
+            assertEquals("Yep, this is the bug. Why call initialized=true on an object that's not on stage?", 0, _creationCompleteCalls);
+        }
+
+        private function then_assert_not_initialized_but_partially_validated(event:Event = null, passThroughData:Object = null):void
+        {
+            //then
+            then_assert_not_initialized(event, passThroughData);
+            assert_validation_count(1, 1, 0);
+        }
+
+        private function assert_validation_count(numPropertiesValidations:int = 1, numSizeValidations:int = 1, numDisplayListValidations:int = 1):void
+        {
+            //then
+            assertEquals("Properties should have been validated", numPropertiesValidations, _objectWhichIsRemovedAtValidation.numValidatePropertiesCalls);
+            assertEquals("Size should have been validated", numSizeValidations, _objectWhichIsRemovedAtValidation.numValidateSizeCalls);
+            assertEquals("Display list should have been validated", numDisplayListValidations, _objectWhichIsRemovedAtValidation.numValidateDisplayListCalls);
+        }
+
+
+
         private static function onEnterFrame(event:Event):void
         {
             if(!--noEnterFramesRemaining)
@@ -86,25 +203,39 @@
 
         private function onCreationComplete(event:FlexEvent):void
         {
-            _creationCompleteCalled = true;
+            _creationCompleteCalls++;
         }
     }
 }
 
-import flash.events.TimerEvent;
-import flash.utils.Timer;
-
 import mx.core.IVisualElementContainer;
 import mx.core.UIComponent;
 
 class SomeComponent extends UIComponent
 {
-    private var _timer:Timer = new Timer(1, 1);
+    private var _removeFromStageOnValidateProperties:Boolean = false;
+    private var _numValidatePropertiesCalls:int = 0;
+    private var _numValidateSizeCalls:int = 0;
+    private var _numValidateDisplayListCalls:int = 0;
+
+    override public function validateProperties():void
+    {
+        super.validateProperties();
+        _numValidatePropertiesCalls++;
+        if(_removeFromStageOnValidateProperties)
+            removeFromStage();
+    }
 
     override public function validateSize(recursive:Boolean = false):void
     {
         super.validateSize(recursive);
-        removeFromStage();
+        _numValidateSizeCalls++;
+    }
+
+    override public function validateDisplayList():void
+    {
+        super.validateDisplayList();
+        _numValidateDisplayListCalls++;
     }
 
     private function removeFromStage():void
@@ -118,8 +249,28 @@
         }
     }
 
-    public function pretendUserAskedForComponentRemoval():void
+    public function pretendUserAskedForComponentRemovalInNextFrame():void
     {
         callLater(removeFromStage);
     }
+
+    public function set removeFromStageOnValidateProperties(value:Boolean):void
+    {
+        _removeFromStageOnValidateProperties = value;
+    }
+
+    public function get numValidateDisplayListCalls():int
+    {
+        return _numValidateDisplayListCalls;
+    }
+
+    public function get numValidateSizeCalls():int
+    {
+        return _numValidateSizeCalls;
+    }
+
+    public function get numValidatePropertiesCalls():int
+    {
+        return _numValidatePropertiesCalls;
+    }
 }
\ No newline at end of file