drawer_test.dart 13.3 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 6
// @dart = 2.8

7 8
import 'dart:ui';

Adam Barth's avatar
Adam Barth committed
9
import 'package:flutter_test/flutter_test.dart';
10
import 'package:flutter/foundation.dart';
11
import 'package:flutter/material.dart';
12
import 'package:flutter/rendering.dart';
13
import 'package:flutter/widgets.dart';
14
import 'package:flutter/gestures.dart' show DragStartBehavior;
Hixie's avatar
Hixie committed
15

16 17
import 'semantics_tester.dart';

Hixie's avatar
Hixie committed
18 19
void main() {

20
  testWidgets('Drawer control test', (WidgetTester tester) async {
21
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
22
    BuildContext savedContext;
23
    await tester.pumpWidget(
24 25
      MaterialApp(
        home: Builder(
26 27
          builder: (BuildContext context) {
            savedContext = context;
28
            return Scaffold(
29
              key: scaffoldKey,
30
              drawer: const Text('drawer'),
31
              body: Container(),
32
            );
33 34 35
          },
        ),
      ),
36
    );
37
    await tester.pump(); // no effect
38 39
    expect(find.text('drawer'), findsNothing);
    scaffoldKey.currentState.openDrawer();
40
    await tester.pump(); // drawer should be starting to animate in
41
    expect(find.text('drawer'), findsOneWidget);
42
    await tester.pump(const Duration(seconds: 1)); // animation done
43 44
    expect(find.text('drawer'), findsOneWidget);
    Navigator.pop(savedContext);
45
    await tester.pump(); // drawer should be starting to animate away
46
    expect(find.text('drawer'), findsOneWidget);
47
    await tester.pump(const Duration(seconds: 1)); // animation done
48
    expect(find.text('drawer'), findsNothing);
Hixie's avatar
Hixie committed
49 50
  });

51
  testWidgets('Drawer tap test', (WidgetTester tester) async {
52
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
53
    await tester.pumpWidget(
54 55
      MaterialApp(
        home: Scaffold(
56
          key: scaffoldKey,
57
          drawer: const Text('drawer'),
58
          body: Container(),
59 60
        ),
      ),
61
    );
62
    await tester.pump(); // no effect
63 64
    expect(find.text('drawer'), findsNothing);
    scaffoldKey.currentState.openDrawer();
65
    await tester.pump(); // drawer should be starting to animate in
66
    expect(find.text('drawer'), findsOneWidget);
67
    await tester.pump(const Duration(seconds: 1)); // animation done
68
    expect(find.text('drawer'), findsOneWidget);
69 70
    await tester.tap(find.text('drawer'));
    await tester.pump(); // nothing should have happened
71
    expect(find.text('drawer'), findsOneWidget);
72
    await tester.pump(const Duration(seconds: 1)); // ditto
73
    expect(find.text('drawer'), findsOneWidget);
74
    await tester.tapAt(const Offset(750.0, 100.0)); // on the mask
75
    await tester.pump();
76
    await tester.pump(const Duration(milliseconds: 10));
77 78
    // drawer should be starting to animate away
    expect(find.text('drawer'), findsOneWidget);
79
    await tester.pump(const Duration(seconds: 1)); // animation done
80
    expect(find.text('drawer'), findsNothing);
Hixie's avatar
Hixie committed
81 82
  });

83 84 85 86 87
  testWidgets('Drawer hover test', (WidgetTester tester) async {
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
    final List<String> logs = <String>[];
    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
    // Start out of hoverTarget
88 89
    await gesture.addPointer(location: const Offset(100, 100));
    addTearDown(gesture.removePointer);
90 91 92 93 94 95 96 97 98 99 100 101

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          key: scaffoldKey,
          drawer: const Text('drawer'),
          body: Align(
            alignment: Alignment.topLeft,
            child: MouseRegion(
              onEnter: (_) { logs.add('enter'); },
              onHover: (_) { logs.add('hover'); },
              onExit: (_) { logs.add('exit'); },
102
              child: const SizedBox(width: 10, height: 10),
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
            ),
          ),
        ),
      ),
    );
    expect(logs, isEmpty);
    expect(find.text('drawer'), findsNothing);

    // When drawer is closed, hover is interactable
    await gesture.moveTo(const Offset(5, 5));
    await tester.pump(); // no effect
    expect(logs, <String>['enter', 'hover']);
    logs.clear();

    await gesture.moveTo(const Offset(20, 20));
    await tester.pump(); // no effect
    expect(logs, <String>['exit']);
    logs.clear();

    // When drawer is open, hover is uninteractable
    scaffoldKey.currentState.openDrawer();
    await tester.pump(const Duration(seconds: 1)); // animation done
    expect(find.text('drawer'), findsOneWidget);

    await gesture.moveTo(const Offset(5, 5));
    await tester.pump(); // no effect
    expect(logs, isEmpty);
    logs.clear();

    await gesture.moveTo(const Offset(20, 20));
    await tester.pump(); // no effect
    expect(logs, isEmpty);
    logs.clear();

    // Close drawer, hover is interactable again
    await tester.tapAt(const Offset(750.0, 100.0)); // on the mask
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // animation done
    expect(find.text('drawer'), findsNothing);

    await gesture.moveTo(const Offset(5, 5));
    await tester.pump(); // no effect
    expect(logs, <String>['enter', 'hover']);
    logs.clear();

    await gesture.moveTo(const Offset(20, 20));
    await tester.pump(); // no effect
    expect(logs, <String>['exit']);
    logs.clear();
  });

