// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; class StateMarker extends StatefulWidget { const StateMarker({ Key key, this.child }) : super(key: key); final Widget child; @override StateMarkerState createState() => StateMarkerState(); } class StateMarkerState extends State<StateMarker> { String marker; @override Widget build(BuildContext context) { if (widget.child != null) return widget.child; return Container(); } } class DeactivateLogger extends StatefulWidget { const DeactivateLogger({ Key key, this.log }) : super(key: key); final List<String> log; @override DeactivateLoggerState createState() => DeactivateLoggerState(); } class DeactivateLoggerState extends State<DeactivateLogger> { @override void deactivate() { widget.log.add('deactivate'); super.deactivate(); } @override Widget build(BuildContext context) { widget.log.add('build'); return Container(); } } void main() { testWidgets('can reparent state', (WidgetTester tester) async { final GlobalKey left = GlobalKey(); final GlobalKey right = GlobalKey(); const StateMarker grandchild = StateMarker(); await tester.pumpWidget( Stack( textDirection: TextDirection.ltr, children: <Widget>[ Container( child: StateMarker(key: left), ), Container( child: StateMarker( key: right, child: grandchild, ), ), ], ), ); final StateMarkerState leftState = left.currentState as StateMarkerState; leftState.marker = 'left'; final StateMarkerState rightState = right.currentState as StateMarkerState; rightState.marker = 'right'; final StateMarkerState grandchildState = tester.state(find.byWidget(grandchild)); expect(grandchildState, isNotNull); grandchildState.marker = 'grandchild'; const StateMarker newGrandchild = StateMarker(); await tester.pumpWidget( Stack( textDirection: TextDirection.ltr, children: <Widget>[ Container( child: StateMarker( key: right, child: newGrandchild, ), ), Container( child: StateMarker(key: left), ), ], ), ); expect(left.currentState, equals(leftState)); expect(leftState.marker, equals('left')); expect(right.currentState, equals(rightState)); expect(rightState.marker, equals('right')); final StateMarkerState newGrandchildState = tester.state(find.byWidget(newGrandchild)); expect(newGrandchildState, isNotNull); expect(newGrandchildState, equals(grandchildState)); expect(newGrandchildState.marker, equals('grandchild')); await tester.pumpWidget( Center( child: Container( child: StateMarker( key: left, child: Container(), ), ), ), ); expect(left.currentState, equals(leftState)); expect(leftState.marker, equals('left')); expect(right.currentState, isNull); }); testWidgets('can reparent state with multichild widgets', (WidgetTester tester) async { final GlobalKey left = GlobalKey(); final GlobalKey right = GlobalKey(); const StateMarker grandchild = StateMarker(); await tester.pumpWidget( Stack( textDirection: TextDirection.ltr, children: <Widget>[ StateMarker(key: left), StateMarker( key: right, child: grandchild, ), ], ), ); final StateMarkerState leftState = left.currentState as StateMarkerState; leftState.marker = 'left'; final StateMarkerState rightState = right.currentState as StateMarkerState; rightState.marker = 'right'; final StateMarkerState grandchildState = tester.state(find.byWidget(grandchild)); expect(grandchildState, isNotNull); grandchildState.marker = 'grandchild'; const StateMarker newGrandchild = StateMarker(); await tester.pumpWidget( Stack( textDirection: TextDirection.ltr, children: <Widget>[ StateMarker( key: right, child: newGrandchild, ), StateMarker(key: left), ], ), ); expect(left.currentState, equals(leftState)); expect(leftState.marker, equals('left')); expect(right.currentState, equals(rightState)); expect(rightState.marker, equals('right')); final StateMarkerState newGrandchildState = tester.state(find.byWidget(newGrandchild)); expect(newGrandchildState, isNotNull); expect(newGrandchildState, equals(grandchildState)); expect(newGrandchildState.marker, equals('grandchild')); await tester.pumpWidget( Center( child: Container( child: StateMarker( key: left, child: Container(), ), ), ), ); expect(left.currentState, equals(leftState)); expect(leftState.marker, equals('left')); expect(right.currentState, isNull); }); testWidgets('can with scrollable list', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget(StateMarker(key: key)); final StateMarkerState keyState = key.currentState as StateMarkerState; keyState.marker = 'marked'; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: ListView( itemExtent: 100.0, children: <Widget>[ Container( key: const Key('container'), height: 100.0, child: StateMarker(key: key), ), ], ), ), ); expect(key.currentState, equals(keyState)); expect(keyState.marker, equals('marked')); await tester.pumpWidget(StateMarker(key: key)); expect(key.currentState, equals(keyState)); expect(keyState.marker, equals('marked')); }); testWidgets('Reparent during update children', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget(Stack( textDirection: TextDirection.ltr, children: <Widget>[ StateMarker(key: key), Container(width: 100.0, height: 100.0), ], )); final StateMarkerState keyState = key.currentState as StateMarkerState; keyState.marker = 'marked'; await tester.pumpWidget(Stack( textDirection: TextDirection.ltr, children: <Widget>[ Container(width: 100.0, height: 100.0), StateMarker(key: key), ], )); expect(key.currentState, equals(keyState)); expect(keyState.marker, equals('marked')); await tester.pumpWidget(Stack( textDirection: TextDirection.ltr, children: <Widget>[ StateMarker(key: key), Container(width: 100.0, height: 100.0), ], )); expect(key.currentState, equals(keyState)); expect(keyState.marker, equals('marked')); }); testWidgets('Reparent to child during update children', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget(Stack( textDirection: TextDirection.ltr, children: <Widget>[ Container(width: 100.0, height: 100.0), StateMarker(key: key), Container(width: 100.0, height: 100.0), ], )); final StateMarkerState keyState = key.currentState as StateMarkerState; keyState.marker = 'marked'; await tester.pumpWidget(Stack( textDirection: TextDirection.ltr, children: <Widget>[ Container(width: 100.0, height: 100.0, child: StateMarker(key: key)), Container(width: 100.0, height: 100.0), ], )); expect(key.currentState, equals(keyState)); expect(keyState.marker, equals('marked')); await tester.pumpWidget(Stack( textDirection: TextDirection.ltr, children: <Widget>[ Container(width: 100.0, height: 100.0), StateMarker(key: key), Container(width: 100.0, height: 100.0), ], )); expect(key.currentState, equals(keyState)); expect(keyState.marker, equals('marked')); await tester.pumpWidget(Stack( textDirection: TextDirection.ltr, children: <Widget>[ Container(width: 100.0, height: 100.0), Container(width: 100.0, height: 100.0, child: StateMarker(key: key)), ], )); expect(key.currentState, equals(keyState)); expect(keyState.marker, equals('marked')); await tester.pumpWidget(Stack( textDirection: TextDirection.ltr, children: <Widget>[ Container(width: 100.0, height: 100.0), StateMarker(key: key), Container(width: 100.0, height: 100.0), ], )); expect(key.currentState, equals(keyState)); expect(keyState.marker, equals('marked')); }); testWidgets('Deactivate implies build', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); final List<String> log = <String>[]; final DeactivateLogger logger = DeactivateLogger(key: key, log: log); await tester.pumpWidget( Container(key: UniqueKey(), child: logger) ); expect(log, equals(<String>['build'])); await tester.pumpWidget( Container(key: UniqueKey(), child: logger) ); expect(log, equals(<String>['build', 'deactivate', 'build'])); log.clear(); await tester.pump(); expect(log, isEmpty); }); testWidgets('Reparenting with multiple moves', (WidgetTester tester) async { final GlobalKey key1 = GlobalKey(); final GlobalKey key2 = GlobalKey(); final GlobalKey key3 = GlobalKey(); await tester.pumpWidget( Row( textDirection: TextDirection.ltr, children: <Widget>[ StateMarker( key: key1, child: StateMarker( key: key2, child: StateMarker( key: key3, child: StateMarker(child: Container(width: 100.0)), ), ), ), ], ), ); await tester.pumpWidget( Row( textDirection: TextDirection.ltr, children: <Widget>[ StateMarker( key: key2, child: StateMarker(child: Container(width: 100.0)), ), StateMarker( key: key1, child: StateMarker( key: key3, child: StateMarker(child: Container(width: 100.0)), ), ), ], ), ); }); }