Remove "disqualified" concept

Allows a non-initialised stateful component to be used as a source of
settings more than once. Instead of asserting that it was only being
used once, we assert that once you are stateful you don't get used as a
bag of settings, which is the real thing we were trying to avoid.

A lot of code ends up using StatefulComponents as a source multiple
times in a row, and before this would fail.

Patch by Ian Hickson
diff --git a/sky/packages/sky/lib/src/widgets/framework.dart b/sky/packages/sky/lib/src/widgets/framework.dart
index 7b887a2..09d5dd2 100644
--- a/sky/packages/sky/lib/src/widgets/framework.dart
+++ b/sky/packages/sky/lib/src/widgets/framework.dart
@@ -879,25 +879,13 @@
 
   StatefulComponent({ Key key }) : super(key: key);
 
-  bool _disqualifiedFromEverAppearingAgain = false;
   bool _isStateInitialized = false;
 
-  void didMount() {
-    assert(!_disqualifiedFromEverAppearingAgain);
-    super.didMount();
-  }
-
-  void _buildIfDirty() {
-    assert(!_disqualifiedFromEverAppearingAgain);
-    super._buildIfDirty();
-  }
-
   bool retainStatefulNodeIfPossible(StatefulComponent newNode) {
-    assert(!_disqualifiedFromEverAppearingAgain);
     assert(newNode != null);
+    assert(!newNode._isStateInitialized);
     assert(_canSync(this, newNode));
     assert(_child != null);
-    newNode._disqualifiedFromEverAppearingAgain = true;
 
     newNode._child = _child;
     _child = null;
@@ -910,14 +898,17 @@
   // when _sync is called, our 'old' is actually the new instance that
   // we are to copy state from.
   void _sync(Widget old, dynamic slot) {
-    assert(!_disqualifiedFromEverAppearingAgain);
-    // TODO(ianh): _sync should only be called once when old == null
-    if (old == null && !_isStateInitialized) {
-      initState();
-      _isStateInitialized = true;
+    if (old == null) {
+      if (!_isStateInitialized) {
+        initState();
+        _isStateInitialized = true;
+      }
     }
-    if (old != null)
+    if (old != null) {
+      assert(_isStateInitialized);
+      assert(!old._isStateInitialized);
       syncConstructorArguments(old);
+    }
     super._sync(old, slot);
   }
 
@@ -930,25 +921,21 @@
   // method to update `this` to account for the new values the parent
   // passed to `source`. Make sure to call super.syncConstructorArguments(source)
   // unless you are extending StatefulComponent directly.
+  // A given source can be used multiple times as a source.
+  // The source must not be mutated.
   void syncConstructorArguments(Component source);
 
-  Widget syncChild(Widget node, Widget oldNode, dynamic slot) {
-    assert(!_disqualifiedFromEverAppearingAgain);
-    return super.syncChild(node, oldNode, slot);
-  }
-
   // Calls function fn immediately and then schedules another build
   // for this Component.
   void setState(void fn()) {
-    assert(!_disqualifiedFromEverAppearingAgain);
     fn();
     _scheduleBuild();
   }
 
   String toStringName() {
-    if (_disqualifiedFromEverAppearingAgain)
-      return '[[DISQUALIFIED]] ${super.toStringName()}';
-    return super.toStringName();
+    if (_isStateInitialized)
+      return 'Stateful ${super.toStringName()}';
+    return 'Stateless ${super.toStringName()}';
   }
 }
 
diff --git a/sky/unit/test/widget/stateful_components_test.dart b/sky/unit/test/widget/stateful_components_test.dart
new file mode 100644
index 0000000..782a296
--- /dev/null
+++ b/sky/unit/test/widget/stateful_components_test.dart
@@ -0,0 +1,66 @@
+import 'package:sky/animation.dart';
+import 'package:sky/widgets.dart';
+import 'package:test/test.dart';
+
+import 'widget_tester.dart';
+
+class InnerComponent extends StatefulComponent {
+  InnerComponent();
+
+  bool _didInitState = false;
+
+  void initState() {
+    _didInitState = true;
+  }
+
+  void syncConstructorArguments(InnerComponent source) {
+  }
+
+  Widget build() {
+    return new Container();
+  }
+}
+
+class OutterContainer extends StatefulComponent {
+  OutterContainer({ this.child });
+
+  InnerComponent child;
+
+  void syncConstructorArguments(OutterContainer source) {
+    child = source.child;
+  }
+
+  Widget build() {
+    return child;
+  }
+}
+
+void main() {
+  test('resync stateful widget', () {
+
+    WidgetTester tester = new WidgetTester();
+
+    InnerComponent inner;
+    OutterContainer outter;
+
+    tester.pumpFrame(() {
+      return new OutterContainer(child: new InnerComponent());
+    });
+
+    tester.pumpFrame(() {
+      inner = new InnerComponent();
+      outter = new OutterContainer(child: inner);
+      return outter;
+    });
+
+    expect(inner._didInitState, isFalse);
+    expect(inner.parent, isNull);
+
+    outter.setState(() {});
+    scheduler.beginFrame(0.0);
+
+    expect(inner._didInitState, isFalse);
+    expect(inner.parent, isNull);
+
+  });
+}