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

5 6 7 8 9 10
// TODO(gspencergoog): Remove this tag once this test's state leaks/test
// dependencies have been fixed.
// https://github.com/flutter/flutter/issues/85160
// Fails with "flutter test --test-randomize-ordering-seed=382757700"
@Tags(<String>['no-shuffle'])

11
import 'package:flutter/foundation.dart';
12 13
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
14
import 'package:flutter_test/flutter_test.dart';
15

16
class MemoryPressureObserver with WidgetsBindingObserver {
17 18 19 20 21 22 23 24
  bool sawMemoryPressure = false;

  @override
  void didHaveMemoryPressure() {
    sawMemoryPressure = true;
  }
}

25
class AppLifecycleStateObserver with WidgetsBindingObserver {
26
  late AppLifecycleState lifecycleState;
27 28 29 30 31 32 33

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    lifecycleState = state;
  }
}

34
class PushRouteObserver with WidgetsBindingObserver {
35
  late String pushedRoute;
36 37 38 39 40 41 42 43

  @override
  Future<bool> didPushRoute(String route) async {
    pushedRoute = route;
    return true;
  }
}

44
class PushRouteInformationObserver with WidgetsBindingObserver {
45
  late RouteInformation pushedRouteInformation;
46 47 48 49 50 51 52 53

  @override
  Future<bool> didPushRouteInformation(RouteInformation routeInformation) async {
    pushedRouteInformation = routeInformation;
    return true;
  }
}

54 55 56 57 58 59
void main() {
  setUp(() {
    WidgetsFlutterBinding.ensureInitialized();
  });

  testWidgets('didHaveMemoryPressure callback', (WidgetTester tester) async {
60
    final MemoryPressureObserver observer = MemoryPressureObserver();
61
    WidgetsBinding.instance!.addObserver(observer);
62
    final ByteData message = const JSONMessageCodec().encodeMessage(<String, dynamic>{'type': 'memoryPressure'})!;
63
    await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('flutter/system', message, (_) { });
64
    expect(observer.sawMemoryPressure, true);
65
    WidgetsBinding.instance!.removeObserver(observer);
66
  });
67 68

  testWidgets('handleLifecycleStateChanged callback', (WidgetTester tester) async {
69
    final BinaryMessenger defaultBinaryMessenger = ServicesBinding.instance!.defaultBinaryMessenger;
70
    final AppLifecycleStateObserver observer = AppLifecycleStateObserver();
71
    WidgetsBinding.instance!.addObserver(observer);
72

73
    ByteData message = const StringCodec().encodeMessage('AppLifecycleState.paused')!;
74
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
75 76
    expect(observer.lifecycleState, AppLifecycleState.paused);

77
    message = const StringCodec().encodeMessage('AppLifecycleState.resumed')!;
78
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
79 80
    expect(observer.lifecycleState, AppLifecycleState.resumed);

81
    message = const StringCodec().encodeMessage('AppLifecycleState.inactive')!;
82
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
83
    expect(observer.lifecycleState, AppLifecycleState.inactive);
84

85
    message = const StringCodec().encodeMessage('AppLifecycleState.detached')!;
86
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
87
    expect(observer.lifecycleState, AppLifecycleState.detached);
88
  });
89 90

  testWidgets('didPushRoute callback', (WidgetTester tester) async {
91
    final PushRouteObserver observer = PushRouteObserver();
92
    WidgetsBinding.instance!.addObserver(observer);
93

94
    const String testRouteName = 'testRouteName';
95
    final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('pushRoute', testRouteName));
96
    await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
97 98
    expect(observer.pushedRoute, testRouteName);

99
    WidgetsBinding.instance!.removeObserver(observer);
100
  });
101

