floating_action_button_test.dart 15.3 KB
Newer Older
1 2 3 4
// Copyright 2016 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.

5
import 'dart:io';
6 7
import 'dart:ui';

8
import 'package:flutter/material.dart';
9
import 'package:flutter/rendering.dart';
10 11
import 'package:flutter_test/flutter_test.dart';

12
import '../rendering/mock_canvas.dart';
13 14
import '../widgets/semantics_tester.dart';

15
void main() {
Ian Hickson's avatar
Ian Hickson committed
16
  testWidgets('Floating Action Button control test', (WidgetTester tester) async {
17 18
    bool didPressButton = false;
    await tester.pumpWidget(
19
      Directionality(
Ian Hickson's avatar
Ian Hickson committed
20
        textDirection: TextDirection.ltr,
21 22
        child: Center(
          child: FloatingActionButton(
Ian Hickson's avatar
Ian Hickson committed
23 24 25 26 27
            onPressed: () {
              didPressButton = true;
            },
            child: const Icon(Icons.add),
          ),
28 29
        ),
      ),
30 31 32 33 34 35
    );

    expect(didPressButton, isFalse);
    await tester.tap(find.byType(Icon));
    expect(didPressButton, isTrue);
  });
36 37 38

  testWidgets('Floating Action Button tooltip', (WidgetTester tester) async {
    await tester.pumpWidget(
39 40
      const MaterialApp(
        home: Scaffold(
41
          floatingActionButton: FloatingActionButton(
42 43
            onPressed: null,
            tooltip: 'Add',
44
            child: Icon(Icons.add),
45 46 47 48 49 50 51 52
          ),
        ),
      ),
    );

    await tester.tap(find.byType(Icon));
    expect(find.byTooltip('Add'), findsOneWidget);
  });
53

54 55 56
  // Regression test for: https://github.com/flutter/flutter/pull/21084
  testWidgets('Floating Action Button tooltip (long press button edge)', (WidgetTester tester) async {
    await tester.pumpWidget(
57 58
      const MaterialApp(
        home: Scaffold(
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
          floatingActionButton: FloatingActionButton(
            onPressed: null,
            tooltip: 'Add',
            child: Icon(Icons.add),
          ),
        ),
      ),
    );

    expect(find.text('Add'), findsNothing);
    await tester.longPressAt(_rightEdgeOfFab(tester));
    await tester.pumpAndSettle();

    expect(find.text('Add'), findsOneWidget);
  });

  // Regression test for: https://github.com/flutter/flutter/pull/21084
  testWidgets('Floating Action Button tooltip (long press button edge - no child)', (WidgetTester tester) async {
    await tester.pumpWidget(
78 79
      const MaterialApp(
        home: Scaffold(
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
          floatingActionButton: FloatingActionButton(
            onPressed: null,
            tooltip: 'Add',
          ),
        ),
      ),
    );

    expect(find.text('Add'), findsNothing);
    await tester.longPressAt(_rightEdgeOfFab(tester));
    await tester.pumpAndSettle();

    expect(find.text('Add'), findsOneWidget);
  });

95 96
  testWidgets('Floating Action Button tooltip (no child)', (WidgetTester tester) async {
    await tester.pumpWidget(
97 98
      const MaterialApp(
        home: Scaffold(
99
          floatingActionButton: FloatingActionButton(
100 101 102 103 104 105 106
            onPressed: null,
            tooltip: 'Add',
          ),
        ),
      ),
    );

107
    expect(find.text('Add'), findsNothing);
108
    await tester.longPress(find.byType(FloatingActionButton));
109
    await tester.pumpAndSettle();
110
    expect(find.text('Add'), findsOneWidget);
111 112
  });

113
  testWidgets('FlatActionButton mini size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
114
    final Key key1 = UniqueKey();
115
    await tester.pumpWidget(
116 117 118 119 120
      MaterialApp(
        home: Theme(
          data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
          child: Scaffold(
            floatingActionButton: FloatingActionButton(
121 122 123 124 125 126 127 128 129 130 131
              key: key1,
              mini: true,
              onPressed: null,
            ),
          ),
        ),
      ),
    );

    expect(tester.getSize(find.byKey(key1)), const Size(48.0, 48.0));

132
    final Key key2 = UniqueKey();
133
    await tester.pumpWidget(
134 135 136 137 138
      MaterialApp(
        home: Theme(
          data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
          child: Scaffold(
            floatingActionButton: FloatingActionButton(
139 140 141 142 143 144 145 146 147 148 149 150
              key: key2,
              mini: true,
              onPressed: null,
            ),
          ),
        ),
      ),
    );

    expect(tester.getSize(find.byKey(key2)), const Size(40.0, 40.0));
  });

