build_scope_test.dart 6 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Hixie's avatar
Hixie committed
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/widgets.dart';
6
import 'package:flutter_test/flutter_test.dart';
7

Adam Barth's avatar
Adam Barth committed
8
import 'test_widgets.dart';
9

10
class ProbeWidget extends StatefulWidget {
11
  const ProbeWidget({ Key? key }) : super(key: key);
12
  @override
13
  ProbeWidgetState createState() => ProbeWidgetState();
14 15 16 17 18
}

class ProbeWidgetState extends State<ProbeWidget> {
  static int buildCount = 0;

19
  @override
20 21
  void initState() {
    super.initState();
22
    setState(() { });
23 24
  }

25
  @override
26
  void didUpdateWidget(ProbeWidget oldWidget) {
27
    super.didUpdateWidget(oldWidget);
28
    setState(() { });
29 30
  }

31
  @override
32
  Widget build(BuildContext context) {
33
    setState(() { });
34
    buildCount++;
35
    return Container();
36 37 38
  }
}

39
class BadWidget extends StatelessWidget {
40
  const BadWidget(this.parentState, { Key? key }) : super(key: key);
41

42
  final BadWidgetParentState parentState;
43

44
  @override
45
  Widget build(BuildContext context) {
46
    parentState._markNeedsBuild();
47
    return Container();
48 49 50
  }
}

51
class BadWidgetParent extends StatefulWidget {
52
  const BadWidgetParent({ Key? key }) : super(key: key);
53
  @override
54
  BadWidgetParentState createState() => BadWidgetParentState();
55 56 57
}

class BadWidgetParentState extends State<BadWidgetParent> {
58 59 60 61 62 63 64
  void _markNeedsBuild() {
    setState(() {
      // Our state didn't really change, but we're doing something pathological
      // here to trigger an interesting scenario to test.
    });
  }

65
  @override
66
  Widget build(BuildContext context) {
67
    return BadWidget(this);
68 69 70
  }
}

71
class BadDisposeWidget extends StatefulWidget {
72
  const BadDisposeWidget({ Key? key }) : super(key: key);
73
  @override
74
  BadDisposeWidgetState createState() => BadDisposeWidgetState();
75 76 77
}

class BadDisposeWidgetState extends State<BadDisposeWidget> {
78
  @override
79
  Widget build(BuildContext context) {
80
    return Container();
81 82
  }

83
  @override
84
  void dispose() {
85
    setState(() { /* This is invalid behavior. */ });
86 87 88 89
    super.dispose();
  }
}

90
class StatefulWrapper extends StatefulWidget {
91
  const StatefulWrapper({
92 93
    Key? key,
    required this.child,
94 95 96 97 98
  }) : super(key: key);

  final Widget child;

  @override
99
  StatefulWrapperState createState() => StatefulWrapperState();
100 101 102 103 104 105 106 107
}

class StatefulWrapperState extends State<StatefulWrapper> {

  void trigger() {
    setState(() { built = null; });
  }

108 109
  int? built;
  late int oldBuilt;
110 111 112 113 114 115 116

  static int buildId = 0;

  @override
  Widget build(BuildContext context) {
    buildId += 1;
    built = buildId;
117
    return widget.child;
118 119 120 121
  }
}

class Wrapper extends StatelessWidget {
122
  const Wrapper({
123 124
    Key? key,
    required this.child,
125 126 127 128 129 130 131 132 133 134
  }) : super(key: key);

  final Widget child;

  @override
  Widget build(BuildContext context) {
    return child;
  }
}

135
void main() {
136
  testWidgets('Legal times for setState', (WidgetTester tester) async {
137
    final GlobalKey flipKey = GlobalKey();
138
    expect(ProbeWidgetState.buildCount, equals(0));
139
    await tester.pumpWidget(const ProbeWidget(key: Key('a')));
140
    expect(ProbeWidgetState.buildCount, equals(1));
141
    await tester.pumpWidget(const ProbeWidget(key: Key('b')));
142
    expect(ProbeWidgetState.buildCount, equals(2));
143
    await tester.pumpWidget(FlipWidget(
144
      key: flipKey,
145
      left: Container(),
146
      right: const ProbeWidget(key: Key('c')),
147 148
    ));
    expect(ProbeWidgetState.buildCount, equals(2));
149
    final FlipWidgetState flipState1 = flipKey.currentState! as FlipWidgetState;
150
    flipState1.flip();
151
    await tester.pump();
152
    expect(ProbeWidgetState.buildCount, equals(3));
153
    final FlipWidgetState flipState2 = flipKey.currentState! as FlipWidgetState;
154
    flipState2.flip();
155
    await tester.pump();
156
    expect(ProbeWidgetState.buildCount, equals(3));
157
    await tester.pumpWidget(Container());
158
    expect(ProbeWidgetState.buildCount, equals(3));
159 160
  });

161
  testWidgets('Setting parent state during build is forbidden', (WidgetTester tester) async {
162
    await tester.pumpWidget(const BadWidgetParent());
163
    expect(tester.takeException(), isFlutterError);
164
    await tester.pumpWidget(Container());
165 166
  });

167
  testWidgets('Setting state during dispose is forbidden', (WidgetTester tester) async {
168
    await tester.pumpWidget(const BadDisposeWidget());
169
    expect(tester.takeException(), isNull);
170
    await tester.pumpWidget(Container());
171
    expect(tester.takeException(), isNotNull);
172
  });
173 174

  testWidgets('Dirty element list sort order', (WidgetTester tester) async {
175 176
    final GlobalKey key1 = GlobalKey(debugLabel: 'key1');
    final GlobalKey key2 = GlobalKey(debugLabel: 'key2');
177 178

    bool didMiddle = false;
179
    late Widget middle;
180
    final List<StateSetter> setStates = <StateSetter>[];
181 182
    Widget builder(BuildContext context, StateSetter setState) {
      setStates.add(setState);
183
      final bool returnMiddle = !didMiddle;
184
      didMiddle = true;
185 186 187 188
      return Wrapper(
        child: Wrapper(
          child: StatefulWrapper(
            child: returnMiddle ? middle : Container(),
189 190 191 192
          ),
        ),
      );
    }
193 194
    final Widget part1 = Wrapper(
      child: KeyedSubtree(
195
        key: key1,
196
        child: StatefulBuilder(
197 198 199 200
          builder: builder,
        ),
      ),
    );
201 202
    final Widget part2 = Wrapper(
      child: KeyedSubtree(
203
        key: key2,
204
        child: StatefulBuilder(
205 206 207 208 209 210 211 212
          builder: builder,
        ),
      ),
    );

    middle = part2;
    await tester.pumpWidget(part1);

213
    for (final StatefulWrapperState state in tester.stateList<StatefulWrapperState>(find.byType(StatefulWrapper))) {
214
      expect(state.built, isNotNull);
215
      state.oldBuilt = state.built!;
216 217
      state.trigger();
    }
218
    for (final StateSetter setState in setStates)
219 220 221 222 223 224 225
      setState(() { });

    StatefulWrapperState.buildId = 0;
    middle = part1;
    didMiddle = false;
    await tester.pumpWidget(part2);

226
    for (final StatefulWrapperState state in tester.stateList<StatefulWrapperState>(find.byType(StatefulWrapper))) {
227 228 229 230 231
      expect(state.built, isNotNull);
      expect(state.built, isNot(equals(state.oldBuilt)));
    }

  });
232
}