154
  testWidgets('Drawer drag cancel resume (LTR)', (WidgetTester tester) async {
155
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
156
    await tester.pumpWidget(
157 158
      MaterialApp(
        home: Scaffold(
159
          drawerDragStartBehavior: DragStartBehavior.down,
160
          key: scaffoldKey,
161 162
          drawer: Drawer(
            child: ListView(
163
              children: <Widget>[
164
                const Text('drawer'),
165
                Container(
166
                  height: 1000.0,
167
                  color: Colors.blue[500],
168
                ),
169 170
              ],
            ),
171
          ),
172
          body: Container(),
173 174
        ),
      ),
175 176 177
    );
    expect(find.text('drawer'), findsNothing);
    scaffoldKey.currentState.openDrawer();
178
    await tester.pump(); // drawer should be starting to animate in
179
    expect(find.text('drawer'), findsOneWidget);
180
    await tester.pump(const Duration(seconds: 1)); // animation done
181
    expect(find.text('drawer'), findsOneWidget);
182

183
    await tester.tapAt(const Offset(750.0, 100.0)); // on the mask
184
    await tester.pump();
185
    await tester.pump(const Duration(milliseconds: 10));
186
    // drawer should be starting to animate away
187
    final double textLeft = tester.getTopLeft(find.text('drawer')).dx;
188
    expect(textLeft, lessThan(0.0));
189

190
    final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0));
191
    // drawer should be stopped.
192
    await tester.pump();
193
    await tester.pump(const Duration(milliseconds: 10));
194
    expect(tester.getTopLeft(find.text('drawer')).dx, equals(textLeft));
195

196
    await gesture.moveBy(const Offset(50.0, 0.0));
197
    // drawer should be returning to visible
198
    await tester.pump();
199
    await tester.pump(const Duration(seconds: 1));
200 201 202 203 204 205
    expect(tester.getTopLeft(find.text('drawer')).dx, equals(0.0));

    await gesture.up();
  });

  testWidgets('Drawer drag cancel resume (RTL)', (WidgetTester tester) async {
206
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
207
    await tester.pumpWidget(
208 209
      MaterialApp(
        home: Directionality(
210
          textDirection: TextDirection.rtl,
211
          child: Scaffold(
212
            drawerDragStartBehavior: DragStartBehavior.down,
213
            key: scaffoldKey,
214 215
            drawer: Drawer(
              child: ListView(
216 217
                children: <Widget>[
                  const Text('drawer'),
218
                  Container(
219 220 221 222 223 224
                    height: 1000.0,
                    color: Colors.blue[500],
                  ),
                ],
              ),
            ),
225
            body: Container(),
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
          ),
        ),
      ),
    );
    expect(find.text('drawer'), findsNothing);
    scaffoldKey.currentState.openDrawer();
    await tester.pump(); // drawer should be starting to animate in
    expect(find.text('drawer'), findsOneWidget);
    await tester.pump(const Duration(seconds: 1)); // animation done
    expect(find.text('drawer'), findsOneWidget);

    await tester.tapAt(const Offset(50.0, 100.0)); // on the mask
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 10));
    // drawer should be starting to animate away
    final double textRight = tester.getTopRight(find.text('drawer')).dx;
    expect(textRight, greaterThan(800.0));

    final TestGesture gesture = await tester.startGesture(const Offset(700.0, 100.0));
    // drawer should be stopped.
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 10));
    expect(tester.getTopRight(find.text('drawer')).dx, equals(textRight));

    await gesture.moveBy(const Offset(-50.0, 0.0));
    // drawer should be returning to visible
    await tester.pump();
    await tester.pump(const Duration(seconds: 1));
    expect(tester.getTopRight(find.text('drawer')).dx, equals(800.0));
