binding_test.dart 11.5 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
import 'package:flutter/foundation.dart';
6 7 8 9
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';

10
class MemoryPressureObserver with WidgetsBindingObserver {
11 12 13 14 15 16 17 18
  bool sawMemoryPressure = false;

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

19
class AppLifecycleStateObserver with WidgetsBindingObserver {
20
  late AppLifecycleState lifecycleState;
21 22 23 24 25 26 27

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

28
class PushRouteObserver with WidgetsBindingObserver {
29
  late String pushedRoute;
30 31 32 33 34 35 36 37

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

38
class PushRouteInformationObserver with WidgetsBindingObserver {
39
  late RouteInformation pushedRouteInformation;
40 41 42 43 44 45 46 47

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

48 49 50 51 52 53
void main() {
  setUp(() {
    WidgetsFlutterBinding.ensureInitialized();
  });

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

  testWidgets('handleLifecycleStateChanged callback', (WidgetTester tester) async {
64
    final BinaryMessenger defaultBinaryMessenger = ServicesBinding.instance!.defaultBinaryMessenger;
65
    final AppLifecycleStateObserver observer = AppLifecycleStateObserver();
66
    WidgetsBinding.instance!.addObserver(observer);
67

68
    ByteData message = const StringCodec().encodeMessage('AppLifecycleState.paused')!;
69
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
70 71
    expect(observer.lifecycleState, AppLifecycleState.paused);

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

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

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

  testWidgets('didPushRoute callback', (WidgetTester tester) async {
86
    final PushRouteObserver observer = PushRouteObserver();
87
    WidgetsBinding.instance!.addObserver(observer);
88

89
    const String testRouteName = 'testRouteName';
90
    final ByteData message = const JSONMethodCodec().encodeMethodCall(
91
      const MethodCall('pushRoute', testRouteName));
92
    await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { });
93 94
    expect(observer.pushedRoute, testRouteName);

95
    WidgetsBinding.instance!.removeObserver(observer);
96
  });
97

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

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

  testWidgets('didPushRouteInformation callback', (WidgetTester tester) async {
    final PushRouteInformationObserver observer = PushRouteInformationObserver();
116
    WidgetsBinding.instance!.addObserver(observer);
117 118 119 120 121 122 123

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

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146

  testWidgets('didPushRouteInformation callback with null state', (WidgetTester tester) async {
    final PushRouteInformationObserver observer = PushRouteInformationObserver();
    WidgetsBinding.instance!.addObserver(observer);

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

147
  testWidgets('Application lifecycle affects frame scheduling', (WidgetTester tester) async {
148
    final BinaryMessenger defaultBinaryMessenger = ServicesBinding.instance!.defaultBinaryMessenger;
149 150 151
    ByteData message;
    expect(tester.binding.hasScheduledFrame, isFalse);

152
    message = const StringCodec().encodeMessage('AppLifecycleState.paused')!;
153
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
154 155
    expect(tester.binding.hasScheduledFrame, isFalse);

156
    message = const StringCodec().encodeMessage('AppLifecycleState.resumed')!;
157
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
158 159 160 161
    expect(tester.binding.hasScheduledFrame, isTrue);
    await tester.pump();
    expect(tester.binding.hasScheduledFrame, isFalse);

162
    message = const StringCodec().encodeMessage('AppLifecycleState.inactive')!;
163
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
164 165
    expect(tester.binding.hasScheduledFrame, isFalse);

166
    message = const StringCodec().encodeMessage('AppLifecycleState.detached')!;
167 168 169
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
    expect(tester.binding.hasScheduledFrame, isFalse);

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

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

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

183 184
    // TODO(chunhtai): fix this test after workaround is removed
    // https://github.com/flutter/flutter/issues/45131
185 186 187 188 189 190 191 192 193 194 195 196 197
    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);
198 199

    // Get the tester back to a resumed state for subsequent tests.
200
    message = const StringCodec().encodeMessage('AppLifecycleState.resumed')!;
201 202 203
    await defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
    expect(tester.binding.hasScheduledFrame, isTrue);
    await tester.pump();
204
  });
205 206

  testWidgets('scheduleFrameCallback error control test', (WidgetTester tester) async {
207
    late FlutterError error;
208
    try {
209
      tester.binding.scheduleFrameCallback((Duration _) { }, rescheduling: true);
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
    } 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'
        'argument.\n'
      ),
    );
    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'
      '   argument.\n'
    );
  });
237 238

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

class TestStatefulWidget extends StatefulWidget {
270
  const TestStatefulWidget({required this.child, Key? key}) : super(key: key);
271 272 273 274 275 276 277 278 279 280 281 282

  final Widget child;

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

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