blob: 78026985fd9e082d9890d0263706e0a8df282c20 [file] [log] [blame]
// 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);
});
}