blob: 8fb7a786c31b25296a06bd8c2ca49cf9c62890c2 [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 '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', (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)));
}
});
}