234 lines
6 KiB
Dart
234 lines
6 KiB
Dart
// 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 flipState1 = flipKey.currentState! as FlipWidgetState;
|
|
flipState1.flip();
|
|
await tester.pump();
|
|
expect(ProbeWidgetState.buildCount, equals(3));
|
|
final 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');
|
|
|
|
var didMiddle = false;
|
|
late Widget middle;
|
|
final 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 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)));
|
|
}
|
|
});
|
|
}
|