drawer_test.dart 13.1 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
import 'dart:ui';

7
import 'package:flutter/gestures.dart' show DragStartBehavior;
8
import 'package:flutter/material.dart';
9
import 'package:flutter/rendering.dart';
10
import 'package:flutter_test/flutter_test.dart';
Hixie's avatar
Hixie committed
11

12 13
import 'semantics_tester.dart';

Hixie's avatar
Hixie committed
14 15
void main() {

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

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

79 80 81 82 83
  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
84
    await gesture.addPointer(location: const Offset(100, 100));
85 86 87 88 89 90 91 92 93 94 95 96

    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'); },
97
              child: const SizedBox(width: 10, height: 10),
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
            ),
          ),
        ),
      ),
    );
    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
118
    scaffoldKey.currentState!.openDrawer();
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
    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();
  });

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

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

185
    final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0));
186
    // drawer should be stopped.
187
    await tester.pump();
188
    await tester.pump(const Duration(milliseconds: 10));
189
    expect(tester.getTopLeft(find.text('drawer')).dx, equals(textLeft));
190

191
    await gesture.moveBy(const Offset(50.0, 0.0));
192
    // drawer should be returning to visible
193
    await tester.pump();
194
    await tester.pump(const Duration(seconds: 1));
195 196 197 198 199 200
    expect(tester.getTopLeft(find.text('drawer')).dx, equals(0.0));

    await gesture.up();
  });

  testWidgets('Drawer drag cancel resume (RTL)', (WidgetTester tester) async {
201
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
202
    await tester.pumpWidget(
203 204
      MaterialApp(
        home: Directionality(
205
          textDirection: TextDirection.rtl,
206
          child: Scaffold(
207
            drawerDragStartBehavior: DragStartBehavior.down,
208
            key: scaffoldKey,
209 210
            drawer: Drawer(
              child: ListView(
211 212
                children: <Widget>[
                  const Text('drawer'),
213
                  Container(
214 215 216 217 218 219
                    height: 1000.0,
                    color: Colors.blue[500],
                  ),
                ],
              ),
            ),
220
            body: Container(),
221 222 223 224 225
          ),
        ),
      ),
    );
    expect(find.text('drawer'), findsNothing);
226
    scaffoldKey.currentState!.openDrawer();
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
    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));
250

251
    await gesture.up();
252 253
  });

254
  testWidgets('Drawer navigator back button', (WidgetTester tester) async {
255
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
256 257 258
    bool buttonPressed = false;

    await tester.pumpWidget(
259 260
      MaterialApp(
        home: Builder(
261
          builder: (BuildContext context) {
262
            return Scaffold(
263
              key: scaffoldKey,
264 265
              drawer: Drawer(
                child: ListView(
266
                  children: <Widget>[
267
                    const Text('drawer'),
268
                    TextButton(
269
                      child: const Text('close'),
270
                      onPressed: () => Navigator.pop(context),
271
                    ),
272 273
                  ],
                ),
274
              ),
275 276 277
              body: TextButton(
                child: const Text('button'),
                onPressed: () { buttonPressed = true; },
278
              ),
279
            );
280 281 282
          },
        ),
      ),
283 284 285
    );

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

    // Tap the close button to pop the drawer route.
291
    await tester.pump(const Duration(milliseconds: 100));
292 293
    await tester.tap(find.text('close'));
    await tester.pump();
294
    await tester.pump(const Duration(seconds: 1));
295 296 297 298 299 300 301
    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
302
  testWidgets('Dismissible ModalBarrier includes button in semantic tree', (WidgetTester tester) async {
303 304
    final SemanticsTester semantics = SemanticsTester(tester);
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
305 306

    await tester.pumpWidget(
307 308
      MaterialApp(
        home: Builder(
309
          builder: (BuildContext context) {
310
            return Scaffold(
311 312 313 314 315 316
              key: scaffoldKey,
              drawer: const Drawer(),
            );
          },
        ),
      ),
317 318 319
    );

    // Open the drawer.
320
    scaffoldKey.currentState!.openDrawer();
321 322 323
    await tester.pump(const Duration(milliseconds: 100));

    expect(semantics, includesNodeWith(actions: <SemanticsAction>[SemanticsAction.tap]));
324
    expect(semantics, includesNodeWith(label: 'Dismiss'));
325 326

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

  testWidgets('Dismissible ModalBarrier is hidden on Android (back button is used to dismiss)', (WidgetTester tester) async {
330 331
    final SemanticsTester semantics = SemanticsTester(tester);
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
332 333

    await tester.pumpWidget(
334 335
      MaterialApp(
        home: Builder(
336
          builder: (BuildContext context) {
337
            return Scaffold(
338 339
              key: scaffoldKey,
              drawer: const Drawer(),
340
              body: Container(),
341 342 343 344
            );
          },
        ),
      ),
345 346 347
    );

    // Open the drawer.
348
    scaffoldKey.currentState!.openDrawer();
349 350 351
    await tester.pump(const Duration(milliseconds: 100));

    expect(semantics, isNot(includesNodeWith(actions: <SemanticsAction>[SemanticsAction.tap])));
352
    expect(semantics, isNot(includesNodeWith(label: 'Dismiss')));
353 354

    semantics.dispose();
355
  }, variant: TargetPlatformVariant.only(TargetPlatform.android));
356 357

  testWidgets('Drawer contains route semantics flags', (WidgetTester tester) async {
358 359
    final SemanticsTester semantics = SemanticsTester(tester);
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
360 361

    await tester.pumpWidget(
362 363
      MaterialApp(
        home: Builder(
364
          builder: (BuildContext context) {
365
            return Scaffold(
366 367
              key: scaffoldKey,
              drawer: const Drawer(),
368
              body: Container(),
369 370 371 372 373 374 375
            );
          },
        ),
      ),
    );

    // Open the drawer.
376
    scaffoldKey.currentState!.openDrawer();
377 378 379 380 381 382 383 384 385 386 387 388 389
    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
390
}