151 152
  testWidgets('FloatingActionButton.isExtended', (WidgetTester tester) async {
    await tester.pumpWidget(
153 154
      const MaterialApp(
        home: Scaffold(
155
          floatingActionButton: FloatingActionButton(onPressed: null),
156 157 158 159 160 161 162 163 164 165 166 167 168 169
        ),
      ),
    );

    final Finder fabFinder = find.byType(FloatingActionButton);

    FloatingActionButton getFabWidget() {
      return tester.widget<FloatingActionButton>(fabFinder);
    }

    expect(getFabWidget().isExtended, false);
    expect(getFabWidget().shape, const CircleBorder());

    await tester.pumpWidget(
170 171 172
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton.extended(
173 174
            label: const SizedBox(
              width: 100.0,
175
              child: Text('label'),
176
            ),
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
            icon: const Icon(Icons.android),
            onPressed: null,
          ),
        ),
      ),
    );

    expect(getFabWidget().isExtended, true);
    expect(getFabWidget().shape, const StadiumBorder());
    expect(find.text('label'), findsOneWidget);
    expect(find.byType(Icon), findsOneWidget);

    // Verify that the widget's height is 48 and that its internal
    /// horizontal layout is: 16 icon 8 label 20
    expect(tester.getSize(fabFinder).height, 48.0);
192

193 194 195 196 197 198 199 200 201
    final double fabLeft = tester.getTopLeft(fabFinder).dx;
    final double fabRight = tester.getTopRight(fabFinder).dx;
    final double iconLeft = tester.getTopLeft(find.byType(Icon)).dx;
    final double iconRight = tester.getTopRight(find.byType(Icon)).dx;
    final double labelLeft = tester.getTopLeft(find.text('label')).dx;
    final double labelRight = tester.getTopRight(find.text('label')).dx;
    expect(iconLeft - fabLeft, 16.0);
    expect(labelLeft - iconRight, 8.0);
    expect(fabRight - labelRight, 20.0);
202 203 204 205 206 207

    // The overall width of the button is:
    // 168 = 16 + 24(icon) + 8 + 100(label) + 20
    expect(tester.getSize(find.byType(Icon)).width, 24.0);
    expect(tester.getSize(find.text('label')).width, 100.0);
    expect(tester.getSize(fabFinder).width, 168);
208 209
  });

210 211 212
  testWidgets('Floating Action Button heroTag', (WidgetTester tester) async {
    BuildContext theContext;
    await tester.pumpWidget(
213 214 215
      MaterialApp(
        home: Scaffold(
          body: Builder(
216 217 218 219 220 221 222 223 224
            builder: (BuildContext context) {
              theContext = context;
              return const FloatingActionButton(heroTag: 1, onPressed: null);
            },
          ),
          floatingActionButton: const FloatingActionButton(heroTag: 2, onPressed: null),
        ),
      ),
    );
225
    Navigator.push(theContext, PageRouteBuilder<void>(
226 227 228 229 230 231 232 233 234 235
      pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
        return const Placeholder();
      },
    ));
    await tester.pump(); // this would fail if heroTag was the same on both FloatingActionButtons (see below).
  });

  testWidgets('Floating Action Button heroTag - with duplicate', (WidgetTester tester) async {
    BuildContext theContext;
    await tester.pumpWidget(
236 237 238
      MaterialApp(
        home: Scaffold(
          body: Builder(
239 240 241 242 243 244 245 246 247
            builder: (BuildContext context) {
              theContext = context;
              return const FloatingActionButton(onPressed: null);
            },
          ),
          floatingActionButton: const FloatingActionButton(onPressed: null),
        ),
      ),
    );
248
    Navigator.push(theContext, PageRouteBuilder<void>(
249 250 251 252 253 254 255 256 257 258 259
      pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
        return const Placeholder();
      },
    ));
    await tester.pump();
    expect(tester.takeException().toString(), contains('FloatingActionButton'));
  });

  testWidgets('Floating Action Button heroTag - with duplicate', (WidgetTester tester) async {
    BuildContext theContext;
    await tester.pumpWidget(
260 261 262
      MaterialApp(
        home: Scaffold(
          body: Builder(
263 264 265 266 267 268 269 270 271
            builder: (BuildContext context) {
              theContext = context;
              return const FloatingActionButton(heroTag: 'xyzzy', onPressed: null);
            },
          ),
          floatingActionButton: const FloatingActionButton(heroTag: 'xyzzy', onPressed: null),
        ),
      ),
    );
272
    Navigator.push(theContext, PageRouteBuilder<void>(
273 274 275 276 277 278 279
      pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
        return const Placeholder();
      },
    ));
    await tester.pump();
    expect(tester.takeException().toString(), contains('xyzzy'));
  });