255

256
    await gesture.up();
257 258
  });

259
  testWidgets('Drawer navigator back button', (WidgetTester tester) async {
260
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
261 262 263
    bool buttonPressed = false;

    await tester.pumpWidget(
264 265
      MaterialApp(
        home: Builder(
266
          builder: (BuildContext context) {
267
            return Scaffold(
268
              key: scaffoldKey,
269 270
              drawer: Drawer(
                child: ListView(
271
                  children: <Widget>[
272
                    const Text('drawer'),
273
                    TextButton(
274
                      child: const Text('close'),
275
                      onPressed: () => Navigator.pop(context),
276
                    ),
277 278
                  ],
                ),
279
              ),
280
              body: Container(
281
                child: TextButton(
282
                  child: const Text('button'),
283 284 285
                  onPressed: () { buttonPressed = true; },
                ),
              ),
286
            );
287 288 289
          },
        ),
      ),
290 291 292 293 294 295 296 297
    );

    // Open the drawer.
    scaffoldKey.currentState.openDrawer();
    await tester.pump(); // drawer should be starting to animate in
    expect(find.text('drawer'), findsOneWidget);

    // Tap the close button to pop the drawer route.
298
    await tester.pump(const Duration(milliseconds: 100));
299 300
    await tester.tap(find.text('close'));
    await tester.pump();
301
    await tester.pump(const Duration(seconds: 1));
302 303 304 305 306 307 308
    expect(find.text('drawer'), findsNothing);

    // Confirm that a button in the scaffold body is still clickable.
    await tester.tap(find.text('button'));
    expect(buttonPressed, equals(true));
  });

Dan Field's avatar
Dan Field committed
309
  testWidgets('Dismissible ModalBarrier includes button in semantic tree', (WidgetTester tester) async {
310 311
    final SemanticsTester semantics = SemanticsTester(tester);
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
312 313

    await tester.pumpWidget(
314 315
      MaterialApp(
        home: Builder(
316
          builder: (BuildContext context) {
317
            return Scaffold(
318 319 320 321 322 323
              key: scaffoldKey,
              drawer: const Drawer(),
            );
          },
        ),
      ),
324 325 326 327 328 329 330
    );

    // Open the drawer.
    scaffoldKey.currentState.openDrawer();
    await tester.pump(const Duration(milliseconds: 100));

    expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.tap]));
331
    expect(semantics, includesNodeWith(label: 'Dismiss'));
332 333

    semantics.dispose();
Dan Field's avatar
Dan Field committed
334
  }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS,  TargetPlatform.macOS }));
335 336

  testWidgets('Dismissible ModalBarrier is hidden on Android (back button is used to dismiss)', (WidgetTester tester) async {
337 338
    final SemanticsTester semantics = SemanticsTester(tester);
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
339 340

    await tester.pumpWidget(
341 342
      MaterialApp(
        home: Builder(
343
          builder: (BuildContext context) {
344
            return Scaffold(
345 346
              key: scaffoldKey,
              drawer: const Drawer(),
347
              body: Container(),
348 349 350 351
            );
          },
        ),
      ),
352 353 354 355 356 357 358
    );

    // Open the drawer.
    scaffoldKey.currentState.openDrawer();
    await tester.pump(const Duration(milliseconds: 100));

    expect(semantics, isNot(includesNodeWith(actions: <SemanticsAction>[SemanticsAction.tap])));
359
    expect(semantics, isNot(includesNodeWith(label: 'Dismiss')));
360 361

    semantics.dispose();
362
  }, variant: TargetPlatformVariant.only(TargetPlatform.android));
363 364

  testWidgets('Drawer contains route semantics flags', (WidgetTester tester) async {
365 366
    final SemanticsTester semantics = SemanticsTester(tester);
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
367 368

    await tester.pumpWidget(
369 370
      MaterialApp(
        home: Builder(
371
          builder: (BuildContext context) {
372
            return Scaffold(
373 374
              key: scaffoldKey,
              drawer: const Drawer(),
375
              body: Container(),
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
            );
          },
        ),
      ),
    );

    // Open the drawer.
    scaffoldKey.currentState.openDrawer();
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));

    expect(semantics, includesNodeWith(
      label: 'Navigation menu',
      flags: <SemanticsFlag>[
        SemanticsFlag.scopesRoute,
        SemanticsFlag.namesRoute,
      ],
    ));

    semantics.dispose();
  });
Hixie's avatar
Hixie committed
397
}