| // 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))); |
| } |
| }); |
| } |