280 281

  testWidgets('Floating Action Button semantics (enabled)', (WidgetTester tester) async {
282
    final SemanticsTester semantics = SemanticsTester(tester);
283 284

    await tester.pumpWidget(
285
      Directionality(
286
        textDirection: TextDirection.ltr,
287 288
        child: Center(
          child: FloatingActionButton(
289 290 291 292 293 294 295
            onPressed: () { },
            child: const Icon(Icons.add, semanticLabel: 'Add'),
          ),
        ),
      ),
    );

296
    expect(semantics, hasSemantics(TestSemantics.root(
297
      children: <TestSemantics>[
298
        TestSemantics.rootChild(
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
          label: 'Add',
          flags: <SemanticsFlag>[
            SemanticsFlag.isButton,
            SemanticsFlag.hasEnabledState,
            SemanticsFlag.isEnabled,
          ],
          actions: <SemanticsAction>[
            SemanticsAction.tap
          ],
        ),
      ],
    ), ignoreTransform: true, ignoreId: true, ignoreRect: true));

    semantics.dispose();
  });

  testWidgets('Floating Action Button semantics (disabled)', (WidgetTester tester) async {
316
    final SemanticsTester semantics = SemanticsTester(tester);
317 318 319 320

    await tester.pumpWidget(
      const Directionality(
        textDirection: TextDirection.ltr,
321 322
        child: Center(
          child: FloatingActionButton(
323
            onPressed: null,
324
            child: Icon(Icons.add, semanticLabel: 'Add'),
325 326 327 328 329
          ),
        ),
      ),
    );

330
    expect(semantics, hasSemantics(TestSemantics.root(
331
      children: <TestSemantics>[
332
        TestSemantics.rootChild(
333 334 335 336 337 338 339 340 341 342 343
          label: 'Add',
          flags: <SemanticsFlag>[
            SemanticsFlag.isButton,
            SemanticsFlag.hasEnabledState,
          ],
        ),
      ],
    ), ignoreTransform: true, ignoreId: true, ignoreRect: true));

    semantics.dispose();
  });
344

345
  testWidgets('Tooltip is used as semantics label', (WidgetTester tester) async {
346
    final SemanticsTester semantics = SemanticsTester(tester);
347 348

    await tester.pumpWidget(
349 350 351
      MaterialApp(
        home: Scaffold(
          floatingActionButton: FloatingActionButton(
352 353 354 355 356 357 358 359
            onPressed: () { },
            tooltip: 'Add Photo',
            child: const Icon(Icons.add_a_photo),
          ),
        ),
      ),
    );

360
    expect(semantics, hasSemantics(TestSemantics.root(
361
      children: <TestSemantics>[
362
        TestSemantics.rootChild(
363
          children: <TestSemantics>[
364
            TestSemantics(
365
              flags: <SemanticsFlag>[
366 367 368
                SemanticsFlag.scopesRoute,
              ],
              children: <TestSemantics>[
369
                TestSemantics(
370 371 372 373 374 375 376 377 378 379
                  label: 'Add Photo',
                  actions: <SemanticsAction>[
                    SemanticsAction.tap
                  ],
                  flags: <SemanticsFlag>[
                    SemanticsFlag.isButton,
                    SemanticsFlag.hasEnabledState,
                    SemanticsFlag.isEnabled,
                  ],
                ),
380 381
              ],
            ),
382 383 384 385 386 387 388 389
          ],
        ),
      ],
    ), ignoreTransform: true, ignoreId: true, ignoreRect: true));

    semantics.dispose();
  });

