page_test.dart 16.7 KB
Newer Older
1 2 3 4 5
// Copyright 2017 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/material.dart';
6
import 'package:flutter/rendering.dart';
7 8 9
import 'package:flutter_test/flutter_test.dart' hide TypeMatcher;

import '../rendering/mock_canvas.dart';
10 11 12 13 14 15

void main() {
  testWidgets('test Android page transition', (WidgetTester tester) async {
    await tester.pumpWidget(
      new MaterialApp(
        theme: new ThemeData(platform: TargetPlatform.android),
16
        home: const Material(child: const Text('Page 1')),
17 18
        routes: <String, WidgetBuilder>{
          '/next': (BuildContext context) {
19
            return const Material(child: const Text('Page 2'));
20
          },
21
        },
22 23 24
      )
    );

25
    final Offset widget1TopLeft = tester.getTopLeft(find.text('Page 1'));
26 27 28 29 30

    tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 1));

31
    Offset widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
32 33
    final Size widget2Size = tester.getSize(find.text('Page 2'));

34
    // Android transition is vertical only.
35
    expect(widget1TopLeft.dx == widget2TopLeft.dx, true);
36
    // Page 1 is above page 2 mid-transition.
37
    expect(widget1TopLeft.dy < widget2TopLeft.dy, true);
38 39
    // Animation begins from the top of the page.
    expect(widget2TopLeft.dy < widget2Size.height, true);
40

41
    await tester.pump(const Duration(milliseconds: 300));
42 43 44 45 46 47 48 49 50 51 52 53

    // Page 2 covers page 1.
    expect(find.text('Page 1'), findsNothing);
    expect(find.text('Page 2'), isOnstage);

    tester.state<NavigatorState>(find.byType(Navigator)).pop();
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 1));

    widget2TopLeft = tester.getTopLeft(find.text('Page 2'));

    // Page 2 starts to move down.
54
    expect(widget1TopLeft.dy < widget2TopLeft.dy, true);
55

56
    await tester.pump(const Duration(milliseconds: 300));
57 58 59

    expect(find.text('Page 1'), isOnstage);
    expect(find.text('Page 2'), findsNothing);
60
  });
61 62

  testWidgets('test iOS page transition', (WidgetTester tester) async {
63
    final Key page2Key = new UniqueKey();
64 65 66
    await tester.pumpWidget(
      new MaterialApp(
        theme: new ThemeData(platform: TargetPlatform.iOS),
67
        home: const Material(child: const Text('Page 1')),
68 69
        routes: <String, WidgetBuilder>{
          '/next': (BuildContext context) {
70 71 72 73
            return new Material(
              key: page2Key,
              child: const Text('Page 2'),
            );
74
          },
75
        },
76 77 78
      )
    );

79
    final Offset widget1InitialTopLeft = tester.getTopLeft(find.text('Page 1'));
80 81

    tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
82

83
    await tester.pump();
84
    await tester.pump(const Duration(milliseconds: 150));
85

86 87
    Offset widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));
    Offset widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
88 89
    final RenderDecoratedBox box = tester.element(find.byKey(page2Key))
        .ancestorRenderObjectOfType(const TypeMatcher<RenderDecoratedBox>());
90

91
    // Page 1 is moving to the left.
92
    expect(widget1TransientTopLeft.dx < widget1InitialTopLeft.dx, true);
93
    // Page 1 isn't moving vertically.
94
    expect(widget1TransientTopLeft.dy == widget1InitialTopLeft.dy, true);
95
    // iOS transition is horizontal only.
96
    expect(widget1InitialTopLeft.dy == widget2TopLeft.dy, true);
97
    // Page 2 is coming in from the right.
98
    expect(widget2TopLeft.dx > widget1InitialTopLeft.dx, true);
99
    // The shadow should be drawn to one screen width to the left of where
100 101
    // the page 2 box is. `paints` tests relative to the painter's given canvas
    // rather than relative to the screen so assert that it's one screen
102
    // width to the left of 0 offset box rect and nothing is drawn inside the
103 104 105 106
    // box's rect.
    expect(box, paints..rect(
      rect: new Rect.fromLTWH(-800.0, 0.0, 800.0, 600.0)
    ));
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121

    await tester.pumpAndSettle();

    // Page 2 covers page 1.
    expect(find.text('Page 1'), findsNothing);
    expect(find.text('Page 2'), isOnstage);

    tester.state<NavigatorState>(find.byType(Navigator)).pop();
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));

    widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));
    widget2TopLeft = tester.getTopLeft(find.text('Page 2'));

    // Page 1 is coming back from the left.
