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

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

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

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

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

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

    await gesture.up();
  });

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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