| // 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({ Key? key }) : super(key: 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, { Key? key }) : super(key: key); |
| |
| final BadWidgetParentState parentState; |
| |
| @override |
| Widget build(BuildContext context) { |
| parentState._markNeedsBuild(); |
| return Container(); |
| } |
| } |
| |
| class BadWidgetParent extends StatefulWidget { |
| const BadWidgetParent({ Key? key }) : super(key: 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({ Key? key }) : super(key: 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({ |
| Key? key, |
| required this.child, |
| }) : super(key: key); |
| |
| 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({ |
| Key? key, |
| required this.child, |
| }) : super(key: key); |
| |
| 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))); |
| } |
| |
| }); |
| } |