122
    expect(widget1TransientTopLeft.dx < widget1InitialTopLeft.dx, true);
123
    // Page 1 isn't moving vertically.
124
    expect(widget1TransientTopLeft.dy == widget1InitialTopLeft.dy, true);
125
    // iOS transition is horizontal only.
126
    expect(widget1InitialTopLeft.dy == widget2TopLeft.dy, true);
127
    // Page 2 is leaving towards the right.
128
    expect(widget2TopLeft.dx > widget1InitialTopLeft.dx, true);
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144

    await tester.pumpAndSettle();

    expect(find.text('Page 1'), isOnstage);
    expect(find.text('Page 2'), findsNothing);

    widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));

    // Page 1 is back where it started.
    expect(widget1InitialTopLeft == widget1TransientTopLeft, true);
  });

  testWidgets('test iOS fullscreen dialog transition', (WidgetTester tester) async {
    await tester.pumpWidget(
      new MaterialApp(
        theme: new ThemeData(platform: TargetPlatform.iOS),
145
        home: const Material(child: const Text('Page 1')),
146 147 148
      )
    );

149
    final Offset widget1InitialTopLeft = tester.getTopLeft(find.text('Page 1'));
150 151 152

    tester.state<NavigatorState>(find.byType(Navigator)).push(new MaterialPageRoute<Null>(
      builder: (BuildContext context) {
153
        return const Material(child: const Text('Page 2'));
154 155 156 157 158 159 160
      },
      fullscreenDialog: true,
    ));

    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));

161 162
    Offset widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));
    Offset widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
163 164 165 166

    // Page 1 doesn't move.
    expect(widget1TransientTopLeft == widget1InitialTopLeft, true);
    // Fullscreen dialogs transitions vertically only.
167
    expect(widget1InitialTopLeft.dx == widget2TopLeft.dx, true);
168
    // Page 2 is coming in from the bottom.
169
    expect(widget2TopLeft.dy > widget1InitialTopLeft.dy, true);
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186

    await tester.pumpAndSettle();

    // Page 2 covers page 1.
    expect(find.text('Page 1'), findsNothing);
    expect(find.text('Page 2'), isOnstage);

    tester.state<NavigatorState>(find.byType(Navigator)).pop();
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));

    widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));
    widget2TopLeft = tester.getTopLeft(find.text('Page 2'));

    // Page 1 doesn't move.
    expect(widget1TransientTopLeft == widget1InitialTopLeft, true);
    // Fullscreen dialogs transitions vertically only.
187
    expect(widget1InitialTopLeft.dx == widget2TopLeft.dx, true);
188
    // Page 2 is leaving towards the bottom.
189
    expect(widget2TopLeft.dy > widget1InitialTopLeft.dy, true);
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205

    await tester.pumpAndSettle();

    expect(find.text('Page 1'), isOnstage);
    expect(find.text('Page 2'), findsNothing);

    widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));

    // Page 1 is back where it started.
    expect(widget1InitialTopLeft == widget1TransientTopLeft, true);
  });

  testWidgets('test no back gesture on Android', (WidgetTester tester) async {
    await tester.pumpWidget(
      new MaterialApp(
        theme: new ThemeData(platform: TargetPlatform.android),
206
        home: const Scaffold(body: const Text('Page 1')),
207 208
        routes: <String, WidgetBuilder>{
          '/next': (BuildContext context) {
209
            return const Scaffold(body: const Text('Page 2'));
210 211 212 213 214 215 216 217 218 219 220 221
          },
        },
      )
    );

    tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
    await tester.pumpAndSettle();

    expect(find.text('Page 1'), findsNothing);
    expect(find.text('Page 2'), isOnstage);

    // Drag from left edge to invoke the gesture.
222
    final TestGesture gesture = await tester.startGesture(const Offset(5.0, 100.0));
223
    await gesture.moveBy(const Offset(400.0, 0.0));
224
    await tester.pump();
225 226 227 228 229

    expect(find.text('Page 1'), findsNothing);
    expect(find.text('Page 2'), isOnstage);

    // Page 2 didn't move
230
    expect(tester.getTopLeft(find.text('Page 2')), Offset.zero);
231 232 233 234 235 236
  });

  testWidgets('test back gesture on iOS', (WidgetTester tester) async {
    await tester.pumpWidget(
      new MaterialApp(
        theme: new ThemeData(platform: TargetPlatform.iOS),
237
        home: const Scaffold(body: const Text('Page 1')),
238 239
        routes: <String, WidgetBuilder>{
          '/next': (BuildContext context) {
240
            return const Scaffold(body: const Text('Page 2'));
241 242 243 244 245 246 247 248 249 250 251 252
          },
        },
      )
    );

    tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
    await tester.pumpAndSettle();

    expect(find.text('Page 1'), findsNothing);
    expect(find.text('Page 2'), isOnstage);

    // Drag from left edge to invoke the gesture.
253
    final TestGesture gesture = await tester.startGesture(const Offset(5.0, 100.0));
254
    await gesture.moveBy(const Offset(400.0, 0.0));
255
    await tester.pump();
256 257 258 259

    // Page 1 is now visible.
    expect(find.text('Page 1'), isOnstage);
    expect(find.text('Page 2'), isOnstage);
260 261

    // The route widget position needs to track the finger position very exactly.
262
    expect(tester.getTopLeft(find.text('Page 2')), const Offset(400.0, 0.0));
263 264 265 266

    await gesture.moveBy(const Offset(-200.0, 0.0));
    await tester.pump();

267
    expect(tester.getTopLeft(find.text('Page 2')), const Offset(200.0, 0.0));
268 269 270 271

    await gesture.moveBy(const Offset(-100.0, 200.0));
    await tester.pump();

272
    expect(tester.getTopLeft(find.text('Page 2')), const Offset(100.0, 0.0));
273 274
  });