102 103
  testWidgets('didPushRouteInformation calls didPushRoute by default', (WidgetTester tester) async {
    final PushRouteObserver observer = PushRouteObserver();
104
    WidgetsBinding.instance!.addObserver(observer);
105 106 107 108

    const Map<String, dynamic> testRouteInformation = <String, dynamic>{
      'location': 'testRouteName',
      'state': 'state',
109
      'restorationData': <dynamic, dynamic>{'test': 'config'},
110 111
    };
    final ByteData message = const JSONMethodCodec().encodeMethodCall(
112 113
      const MethodCall('pushRouteInformation', testRouteInformation),
    );
114
    await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
115
    expect(observer.pushedRoute, 'testRouteName');
116
    WidgetsBinding.instance!.removeObserver(observer);
117 118 119 120
  });

  testWidgets('didPushRouteInformation callback', (WidgetTester tester) async {
    final PushRouteInformationObserver observer = PushRouteInformationObserver();
121
    WidgetsBinding.instance!.addObserver(observer);
122 123 124 125 126 127

    const Map<String, dynamic> testRouteInformation = <String, dynamic>{
      'location': 'testRouteName',
      'state': 'state',
    };
    final ByteData message = const JSONMethodCodec().encodeMethodCall(
128 129
      const MethodCall('pushRouteInformation', testRouteInformation),
    );
130
    await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
131 132
    expect(observer.pushedRouteInformation.location, 'testRouteName');
    expect(observer.pushedRouteInformation.state, 'state');
133
    WidgetsBinding.instance!.removeObserver(observer);
134 135
  });

136 137 138

  testWidgets('didPushRouteInformation callback with null state', (WidgetTester tester) async {
    final PushRouteInformationObserver observer = PushRouteInformationObserver();
139
    WidgetsBinding.instance!.addObserver(observer);
140 141 142 143 144 145

    const Map<String, dynamic> testRouteInformation = <String, dynamic>{
      'location': 'testRouteName',
      'state': null,
    };
    final ByteData message = const JSONMethodCodec().encodeMethodCall(
146 147
      const MethodCall('pushRouteInformation', testRouteInformation),
    );
148
    await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
149 150
    expect(observer.pushedRouteInformation.location, 'testRouteName');
    expect(observer.pushedRouteInformation.state, null);
151
    WidgetsBinding.instance!.removeObserver(observer);
152 153
  });

154
  testWidgets('Application lifecycle affects frame scheduling', (WidgetTester tester) async {
155
    final BinaryMessenger defaultBinaryMessenger = ServicesBinding.instance!.defaultBinaryMessenger;
156 157 158
    ByteData message;
    expect(tester.binding.hasScheduledFrame, isFalse);

159
    message = const StringCodec().encodeMessage('AppLifecycleState.paused')!;
160
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
161 162
    expect(tester.binding.hasScheduledFrame, isFalse);

163
    message = const StringCodec().encodeMessage('AppLifecycleState.resumed')!;
164
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
165 166 167 168
    expect(tester.binding.hasScheduledFrame, isTrue);
    await tester.pump();
    expect(tester.binding.hasScheduledFrame, isFalse);

169
    message = const StringCodec().encodeMessage('AppLifecycleState.inactive')!;
170
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
171 172
    expect(tester.binding.hasScheduledFrame, isFalse);

173
    message = const StringCodec().encodeMessage('AppLifecycleState.detached')!;
174 175 176
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
    expect(tester.binding.hasScheduledFrame, isFalse);

177
    message = const StringCodec().encodeMessage('AppLifecycleState.inactive')!;
178 179 180 181 182
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
    expect(tester.binding.hasScheduledFrame, isTrue);
    await tester.pump();
    expect(tester.binding.hasScheduledFrame, isFalse);

183
    message = const StringCodec().encodeMessage('AppLifecycleState.paused')!;
184
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
185 186 187 188 189
    expect(tester.binding.hasScheduledFrame, isFalse);

    tester.binding.scheduleFrame();
    expect(tester.binding.hasScheduledFrame, isFalse);

190 191
    // TODO(chunhtai): fix this test after workaround is removed
    // https://github.com/flutter/flutter/issues/45131
192 193 194 195 196 197 198 199 200 201 202 203 204
    tester.binding.scheduleForcedFrame();
    expect(tester.binding.hasScheduledFrame, isFalse);

    int frameCount = 0;
    tester.binding.addPostFrameCallback((Duration duration) { frameCount += 1; });
    expect(tester.binding.hasScheduledFrame, isFalse);
    await tester.pump(const Duration(milliseconds: 1));
    expect(tester.binding.hasScheduledFrame, isFalse);
    expect(frameCount, 0);

    tester.binding.scheduleWarmUpFrame(); // this actually tests flutter_test's implementation
    expect(tester.binding.hasScheduledFrame, isFalse);
    expect(frameCount, 1);
205 206

    // Get the tester back to a resumed state for subsequent tests.
207
    message = const StringCodec().encodeMessage('AppLifecycleState.resumed')!;
208 209 210
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
    expect(tester.binding.hasScheduledFrame, isTrue);
    await tester.pump();
211
  });
