buttons_test.dart 18.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
// Copyright 2015 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.

import 'package:flutter/rendering.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart';

void main() {
14 15 16 17
  setUp(() {
    debugResetSemanticsIdCounter();
  });

18
  testWidgets('Does FlatButton contribute semantics', (WidgetTester tester) async {
19
    final SemanticsTester semantics = SemanticsTester(tester);
20
    await tester.pumpWidget(
21
      Directionality(
Ian Hickson's avatar
Ian Hickson committed
22
        textDirection: TextDirection.ltr,
23 24 25
        child: Material(
          child: Center(
            child: FlatButton(
Ian Hickson's avatar
Ian Hickson committed
26 27 28 29 30 31
              onPressed: () { },
              child: const Text('ABC')
            ),
          ),
        ),
      ),
32 33 34
    );

    expect(semantics, hasSemantics(
35
      TestSemantics.root(
36
        children: <TestSemantics>[
37
          TestSemantics.rootChild(
38 39 40
            actions: <SemanticsAction>[
              SemanticsAction.tap,
            ],
41
            label: 'ABC',
42 43
            rect: Rect.fromLTRB(0.0, 0.0, 88.0, 48.0),
            transform: Matrix4.translationValues(356.0, 276.0, 0.0),
44 45 46 47 48
            flags: <SemanticsFlag>[
              SemanticsFlag.isButton,
              SemanticsFlag.hasEnabledState,
              SemanticsFlag.isEnabled,
            ],
49 50 51 52 53 54 55 56 57 58
          )
        ],
      ),
      ignoreId: true,
    ));

    semantics.dispose();
  });

  testWidgets('Does RaisedButton contribute semantics', (WidgetTester tester) async {
59
    final SemanticsTester semantics = SemanticsTester(tester);
60
    await tester.pumpWidget(
61
      Directionality(
62
        textDirection: TextDirection.ltr,
63 64 65
        child: Material(
          child: Center(
            child: RaisedButton(
66 67 68 69 70 71 72 73 74
              onPressed: () { },
              child: const Text('ABC')
            ),
          ),
        ),
      ),
    );

    expect(semantics, hasSemantics(
75
      TestSemantics.root(
76
        children: <TestSemantics>[
77
          TestSemantics.rootChild(
78 79 80
            actions: <SemanticsAction>[
              SemanticsAction.tap,
            ],
81
            label: 'ABC',
82 83
            rect: Rect.fromLTRB(0.0, 0.0, 88.0, 48.0),
            transform: Matrix4.translationValues(356.0, 276.0, 0.0),
84 85 86 87 88
            flags: <SemanticsFlag>[
              SemanticsFlag.isButton,
              SemanticsFlag.hasEnabledState,
              SemanticsFlag.isEnabled,
            ],
89 90
          )
        ]
91 92
      ),
      ignoreId: true,
93 94 95 96 97
    ));

    semantics.dispose();
  });

98 99
  testWidgets('Does FlatButton scale with font scale changes', (WidgetTester tester) async {
    await tester.pumpWidget(
100
      Directionality(
101
        textDirection: TextDirection.ltr,
102 103
        child: Material(
          child: MediaQuery(
104
            data: const MediaQueryData(textScaleFactor: 1.0),
105 106
            child: Center(
              child: FlatButton(
107 108 109 110 111 112 113 114 115
                onPressed: () { },
                child: const Text('ABC'),
              ),
            ),
          ),
        ),
      ),
    );

116
    expect(tester.getSize(find.byType(FlatButton)), equals(const Size(88.0, 48.0)));
117
    expect(tester.getSize(find.byType(Text)), equals(const Size(42.0, 14.0)));
118

119
    // textScaleFactor expands text, but not button.
120
    await tester.pumpWidget(
121
      Directionality(
122
        textDirection: TextDirection.ltr,
123 124
        child: Material(
          child: MediaQuery(
125
            data: const MediaQueryData(textScaleFactor: 1.3),
126 127
            child: Center(
              child: FlatButton(
128 129 130 131 132 133 134 135 136
                onPressed: () { },
                child: const Text('ABC'),
              ),
            ),
          ),
        ),
      ),
    );

137
    expect(tester.getSize(find.byType(FlatButton)), equals(const Size(88.0, 48.0)));
138
    // Scaled text rendering is different on Linux and Mac by one pixel.
139
    // TODO(gspencergoog): Figure out why this is, and fix it. https://github.com/flutter/flutter/issues/12357
140
    expect(tester.getSize(find.byType(Text)).width, isIn(<double>[54.0, 55.0]));
141
    expect(tester.getSize(find.byType(Text)).height, isIn(<double>[18.0, 19.0]));
142 143 144

    // Set text scale large enough to expand text and button.
    await tester.pumpWidget(
145
      Directionality(
146
        textDirection: TextDirection.ltr,
147 148
        child: Material(
          child: MediaQuery(
149
            data: const MediaQueryData(textScaleFactor: 3.0),
150 151
            child: Center(
              child: FlatButton(
152 153 154 155 156 157 158 159 160 161
                onPressed: () { },
                child: const Text('ABC'),
              ),
            ),
          ),
        ),
      ),
    );

    // Scaled text rendering is different on Linux and Mac by one pixel.
162
    // TODO(gspencergoog): Figure out why this is, and fix it. https://github.com/flutter/flutter/issues/12357
163
    expect(tester.getSize(find.byType(FlatButton)).width, isIn(<double>[158.0, 159.0]));
164
    expect(tester.getSize(find.byType(FlatButton)).height, equals(48.0));
165 166
    expect(tester.getSize(find.byType(Text)).width, isIn(<double>[126.0, 127.0]));
    expect(tester.getSize(find.byType(Text)).height, equals(42.0));
167 168
  });

169 170 171
  // This test is very similar to the '...explicit splashColor and highlightColor' test
  // in icon_button_test.dart. If you change this one, you may want to also change that one.
  testWidgets('MaterialButton with explicit splashColor and highlightColor', (WidgetTester tester) async {
172 173
    const Color directSplashColor = Color(0xFF000011);
    const Color directHighlightColor = Color(0xFF000011);
174

175 176 177
    Widget buttonWidget = Material(
      child: Center(
        child: MaterialButton(
178 179 180
          splashColor: directSplashColor,
          highlightColor: directHighlightColor,
          onPressed: () { /* to make sure the button is enabled */ },
181
          clipBehavior: Clip.antiAlias,
182 183 184 185 186
        ),
      ),
    );

    await tester.pumpWidget(
187
      Directionality(
Ian Hickson's avatar
Ian Hickson committed
188
        textDirection: TextDirection.ltr,
189 190
        child: Theme(
          data: ThemeData(
191 192
            materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
          ),
Ian Hickson's avatar
Ian Hickson committed
193 194
          child: buttonWidget,
        ),
195 196 197
      ),
    );

198
    final Offset center = tester.getCenter(find.byType(MaterialButton));
199 200
    final TestGesture gesture = await tester.startGesture(center);
    await tester.pump(); // start gesture
201
    await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way
202

203 204 205
    final Rect expectedClipRect = Rect.fromLTRB(356.0, 282.0, 444.0, 318.0);
    final Path expectedClipPath = Path()
     ..addRRect(RRect.fromRectAndRadius(
206 207 208
         expectedClipRect,
         const Radius.circular(2.0),
     ));
209 210 211
    expect(
      Material.of(tester.element(find.byType(MaterialButton))),
      paints
212 213 214 215
        ..clipPath(pathMatcher: coversSameAreaAs(
            expectedClipPath,
            areaToCompare: expectedClipRect.inflate(10.0),
        ))
216
        ..circle(color: directSplashColor)
217
        ..rect(color: directHighlightColor)
218 219
    );

220 221
    const Color themeSplashColor1 = Color(0xFF001100);
    const Color themeHighlightColor1 = Color(0xFF001100);
222

223 224 225
    buttonWidget = Material(
      child: Center(
        child: MaterialButton(
226
          onPressed: () { /* to make sure the button is enabled */ },
227
          clipBehavior: Clip.antiAlias,
228 229 230 231 232
        ),
      ),
    );

    await tester.pumpWidget(
233
      Directionality(
Ian Hickson's avatar
Ian Hickson committed
234
        textDirection: TextDirection.ltr,
235 236
        child: Theme(
          data: ThemeData(
Ian Hickson's avatar
Ian Hickson committed
237 238
            highlightColor: themeHighlightColor1,
            splashColor: themeSplashColor1,
239
            materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
Ian Hickson's avatar
Ian Hickson committed
240 241
          ),
          child: buttonWidget,
242 243 244 245 246 247 248
        ),
      ),
    );

    expect(
      Material.of(tester.element(find.byType(MaterialButton))),
      paints
249 250 251 252
        ..clipPath(pathMatcher: coversSameAreaAs(
            expectedClipPath,
            areaToCompare: expectedClipRect.inflate(10.0),
        ))
253
        ..circle(color: themeSplashColor1)
254
        ..rect(color: themeHighlightColor1)
255 256
    );

257 258
    const Color themeSplashColor2 = Color(0xFF002200);
    const Color themeHighlightColor2 = Color(0xFF002200);
259 260

    await tester.pumpWidget(
261
      Directionality(
Ian Hickson's avatar
Ian Hickson committed
262
        textDirection: TextDirection.ltr,
263 264
        child: Theme(
          data: ThemeData(
Ian Hickson's avatar
Ian Hickson committed
265 266
            highlightColor: themeHighlightColor2,
            splashColor: themeSplashColor2,
267
            materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
Ian Hickson's avatar
Ian Hickson committed
268 269
          ),
          child: buttonWidget, // same widget, so does not get updated because of us
270 271 272 273 274 275 276 277
        ),
      ),
    );

    expect(
      Material.of(tester.element(find.byType(MaterialButton))),
      paints
        ..circle(color: themeSplashColor2)
278
        ..rect(color: themeHighlightColor2)
279 280 281 282 283
    );

    await gesture.up();
  });

284
  testWidgets('MaterialButton has no clip by default', (WidgetTester tester) async {
285 286 287 288
    final GlobalKey buttonKey = GlobalKey();
    final Widget buttonWidget = Material(
      child: Center(
        child: MaterialButton(
289 290 291 292 293 294 295
          key: buttonKey,
          onPressed: () { /* to make sure the button is enabled */ },
        ),
      ),
    );

    await tester.pumpWidget(
296
      Directionality(
297
        textDirection: TextDirection.ltr,
298 299
        child: Theme(
          data: ThemeData(
300 301 302 303 304 305 306 307 308 309 310
            materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
          ),
          child: buttonWidget,
        ),
      ),
    );

    expect(
        tester.renderObject(find.byKey(buttonKey)),
        paintsExactlyCountTimes(#clipPath, 0)
    );
311
  });
312

313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
  testWidgets('Disabled MaterialButton has correct default text color', (WidgetTester tester) async {
    const String testText = 'Disabled';
    const Widget buttonWidget = Directionality(
      textDirection: TextDirection.ltr,
      child: Material(
        child: Center(
          child: MaterialButton(
            onPressed: null,
            child: Text(testText), // button is disabled
          ),
        ),
      ),
    );

    await tester.pumpWidget(buttonWidget);

    final RichText text = tester.widget<RichText>(find.byType(RichText));
    expect(text.text.style.color, Colors.black38);
  });

333
  testWidgets('Disabled MaterialButton has same semantic size as enabled and exposes disabled semantics', (WidgetTester tester) async {
334
    final SemanticsTester semantics = SemanticsTester(tester);
335

336
    final Rect expectedButtonSize = Rect.fromLTRB(0.0, 0.0, 116.0, 48.0);
337
    // Button is in center of screen
338
    final Matrix4 expectedButtonTransform = Matrix4.identity()
339 340 341 342 343 344
      ..translate(
        TestSemantics.fullScreen.width / 2 - expectedButtonSize.width /2,
        TestSemantics.fullScreen.height / 2 - expectedButtonSize.height /2,
      );

    // enabled button
345
    await tester.pumpWidget(Directionality(
346
      textDirection: TextDirection.ltr,
347 348 349
      child: Material(
        child: Center(
          child: MaterialButton(
350 351 352 353 354 355 356 357
            child: const Text('Button'),
            onPressed: () { /* to make sure the button is enabled */ },
          ),
        ),
      ),
    ));

    expect(semantics, hasSemantics(
358
      TestSemantics.root(
359
        children: <TestSemantics>[
360
          TestSemantics.rootChild(
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
            id: 1,
            rect: expectedButtonSize,
            transform: expectedButtonTransform,
            label: 'Button',
            actions: <SemanticsAction>[
              SemanticsAction.tap,
            ],
            flags: <SemanticsFlag>[
              SemanticsFlag.isButton,
              SemanticsFlag.hasEnabledState,
              SemanticsFlag.isEnabled,
            ],
          ),
        ],
      ),
    ));

    // disabled button
    await tester.pumpWidget(const Directionality(
      textDirection: TextDirection.ltr,
381 382 383 384
      child: Material(
        child: Center(
          child: MaterialButton(
            child: Text('Button'),
385 386 387 388 389 390 391
            onPressed: null, // button is disabled
          ),
        ),
      ),
    ));

    expect(semantics, hasSemantics(
392
      TestSemantics.root(
393
        children: <TestSemantics>[
394
          TestSemantics.rootChild(
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
            id: 1,
            rect: expectedButtonSize,
            transform: expectedButtonTransform,
            label: 'Button',
            flags: <SemanticsFlag>[
              SemanticsFlag.isButton,
              SemanticsFlag.hasEnabledState,
            ],
          ),
        ],
      ),
    ));


    semantics.dispose();
  });
411

412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 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 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
  testWidgets('MaterialButton minWidth and height parameters', (WidgetTester tester) async {
    Widget buildFrame({ double minWidth, double height, EdgeInsets padding = EdgeInsets.zero, Widget child }) {
      return Directionality(
        textDirection: TextDirection.ltr,
        child: Center(
          child: MaterialButton(
            padding: padding,
            minWidth: minWidth,
            height: height,
            onPressed: null,
            materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
            child: child,
          ),
        ),
      );
    }

    await tester.pumpWidget(buildFrame(minWidth: 8.0, height: 24.0));
    expect(tester.getSize(find.byType(MaterialButton)), const Size(8.0, 24.0));

    await tester.pumpWidget(buildFrame(minWidth: 8.0));
    // Default minHeight constraint is 36, see RawMaterialButton.
    expect(tester.getSize(find.byType(MaterialButton)), const Size(8.0, 36.0));

    await tester.pumpWidget(buildFrame(height: 8.0));
    // Default minWidth constraint is 88, see RawMaterialButton.
    expect(tester.getSize(find.byType(MaterialButton)), const Size(88.0, 8.0));

    await tester.pumpWidget(buildFrame());
    expect(tester.getSize(find.byType(MaterialButton)), const Size(88.0, 36.0));

    await tester.pumpWidget(buildFrame(padding: const EdgeInsets.all(4.0)));
    expect(tester.getSize(find.byType(MaterialButton)), const Size(88.0, 36.0));

    // Size is defined by the padding.
    await tester.pumpWidget(
      buildFrame(
        minWidth: 0.0,
        height: 0.0,
        padding: const EdgeInsets.all(4.0),
      ),
    );
    expect(tester.getSize(find.byType(MaterialButton)), const Size(8.0, 8.0));

    // Size is defined by the padded child.
    await tester.pumpWidget(
      buildFrame(
        minWidth: 0.0,
        height: 0.0,
        padding: const EdgeInsets.all(4.0),
        child: const SizedBox(width: 8.0, height: 8.0),
      ),
    );
    expect(tester.getSize(find.byType(MaterialButton)), const Size(16.0, 16.0));

    // Size is defined by the minWidth, height constraints.
    await tester.pumpWidget(
      buildFrame(
        minWidth: 18.0,
        height: 18.0,
        padding: const EdgeInsets.all(4.0),
        child: const SizedBox(width: 8.0, height: 8.0),
      ),
    );
    expect(tester.getSize(find.byType(MaterialButton)), const Size(18.0, 18.0));
  });

479
  testWidgets('MaterialButton size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
480
    final Key key1 = UniqueKey();
481
    await tester.pumpWidget(
482 483 484
      Theme(
        data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
        child: Directionality(
485
          textDirection: TextDirection.ltr,
486 487 488
          child: Material(
            child: Center(
              child: MaterialButton(
489 490 491 492 493 494 495 496 497 498 499 500
                key: key1,
                child: const SizedBox(width: 50.0, height: 8.0),
                onPressed: () {},
              ),
            ),
          ),
        ),
      ),
    );

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

501
    final Key key2 = UniqueKey();
502
    await tester.pumpWidget(
503 504 505
      Theme(
        data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
        child: Directionality(
506
          textDirection: TextDirection.ltr,
507 508 509
          child: Material(
            child: Center(
              child: MaterialButton(
510 511 512 513 514 515 516 517 518 519 520 521 522 523
                key: key2,
                child: const SizedBox(width: 50.0, height: 8.0),
                onPressed: () {},
              ),
            ),
          ),
        ),
      ),
    );

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

  testWidgets('FlatButton size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
524
    final Key key1 = UniqueKey();
525
    await tester.pumpWidget(
526 527 528
      Theme(
        data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
        child: Directionality(
529
          textDirection: TextDirection.ltr,
530 531 532
          child: Material(
            child: Center(
              child: FlatButton(
533 534 535 536 537 538 539 540 541 542 543 544
                key: key1,
                child: const SizedBox(width: 50.0, height: 8.0),
                onPressed: () {},
              ),
            ),
          ),
        ),
      ),
    );

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

545
    final Key key2 = UniqueKey();
546
    await tester.pumpWidget(
547 548 549
      Theme(
        data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
        child: Directionality(
550
          textDirection: TextDirection.ltr,
551 552 553
          child: Material(
            child: Center(
              child: FlatButton(
554 555 556 557 558 559 560 561 562 563 564 565 566 567
                key: key2,
                child: const SizedBox(width: 50.0, height: 8.0),
                onPressed: () {},
              ),
            ),
          ),
        ),
      ),
    );

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

  testWidgets('RaisedButton size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
568
    final Key key1 = UniqueKey();
569
    await tester.pumpWidget(
570 571 572
      Theme(
        data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
        child: Directionality(
573
          textDirection: TextDirection.ltr,
574 575 576
          child: Material(
            child: Center(
              child: RaisedButton(
577 578 579 580 581 582 583 584 585 586 587 588
                key: key1,
                child: const SizedBox(width: 50.0, height: 8.0),
                onPressed: () {},
              ),
            ),
          ),
        ),
      ),
    );

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

589
    final Key key2 = UniqueKey();
590
    await tester.pumpWidget(
591 592 593
      Theme(
        data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
        child: Directionality(
594
          textDirection: TextDirection.ltr,
595 596 597
          child: Material(
            child: Center(
              child: RaisedButton(
598 599 600 601 602 603 604 605 606 607 608 609
                key: key2,
                child: const SizedBox(width: 50.0, height: 8.0),
                onPressed: () {},
              ),
            ),
          ),
        ),
      ),
    );

    expect(tester.getSize(find.byKey(key2)), const Size(88.0, 36.0));
  });
610 611 612

  testWidgets('RaisedButton has no clip by default', (WidgetTester tester) async{
    await tester.pumpWidget(
613
      Directionality(
614
          textDirection: TextDirection.ltr,
615 616
          child: Material(
            child: RaisedButton(
617 618 619 620 621 622 623 624 625 626 627
              onPressed: () { /* to make sure the button is enabled */ },
            ),
          )
      ),
    );

    expect(
        tester.renderObject(find.byType(RaisedButton)),
        paintsExactlyCountTimes(#clipPath, 0)
    );
  });
628
}