275 276 277 278
  testWidgets('back gesture while OS changes', (WidgetTester tester) async {
    final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
      '/': (BuildContext context) => new Material(
        child: new FlatButton(
279
          child: const Text('PUSH'),
280 281 282
          onPressed: () { Navigator.of(context).pushNamed('/b'); },
        ),
      ),
283
      '/b': (BuildContext context) => new Container(child: const Text('HELLO')),
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
    };
    await tester.pumpWidget(
      new MaterialApp(
        theme: new ThemeData(platform: TargetPlatform.iOS),
        routes: routes,
      ),
    );
    await tester.tap(find.text('PUSH'));
    expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 2);
    expect(find.text('PUSH'), findsNothing);
    expect(find.text('HELLO'), findsOneWidget);
    final Offset helloPosition1 = tester.getCenter(find.text('HELLO'));
    final TestGesture gesture = await tester.startGesture(const Offset(2.5, 300.0));
    await tester.pump(const Duration(milliseconds: 20));
    await gesture.moveBy(const Offset(100.0, 0.0));
    expect(find.text('PUSH'), findsNothing);
    expect(find.text('HELLO'), findsOneWidget);
    await tester.pump(const Duration(milliseconds: 20));
    expect(find.text('PUSH'), findsOneWidget);
    expect(find.text('HELLO'), findsOneWidget);
    final Offset helloPosition2 = tester.getCenter(find.text('HELLO'));
    expect(helloPosition1.dx, lessThan(helloPosition2.dx));
    expect(helloPosition1.dy, helloPosition2.dy);
    expect(Theme.of(tester.element(find.text('HELLO'))).platform, TargetPlatform.iOS);
    await tester.pumpWidget(
      new MaterialApp(
        theme: new ThemeData(platform: TargetPlatform.android),
        routes: routes,
      ),
    );
    // Now we have to let the theme animation run through.
    // This takes three frames (including the first one above):
    //  1. Start the Theme animation. It's at t=0 so everything else is identical.
    //  2. Start any animations that are informed by the Theme, for example, the
    //     DefaultTextStyle, on the first frame that the theme is not at t=0. In
    //     this case, it's at t=1.0 of the theme animation, so this is also the
    //     frame in which the theme animation ends.
    //  3. End all the other animations.
    expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 2);
    expect(Theme.of(tester.element(find.text('HELLO'))).platform, TargetPlatform.android);
    final Offset helloPosition3 = tester.getCenter(find.text('HELLO'));
    expect(helloPosition3, helloPosition2);
    expect(find.text('PUSH'), findsOneWidget);
    expect(find.text('HELLO'), findsOneWidget);
    await gesture.moveBy(const Offset(100.0, 0.0));
    await tester.pump(const Duration(milliseconds: 20));
    expect(find.text('PUSH'), findsOneWidget);
    expect(find.text('HELLO'), findsOneWidget);
    final Offset helloPosition4 = tester.getCenter(find.text('HELLO'));
    expect(helloPosition3.dx, lessThan(helloPosition4.dx));
    expect(helloPosition3.dy, helloPosition4.dy);
    await gesture.moveBy(const Offset(500.0, 0.0));
    await gesture.up();
    expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 2);
    expect(find.text('PUSH'), findsOneWidget);
    expect(find.text('HELLO'), findsNothing);
  });