212 213

  testWidgets('scheduleFrameCallback error control test', (WidgetTester tester) async {
214
    late FlutterError error;
215
    try {
216
      tester.binding.scheduleFrameCallback((Duration _) { }, rescheduling: true);
217 218 219 220 221 222 223 224 225 226 227
    } on FlutterError catch (e) {
      error = e;
    }
    expect(error, isNotNull);
    expect(error.diagnostics.length, 3);
    expect(error.diagnostics.last.level, DiagnosticLevel.hint);
    expect(
      error.diagnostics.last.toStringDeep(),
      equalsIgnoringHashCodes(
        'If this is the initial registration of the callback, or if the\n'
        'callback is asynchronous, then do not use the "rescheduling"\n'
228
        'argument.\n',
229 230 231 232 233 234 235 236 237 238 239 240
      ),
    );
    expect(
      error.toStringDeep(),
      'FlutterError\n'
      '   scheduleFrameCallback called with rescheduling true, but no\n'
      '   callback is in scope.\n'
      '   The "rescheduling" argument should only be set to true if the\n'
      '   callback is being reregistered from within the callback itself,\n'
      '   and only then if the callback itself is entirely synchronous.\n'
      '   If this is the initial registration of the callback, or if the\n'
      '   callback is asynchronous, then do not use the "rescheduling"\n'
241
      '   argument.\n',
242 243
    );
  });
244 245

  testWidgets('defaultStackFilter elides framework Element mounting stacks', (WidgetTester tester) async {
246 247
    final FlutterExceptionHandler? oldHandler = FlutterError.onError;
    late FlutterErrorDetails errorDetails;
248
    FlutterError.onError = (FlutterErrorDetails details) {
249
      errorDetails = details;
250 251 252 253 254 255 256 257 258
    };
    await tester.pumpWidget(Directionality(
      textDirection: TextDirection.ltr,
      child: TestStatefulWidget(
        child: Builder(
          builder: (BuildContext context) {
            return Opacity(
              opacity: .5,
              child: Builder(
259 260 261 262
                builder: (BuildContext context) {
                  assert(false);
                  return const Text('');
                },
263 264 265 266 267 268 269
              ),
            );
          },
        ),
      ),
    ));
    FlutterError.onError = oldHandler;
270
    expect(errorDetails.exception, isAssertionError);
271
    const String toMatch = '...     Normal element mounting (';
272
    expect(toMatch.allMatches(errorDetails.toString()).length, 1);
273
  }, skip: kIsWeb);
274 275 276
}

class TestStatefulWidget extends StatefulWidget {
277
  const TestStatefulWidget({required this.child, Key? key}) : super(key: key);
278 279 280 281 282 283 284 285 286 287 288 289

  final Widget child;

  @override
  State<StatefulWidget> createState() => TestStatefulWidgetState();
}

class TestStatefulWidgetState extends State<TestStatefulWidget> {
  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
290
}