blob: 3b5b6d1a2ce9810ba8e48aa2aeaaec293e5279b2 [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 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import 'test_widgets.dart';
class ProbeWidget extends StatefulWidget {
const ProbeWidget({super.key});
@override
ProbeWidgetState createState() => ProbeWidgetState();
}
class ProbeWidgetState extends State<ProbeWidget> {
static int buildCount = 0;
@override
void initState() {
super.initState();
setState(() {});
}
@override
void didUpdateWidget(ProbeWidget oldWidget) {
super.didUpdateWidget(oldWidget);
setState(() {});
}
@override
Widget build(BuildContext context) {
setState(() {});
buildCount++;
return Container();
}
}
class BadWidget extends StatelessWidget {
const BadWidget(this.parentState, {super.key});
final BadWidgetParentState parentState;
@override
Widget build(BuildContext context) {
parentState._markNeedsBuild();
return Container();
}
}
class BadWidgetParent extends StatefulWidget {
const BadWidgetParent({super.key});
@override
BadWidgetParentState createState() => BadWidgetParentState();
}
class BadWidgetParentState extends State<BadWidgetParent> {
void _markNeedsBuild() {
setState(() {
// Our state didn't really change, but we're doing something pathological
// here to trigger an interesting scenario to test.
});
}
@override
Widget build(BuildContext context) {
return BadWidget(this);
}
}
class BadDisposeWidget extends StatefulWidget {
const BadDisposeWidget({super.key});
@override
BadDisposeWidgetState createState() => BadDisposeWidgetState();
}
class BadDisposeWidgetState extends State<BadDisposeWidget> {
@override
Widget build(BuildContext context) {
return Container();
}
@override
void dispose() {
setState(() {
/* This is invalid behavior. */
});
super.dispose();
}
}
class StatefulWrapper extends StatefulWidget {
const StatefulWrapper({super.key, required this.child});
final Widget child;
@override
StatefulWrapperState createState() => StatefulWrapperState();
}
class StatefulWrapperState extends State<StatefulWrapper> {
void trigger() {
setState(() {
built = null;
});
}
int? built;
late int oldBuilt;
static int buildId = 0;
@override
Widget build(BuildContext context) {
buildId += 1;
built = buildId;
return widget.child;
}
}
class Wrapper extends StatelessWidget {
const Wrapper({super.key, required this.child});
final Widget child;
@override
Widget build(BuildContext context) {
return child;
}
}
void main() {
testWidgets('Legal times for setState', (WidgetTester tester) async {
final GlobalKey flipKey = GlobalKey();
expect(ProbeWidgetState.buildCount, equals(0));
await tester.pumpWidget(const ProbeWidget(key: Key('a')));
expect(ProbeWidgetState.buildCount, equals(1));
await tester.pumpWidget(const ProbeWidget(key: Key('b')));
expect(ProbeWidgetState.buildCount, equals(2));
await tester.pumpWidget(
FlipWidget(key: flipKey, left: Container(), right: const ProbeWidget(key: Key('c'))),
);
expect(ProbeWidgetState.buildCount, equals(2));
final FlipWidgetState flipState1 = flipKey.currentState! as FlipWidgetState;
flipState1.flip();
await tester.pump();
expect(ProbeWidgetState.buildCount, equals(3));
final FlipWidgetState flipState2 = flipKey.currentState! as FlipWidgetState;
flipState2.flip();
await tester.pump();
expect(ProbeWidgetState.buildCount, equals(3));
await tester.pumpWidget(Container());
expect(ProbeWidgetState.buildCount, equals(3));
});
testWidgets('Setting parent state during build is forbidden', (WidgetTester tester) async {
await tester.pumpWidget(const BadWidgetParent());
expect(tester.takeException(), isFlutterError);
await tester.pumpWidget(Container());
});
testWidgets(
'Setting state during dispose is forbidden',
experimentalLeakTesting:
LeakTesting.settings.withIgnoredAll(), // leaking by design because of exception
(WidgetTester tester) async {
await tester.pumpWidget(const BadDisposeWidget());
expect(tester.takeException(), isNull);
await tester.pumpWidget(Container());
expect(tester.takeException(), isNotNull);
},
);
testWidgets('Dirty element list sort order', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey(debugLabel: 'key1');
final GlobalKey key2 = GlobalKey(debugLabel: 'key2');
bool didMiddle = false;
late Widget middle;
final List<StateSetter> setStates = <StateSetter>[];
Widget builder(BuildContext context, StateSetter setState) {
setStates.add(setState);
final bool returnMiddle = !didMiddle;
didMiddle = true;
return Wrapper(
child: Wrapper(child: StatefulWrapper(child: returnMiddle ? middle : Container())),
);
}
final Widget part1 = Wrapper(
child: KeyedSubtree(key: key1, child: StatefulBuilder(builder: builder)),
);
final Widget part2 = Wrapper(
child: KeyedSubtree(key: key2, child: StatefulBuilder(builder: builder)),
);
middle = part2;
await tester.pumpWidget(part1);
for (final StatefulWrapperState state in tester.stateList<StatefulWrapperState>(
find.byType(StatefulWrapper),
)) {
expect(state.built, isNotNull);
state.oldBuilt = state.built!;
state.trigger();
}
for (final StateSetter setState in setStates) {
setState(() {});
}
StatefulWrapperState.buildId = 0;
middle = part1;
didMiddle = false;
await tester.pumpWidget(part2);
for (final StatefulWrapperState state in tester.stateList<StatefulWrapperState>(
find.byType(StatefulWrapper),
)) {
expect(state.built, isNotNull);
expect(state.built, isNot(equals(state.oldBuilt)));
}
});
}