342 343 344 345
  testWidgets('test no back gesture on iOS fullscreen dialogs', (WidgetTester tester) async {
    await tester.pumpWidget(
      new MaterialApp(
        theme: new ThemeData(platform: TargetPlatform.iOS),
346
        home: const Scaffold(body: const Text('Page 1')),
347 348 349 350 351
      )
    );

    tester.state<NavigatorState>(find.byType(Navigator)).push(new MaterialPageRoute<Null>(
      builder: (BuildContext context) {
352
        return const Scaffold(body: const Text('Page 2'));
353 354 355 356 357 358 359 360 361
      },
      fullscreenDialog: true,
    ));
    await tester.pumpAndSettle();

    expect(find.text('Page 1'), findsNothing);
    expect(find.text('Page 2'), isOnstage);

    // Drag from left edge to invoke the gesture.
362
    final TestGesture gesture = await tester.startGesture(const Offset(5.0, 100.0));
363
    await gesture.moveBy(const Offset(400.0, 0.0));
364
    await tester.pump();
365 366 367 368 369

    expect(find.text('Page 1'), findsNothing);
    expect(find.text('Page 2'), isOnstage);

    // Page 2 didn't move
370
    expect(tester.getTopLeft(find.text('Page 2')), Offset.zero);
371
  });
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446

  testWidgets('test adaptable transitions switch during execution', (WidgetTester tester) async {
    await tester.pumpWidget(
      new MaterialApp(
        theme: new ThemeData(platform: TargetPlatform.android),
        home: const Material(child: const Text('Page 1')),
        routes: <String, WidgetBuilder>{
          '/next': (BuildContext context) {
            return const Material(child: const Text('Page 2'));
          },
        },
      )
    );

    final Offset widget1InitialTopLeft = tester.getTopLeft(find.text('Page 1'));

    tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/next');
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));

    Offset widget2TopLeft = tester.getTopLeft(find.text('Page 2'));
    final Size widget2Size = tester.getSize(find.text('Page 2'));

    // Android transition is vertical only.
    expect(widget1InitialTopLeft.dx == widget2TopLeft.dx, true);
    // Page 1 is above page 2 mid-transition.
    expect(widget1InitialTopLeft.dy < widget2TopLeft.dy, true);
    // Animation begins from the top of the page.
    expect(widget2TopLeft.dy < widget2Size.height, true);

    await tester.pump(const Duration(milliseconds: 300));

    // Page 2 covers page 1.
    expect(find.text('Page 1'), findsNothing);
    expect(find.text('Page 2'), isOnstage);

    // Re-pump the same app but with iOS instead of Android.
    await tester.pumpWidget(
      new MaterialApp(
        theme: new ThemeData(platform: TargetPlatform.iOS),
        home: const Material(child: const Text('Page 1')),
        routes: <String, WidgetBuilder>{
          '/next': (BuildContext context) {
            return const Material(child: const Text('Page 2'));
          },
        },
      )
    );

    tester.state<NavigatorState>(find.byType(Navigator)).pop();
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));

    Offset widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));
    widget2TopLeft = tester.getTopLeft(find.text('Page 2'));

    // Page 1 is coming back from the left.
    expect(widget1TransientTopLeft.dx < widget1InitialTopLeft.dx, true);
    // Page 1 isn't moving vertically.
    expect(widget1TransientTopLeft.dy == widget1InitialTopLeft.dy, true);
    // iOS transition is horizontal only.
    expect(widget1InitialTopLeft.dy == widget2TopLeft.dy, true);
    // Page 2 is leaving towards the right.
    expect(widget2TopLeft.dx > widget1InitialTopLeft.dx, true);

    await tester.pump(const Duration(milliseconds: 300));

    expect(find.text('Page 1'), isOnstage);
    expect(find.text('Page 2'), findsNothing);

    widget1TransientTopLeft = tester.getTopLeft(find.text('Page 1'));

    // Page 1 is back where it started.
    expect(widget1InitialTopLeft == widget1TransientTopLeft, true);
  });
447
}