| // 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 'dart:ui' as ui show window; |
| |
| import 'package:flutter/material.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| // This is a regression test for https://github.com/flutter/flutter/issues/5840. |
| |
| class Bar extends StatefulWidget { |
| const Bar({ Key? key }) : super(key: key); |
| @override |
| BarState createState() => BarState(); |
| } |
| |
| class BarState extends State<Bar> { |
| final GlobalKey _fooKey = GlobalKey(); |
| |
| bool _mode = false; |
| |
| void trigger() { |
| setState(() { |
| _mode = !_mode; |
| }); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| if (_mode) { |
| return SizedBox( |
| child: LayoutBuilder( |
| builder: (BuildContext context, BoxConstraints constraints) { |
| return StatefulCreationCounter(key: _fooKey); |
| }, |
| ), |
| ); |
| } else { |
| return LayoutBuilder( |
| builder: (BuildContext context, BoxConstraints constraints) { |
| return StatefulCreationCounter(key: _fooKey); |
| }, |
| ); |
| } |
| } |
| } |
| |
| class StatefulCreationCounter extends StatefulWidget { |
| const StatefulCreationCounter({ Key? key }) : super(key: key); |
| |
| @override |
| StatefulCreationCounterState createState() => StatefulCreationCounterState(); |
| } |
| |
| class StatefulCreationCounterState extends State<StatefulCreationCounter> { |
| static int creationCount = 0; |
| |
| @override |
| void initState() { |
| super.initState(); |
| creationCount += 1; |
| } |
| |
| @override |
| Widget build(BuildContext context) => Container(); |
| } |
| |
| void main() { |
| testWidgets('reparent state with layout builder', (WidgetTester tester) async { |
| expect(StatefulCreationCounterState.creationCount, 0); |
| await tester.pumpWidget(const Bar()); |
| expect(StatefulCreationCounterState.creationCount, 1); |
| final BarState s = tester.state<BarState>(find.byType(Bar)); |
| s.trigger(); |
| await tester.pump(); |
| expect(StatefulCreationCounterState.creationCount, 1); |
| }); |
| |
| testWidgets('Clean then reparent with dependencies', (WidgetTester tester) async { |
| int layoutBuilderBuildCount = 0; |
| |
| late StateSetter keyedSetState; |
| late StateSetter layoutBuilderSetState; |
| late StateSetter childSetState; |
| |
| final GlobalKey key = GlobalKey(); |
| final Widget keyedWidget = StatefulBuilder( |
| key: key, |
| builder: (BuildContext context, StateSetter setState) { |
| keyedSetState = setState; |
| MediaQuery.of(context); |
| return Container(); |
| }, |
| ); |
| |
| Widget layoutBuilderChild = keyedWidget; |
| Widget deepChild = Container(); |
| |
| await tester.pumpWidget(MediaQuery( |
| data: MediaQueryData.fromWindow(ui.window), |
| child: Column( |
| children: <Widget>[ |
| StatefulBuilder(builder: (BuildContext context, StateSetter setState) { |
| layoutBuilderSetState = setState; |
| return LayoutBuilder( |
| builder: (BuildContext context, BoxConstraints constraints) { |
| layoutBuilderBuildCount += 1; |
| return layoutBuilderChild; // initially keyedWidget above, but then a new Container |
| }, |
| ); |
| }), |
| Container( |
| color: Colors.green, |
| child: Container( |
| color: Colors.green, |
| child: Container( |
| color: Colors.green, |
| child: Container( |
| color: Colors.green, |
| child: Container( |
| color: Colors.green, |
| child: Container( |
| color: Colors.green, |
| child: StatefulBuilder( |
| builder: (BuildContext context, StateSetter setState) { |
| childSetState = setState; |
| return deepChild; // initially a Container, but then the keyedWidget above |
| }, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ], |
| ), |
| )); |
| expect(layoutBuilderBuildCount, 1); |
| |
| keyedSetState(() { /* Change nothing but add the element to the dirty list. */ }); |
| |
| childSetState(() { |
| // The deep child builds in the initial build phase. It takes the child |
| // from the LayoutBuilder before the LayoutBuilder has a chance to build. |
| deepChild = keyedWidget; |
| }); |
| |
| layoutBuilderSetState(() { |
| // The layout builder will build in a separate build scope. This delays |
| // the removal of the keyed child until this build scope. |
| layoutBuilderChild = Container(); |
| }); |
| |
| // The essential part of this test is that this call to pump doesn't throw. |
| await tester.pump(); |
| expect(layoutBuilderBuildCount, 2); |
| }); |
| } |