390 391
  testWidgets('extended FAB hero transitions succeed', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/18782
392

393
    await tester.pumpWidget(
394 395 396
      MaterialApp(
        home: Scaffold(
          floatingActionButton: Builder(
397
            builder: (BuildContext context) { // define context of Navigator.push()
398
              return FloatingActionButton.extended(
399 400 401
                icon: const Icon(Icons.add),
                label: const Text('A long FAB label'),
                onPressed: () {
402
                  Navigator.push(context, MaterialPageRoute<void>(
403
                    builder: (BuildContext context) {
404 405
                      return Scaffold(
                        floatingActionButton: FloatingActionButton.extended(
406 407 408 409
                          icon: const Icon(Icons.add),
                          label: const Text('X'),
                          onPressed: () { },
                        ),
410 411
                        body: Center(
                          child: RaisedButton(
412 413 414 415 416 417 418 419 420 421 422 423 424 425
                            child: const Text('POP'),
                            onPressed: () {
                              Navigator.pop(context);
                            },
                          ),
                        ),
                      );
                    },
                  ));
                },
              );
            },
          ),
          body: const Center(
426
            child: Text('Hello World'),
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
          ),
        ),
      ),
    );

    final Finder longFAB = find.text('A long FAB label');
    final Finder shortFAB = find.text('X');
    final Finder helloWorld = find.text('Hello World');

    expect(longFAB, findsOneWidget);
    expect(shortFAB, findsNothing);
    expect(helloWorld, findsOneWidget);

    await tester.tap(longFAB);
    await tester.pumpAndSettle();

    expect(shortFAB, findsOneWidget);
    expect(longFAB, findsNothing);

    // Trigger a hero transition from shortFAB to longFAB.
    await tester.tap(find.text('POP'));
    await tester.pumpAndSettle();

    expect(longFAB, findsOneWidget);
    expect(shortFAB, findsNothing);
    expect(helloWorld, findsOneWidget);
  });
454 455 456

  // This test prevents https://github.com/flutter/flutter/issues/20483
  testWidgets('Floating Action Button clips ink splash and highlight', (WidgetTester tester) async {
457
    final GlobalKey key = GlobalKey();
458
    await tester.pumpWidget(
459 460 461 462
      MaterialApp(
        home: Scaffold(
          body: Center(
            child: RepaintBoundary(
463
              key: key,
464
              child: FloatingActionButton(
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
                onPressed: () {},
                child: const Icon(Icons.add),
              ),
            ),
          ),
        ),
      ),
    );

    await tester.press(find.byKey(key));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 1000));
    await expectLater(
      find.byKey(key),
      matchesGoldenFile('floating_action_button_test.clip.1.png'),
      skip: !Platform.isLinux,
    );
  });
483 484 485

  testWidgets('Floating Action Button has no clip by default', (WidgetTester tester) async{
    await tester.pumpWidget(
486
      Directionality(
487
          textDirection: TextDirection.ltr,
488 489
          child: Material(
            child: FloatingActionButton(
490 491 492 493 494 495 496 497 498 499 500
              onPressed: () { /* to make sure the button is enabled */ },
            ),
          )
      ),
    );

    expect(
        tester.renderObject(find.byType(FloatingActionButton)),
        paintsExactlyCountTimes(#clipPath, 0)
    );
  });
501
}
502 503 504 505 506

Offset _rightEdgeOfFab(WidgetTester tester) {
  final Finder fab = find.byType(FloatingActionButton);
  return tester.getRect(fab).centerRight - const Offset(1.0, 0.0);
}