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

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

14 15
import 'semantics_tester.dart';

Hixie's avatar
Hixie committed
16 17
void main() {

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

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

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

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

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

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

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

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

    await gesture.up();
  });

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

254
    await gesture.up();
255 256
  });

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

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

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

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

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

    // Open the drawer.
325
    scaffoldKey.currentState!.openDrawer();
326 327 328
    await tester.pump(const Duration(milliseconds: 100));

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

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

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

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

    // Open the drawer.
353
    scaffoldKey.currentState!.openDrawer();
354 355 356
    await tester.pump(const Duration(milliseconds: 100));

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

    semantics.dispose();
360
  }, variant: TargetPlatformVariant.only(TargetPlatform.android));
361 362

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

    await tester.pumpWidget(
367 368
      MaterialApp(
        home: Builder(
369
          builder: (BuildContext context) {
370
            return Scaffold(
371 372
              key: scaffoldKey,
              drawer: const Drawer(),
373
              body: Container(),
374 375 376 377 378 379 380
            );
          },
        ),
      ),
    );

    // Open the drawer.
381
    scaffoldKey.currentState!.openDrawer();
382 383 384 385 386 387 388 389 390 391 392 393 394
    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
395
}