| // Copyright 2015 The Chromium 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_test/flutter_test.dart'; |
| import 'package:flutter/widgets.dart'; |
| |
| import 'test_widgets.dart'; |
| |
| class ProbeWidget extends StatefulWidget { |
| @override |
| ProbeWidgetState createState() => new 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 new Container(); |
| } |
| } |
| |
| class BadWidget extends StatelessWidget { |
| const BadWidget(this.parentState); |
| |
| final BadWidgetParentState parentState; |
| |
| @override |
| Widget build(BuildContext context) { |
| parentState._markNeedsBuild(); |
| return new Container(); |
| } |
| } |
| |
| class BadWidgetParent extends StatefulWidget { |
| @override |
| BadWidgetParentState createState() => new 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 new BadWidget(this); |
| } |
| } |
| |
| class BadDisposeWidget extends StatefulWidget { |
| @override |
| BadDisposeWidgetState createState() => new BadDisposeWidgetState(); |
| } |
| |
| class BadDisposeWidgetState extends State<BadDisposeWidget> { |
| @override |
| Widget build(BuildContext context) { |
| return new Container(); |
| } |
| |
| @override |
| void dispose() { |
| setState(() { /* This is invalid behavior. */ }); |
| super.dispose(); |
| } |
| } |
| |
| class StatefulWrapper extends StatefulWidget { |
| const StatefulWrapper({ |
| Key key, |
| this.child, |
| }) : super(key: key); |
| |
| final Widget child; |
| |
| @override |
| StatefulWrapperState createState() => new StatefulWrapperState(); |
| } |
| |
| class StatefulWrapperState extends State<StatefulWrapper> { |
| |
| void trigger() { |
| setState(() { built = null; }); |
| } |
| |
| int built; |
| 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, |
| 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 = new GlobalKey(); |
| expect(ProbeWidgetState.buildCount, equals(0)); |
| await tester.pumpWidget(new ProbeWidget()); |
| expect(ProbeWidgetState.buildCount, equals(1)); |
| await tester.pumpWidget(new ProbeWidget()); |
| expect(ProbeWidgetState.buildCount, equals(2)); |
| await tester.pumpWidget(new FlipWidget( |
| key: flipKey, |
| left: new Container(), |
| right: new ProbeWidget() |
| )); |
| expect(ProbeWidgetState.buildCount, equals(2)); |
| final FlipWidgetState flipState1 = flipKey.currentState; |
| flipState1.flip(); |
| await tester.pump(); |
| expect(ProbeWidgetState.buildCount, equals(3)); |
| final FlipWidgetState flipState2 = flipKey.currentState; |
| flipState2.flip(); |
| await tester.pump(); |
| expect(ProbeWidgetState.buildCount, equals(3)); |
| await tester.pumpWidget(new Container()); |
| expect(ProbeWidgetState.buildCount, equals(3)); |
| }); |
| |
| testWidgets('Setting parent state during build is forbidden', (WidgetTester tester) async { |
| await tester.pumpWidget(new BadWidgetParent()); |
| expect(tester.takeException(), isFlutterError); |
| await tester.pumpWidget(new Container()); |
| }); |
| |
| testWidgets('Setting state during dispose is forbidden', (WidgetTester tester) async { |
| await tester.pumpWidget(new BadDisposeWidget()); |
| expect(tester.takeException(), isNull); |
| await tester.pumpWidget(new Container()); |
| expect(tester.takeException(), isNotNull); |
| }); |
| |
| testWidgets('Dirty element list sort order', (WidgetTester tester) async { |
| final GlobalKey key1 = new GlobalKey(debugLabel: 'key1'); |
| final GlobalKey key2 = new GlobalKey(debugLabel: 'key2'); |
| |
| bool didMiddle = false; |
| Widget middle; |
| final List<StateSetter> setStates = <StateSetter>[]; |
| Widget builder(BuildContext context, StateSetter setState) { |
| setStates.add(setState); |
| final bool returnMiddle = !didMiddle; |
| didMiddle = true; |
| return new Wrapper( |
| child: new Wrapper( |
| child: new StatefulWrapper( |
| child: returnMiddle ? middle : new Container(), |
| ), |
| ), |
| ); |
| } |
| final Widget part1 = new Wrapper( |
| child: new KeyedSubtree( |
| key: key1, |
| child: new StatefulBuilder( |
| builder: builder, |
| ), |
| ), |
| ); |
| final Widget part2 = new Wrapper( |
| child: new KeyedSubtree( |
| key: key2, |
| child: new StatefulBuilder( |
| builder: builder, |
| ), |
| ), |
| ); |
| |
| middle = part2; |
| await tester.pumpWidget(part1); |
| |
| for (StatefulWrapperState state in tester.stateList<StatefulWrapperState>(find.byType(StatefulWrapper))) { |
| expect(state.built, isNotNull); |
| state.oldBuilt = state.built; |
| state.trigger(); |
| } |
| for (StateSetter setState in setStates) |
| setState(() { }); |
| |
| StatefulWrapperState.buildId = 0; |
| middle = part1; |
| didMiddle = false; |
| await tester.pumpWidget(part2); |
| |
| for (StatefulWrapperState state in tester.stateList<StatefulWrapperState>(find.byType(StatefulWrapper))) { |
| expect(state.built, isNotNull); |
| expect(state.built, isNot(equals(state.oldBuilt))); |
| } |
| |
| }); |
| } |