button_test.dart 15.3 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/cupertino.dart';
6 7
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
8
import 'package:flutter/rendering.dart';
9 10 11
import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';

12 13
import '../widgets/semantics_tester.dart';

14
const TextStyle testStyle = TextStyle(
15 16
  fontFamily: 'Ahem',
  fontSize: 10.0,
xster's avatar
xster committed
17
  letterSpacing: 0.0,
18 19
);

20
void main() {
21
  testWidgets('Default layout minimum size', (WidgetTester tester) async {
22
    await tester.pumpWidget(
Ian Hickson's avatar
Ian Hickson committed
23
      boilerplate(child: const CupertinoButton(
24
        onPressed: null,
25
        child: Text('X', style: testStyle),
26
      )),
27
    );
28
    final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton));
29 30
    expect(
      buttonBox.size,
31 32 33 34 35 36
      // 1 10px character + 16px * 2 is smaller than the default 44px minimum.
      const Size.square(44.0),
    );
  });

  testWidgets('Minimum size parameter', (WidgetTester tester) async {
37
    const double minSize = 60.0;
38
    await tester.pumpWidget(
39
      boilerplate(child: const CupertinoButton(
40 41
        onPressed: null,
        minSize: minSize,
42
        child: Text('X', style: testStyle),
43
      )),
44 45 46 47 48
    );
    final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton));
    expect(
      buttonBox.size,
      // 1 10px character + 16px * 2 is smaller than defined 60.0px minimum
49
      const Size.square(minSize),
50 51 52 53 54
    );
  });

  testWidgets('Size grows with text', (WidgetTester tester) async {
    await tester.pumpWidget(
Ian Hickson's avatar
Ian Hickson committed
55
      boilerplate(child: const CupertinoButton(
56
        onPressed: null,
57
        child: Text('XXXX', style: testStyle),
58
      )),
59
    );
60
    final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton));
61 62
    expect(
      buttonBox.size.width,
63 64
      // 4 10px character + 16px * 2 = 72.
      72.0,
65 66 67
    );
  });

68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
  // TODO(LongCatIsLoong): Uncomment once https://github.com/flutter/flutter/issues/44115
  // is fixed.
  /*
  testWidgets(
    'CupertinoButton.filled default color contrast meets guideline',
    (WidgetTester tester) async {
      // The native color combination systemBlue text over white background fails
      // to pass the color contrast guideline.
      //await tester.pumpWidget(
      //  CupertinoTheme(
      //    data: const CupertinoThemeData(),
      //    child: Directionality(
      //      textDirection: TextDirection.ltr,
      //      child: CupertinoButton.filled(
      //        child: const Text('Button'),
      //        onPressed: () {},
      //      ),
      //    ),
      //  ),
      //);
      //await expectLater(tester, meetsGuideline(textContrastGuideline));

      await tester.pumpWidget(
        CupertinoApp(
          theme: const CupertinoThemeData(brightness: Brightness.dark),
          home: CupertinoPageScaffold(
            child: CupertinoButton.filled(
              child: const Text('Button'),
              onPressed: () {},
            ),
          ),
        ),
      );

      await expectLater(tester, meetsGuideline(textContrastGuideline));
  });
  */

106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
  testWidgets('Button child alignment', (WidgetTester tester) async {
    await tester.pumpWidget(
      CupertinoApp(
        home: CupertinoButton(
          onPressed: () { },
          child: const Text('button'),
        ),
      ),
    );

    Align align = tester.firstWidget<Align>(find.ancestor(of: find.text('button'), matching: find.byType(Align)));
    expect(align.alignment, Alignment.center); // default

    await tester.pumpWidget(
      CupertinoApp(
        home: CupertinoButton(
          alignment: Alignment.centerLeft,
          onPressed: () { },
          child: const Text('button'),
        ),
      ),
    );

    align = tester.firstWidget<Align>(find.ancestor(of: find.text('button'), matching: find.byType(Align)));
    expect(align.alignment, Alignment.centerLeft);
  });

133
  testWidgets('Button with background is wider', (WidgetTester tester) async {
Ian Hickson's avatar
Ian Hickson committed
134
    await tester.pumpWidget(boilerplate(child: const CupertinoButton(
135
      onPressed: null,
136
      color: Color(0xFFFFFFFF),
137
      child: Text('X', style: testStyle),
138
    )));
139
    final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton));
140 141
    expect(
      buttonBox.size.width,
142 143
      // 1 10px character + 64 * 2 = 138 for buttons with background.
      138.0,
144 145 146 147
    );
  });

  testWidgets('Custom padding', (WidgetTester tester) async {
Ian Hickson's avatar
Ian Hickson committed
148
    await tester.pumpWidget(boilerplate(child: const CupertinoButton(
149
      onPressed: null,
150
      padding: EdgeInsets.all(100.0),
151
      child: Text('X', style: testStyle),
152
    )));
153
    final RenderBox buttonBox = tester.renderObject(find.byType(CupertinoButton));
154 155
    expect(
      buttonBox.size,
156
      const Size.square(210.0),
157 158 159 160 161 162
    );
  });

  testWidgets('Button takes taps', (WidgetTester tester) async {
    bool value = false;
    await tester.pumpWidget(
163
      StatefulBuilder(
164
        builder: (BuildContext context, StateSetter setState) {
Ian Hickson's avatar
Ian Hickson committed
165
          return boilerplate(
166
            child: CupertinoButton(
167
              child: const Text('Tap me'),
168 169 170 171 172 173 174 175 176 177 178 179 180
              onPressed: () {
                setState(() {
                  value = true;
                });
              },
            ),
          );
        },
      ),
    );

    expect(value, isFalse);
    // No animating by default.
181
    expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
182 183 184
    await tester.tap(find.byType(CupertinoButton));
    expect(value, isTrue);
    // Animates.
185
    expect(SchedulerBinding.instance.transientCallbackCount, equals(1));
186 187
  });

188
  testWidgets("Disabled button doesn't animate", (WidgetTester tester) async {
Ian Hickson's avatar
Ian Hickson committed
189
    await tester.pumpWidget(boilerplate(child: const CupertinoButton(
190
      onPressed: null,
191
      child: Text('Tap me'),
192
    )));
193
    expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
194 195
    await tester.tap(find.byType(CupertinoButton));
    // Still doesn't animate.
196
    expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
197
  });
198

199 200 201 202 203 204 205 206 207 208 209
  testWidgets('Enabled button animates', (WidgetTester tester) async {
    await tester.pumpWidget(boilerplate(child: CupertinoButton(
      child: const Text('Tap me'),
      onPressed: () { },
    )));

    await tester.tap(find.byType(CupertinoButton));
    // Enter animation.
    await tester.pump();
    FadeTransition transition = tester.firstWidget(find.byType(FadeTransition));

210
    await tester.pump(const Duration(milliseconds: 50));
211
    transition = tester.firstWidget(find.byType(FadeTransition));
212
    expect(transition.opacity.value, moreOrLessEquals(0.403, epsilon: 0.001));
213

214
    await tester.pump(const Duration(milliseconds: 100));
215
    transition = tester.firstWidget(find.byType(FadeTransition));
216
    expect(transition.opacity.value, moreOrLessEquals(0.400, epsilon: 0.001));
217

218
    await tester.pump(const Duration(milliseconds: 50));
219
    transition = tester.firstWidget(find.byType(FadeTransition));
220
    expect(transition.opacity.value, moreOrLessEquals(0.650, epsilon: 0.001));
221

222
    await tester.pump(const Duration(milliseconds: 50));
223
    transition = tester.firstWidget(find.byType(FadeTransition));
224
    expect(transition.opacity.value, moreOrLessEquals(0.894, epsilon: 0.001));
225

226 227 228 229 230
    await tester.pump(const Duration(milliseconds: 50));
    transition = tester.firstWidget(find.byType(FadeTransition));
    expect(transition.opacity.value, moreOrLessEquals(0.988, epsilon: 0.001));

    await tester.pump(const Duration(milliseconds: 50));
231 232 233 234
    transition = tester.firstWidget(find.byType(FadeTransition));
    expect(transition.opacity.value, moreOrLessEquals(1.0, epsilon: 0.001));
  });

235
  testWidgets('pressedOpacity defaults to 0.1', (WidgetTester tester) async {
236
    await tester.pumpWidget(boilerplate(child: CupertinoButton(
237
      child: const Text('Tap me'),
238 239 240 241
      onPressed: () { },
    )));

    // Keep a "down" gesture on the button
242
    final Offset center = tester.getCenter(find.byType(CupertinoButton));
243 244 245 246
    await tester.startGesture(center);
    await tester.pumpAndSettle();

    // Check opacity
247
    final FadeTransition opacity = tester.widget(find.descendant(
248
      of: find.byType(CupertinoButton),
249
      matching: find.byType(FadeTransition),
250
    ));
251
    expect(opacity.opacity.value, 0.4);
252 253 254
  });

  testWidgets('pressedOpacity parameter', (WidgetTester tester) async {
255
    const double pressedOpacity = 0.5;
256
    await tester.pumpWidget(boilerplate(child: CupertinoButton(
257
      pressedOpacity: pressedOpacity,
258
      child: const Text('Tap me'),
259 260 261 262
      onPressed: () { },
    )));

    // Keep a "down" gesture on the button
263
    final Offset center = tester.getCenter(find.byType(CupertinoButton));
264 265 266 267
    await tester.startGesture(center);
    await tester.pumpAndSettle();

    // Check opacity
268
    final FadeTransition opacity = tester.widget(find.descendant(
269
      of: find.byType(CupertinoButton),
270
      matching: find.byType(FadeTransition),
271
    ));
272
    expect(opacity.opacity.value, pressedOpacity);
273
  });
274 275

  testWidgets('Cupertino button is semantically a button', (WidgetTester tester) async {
276
    final SemanticsTester semantics = SemanticsTester(tester);
277 278
    await tester.pumpWidget(
      boilerplate(
279 280
          child: Center(
            child: CupertinoButton(
281
              onPressed: () { },
282
              child: const Text('ABC'),
283 284 285 286 287 288
            ),
          ),
      ),
    );

    expect(semantics, hasSemantics(
289
      TestSemantics.root(
290
        children: <TestSemantics>[
291
          TestSemantics.rootChild(
292 293
            actions: SemanticsAction.tap.index,
            label: 'ABC',
294
            flags: SemanticsFlag.isButton.index,
295
          ),
296 297 298 299 300 301 302 303 304
        ],
      ),
      ignoreId: true,
      ignoreRect: true,
      ignoreTransform: true,
    ));

    semantics.dispose();
  });
305 306

  testWidgets('Can specify colors', (WidgetTester tester) async {
307
    await tester.pumpWidget(boilerplate(child: CupertinoButton(
308 309
      color: const Color(0x000000FF),
      disabledColor: const Color(0x0000FF00),
310
      onPressed: () { },
311
      child: const Text('Skeuomorph me'),
312 313 314
    )));

    BoxDecoration boxDecoration = tester.widget<DecoratedBox>(
315 316
      find.widgetWithText(DecoratedBox, 'Skeuomorph me'),
    ).decoration as BoxDecoration;
317

318
    expect(boxDecoration.color, const Color(0x000000FF));
319 320

    await tester.pumpWidget(boilerplate(child: const CupertinoButton(
321 322
      color: Color(0x000000FF),
      disabledColor: Color(0x0000FF00),
323
      onPressed: null,
324
      child: Text('Skeuomorph me'),
325 326 327
    )));

    boxDecoration = tester.widget<DecoratedBox>(
328 329
      find.widgetWithText(DecoratedBox, 'Skeuomorph me'),
    ).decoration as BoxDecoration;
330

331
    expect(boxDecoration.color, const Color(0x0000FF00));
332
  });
xster's avatar
xster committed
333

334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
  testWidgets('Can specify dynamic colors', (WidgetTester tester) async {
    const Color bgColor = CupertinoDynamicColor.withBrightness(
      color: Color(0xFF123456),
      darkColor: Color(0xFF654321),
    );

    const Color inactive = CupertinoDynamicColor.withBrightness(
      color: Color(0xFF111111),
      darkColor: Color(0xFF222222),
    );

    await tester.pumpWidget(
      MediaQuery(
        data: const MediaQueryData(platformBrightness: Brightness.dark),
        child: boilerplate(child: CupertinoButton(
          color: bgColor,
          disabledColor: inactive,
          onPressed: () { },
352
          child: const Text('Skeuomorph me'),
353
        )),
354 355 356 357
      ),
    );

    BoxDecoration boxDecoration = tester.widget<DecoratedBox>(
358
      find.widgetWithText(DecoratedBox, 'Skeuomorph me'),
359
    ).decoration as BoxDecoration;
360

361
    expect(boxDecoration.color!.value, 0xFF654321);
362 363 364

    await tester.pumpWidget(
      MediaQuery(
365
        data: const MediaQueryData(),
366 367 368 369
        child: boilerplate(child: const CupertinoButton(
          color: bgColor,
          disabledColor: inactive,
          onPressed: null,
370
          child: Text('Skeuomorph me'),
371
        )),
372 373 374 375
      ),
    );

    boxDecoration = tester.widget<DecoratedBox>(
376
      find.widgetWithText(DecoratedBox, 'Skeuomorph me'),
377
    ).decoration as BoxDecoration;
378 379

    // Disabled color.
380
    expect(boxDecoration.color!.value, 0xFF111111);
381 382
  });

383
  testWidgets('Button respects themes', (WidgetTester tester) async {
384
    late TextStyle textStyle;
xster's avatar
xster committed
385 386 387 388

    await tester.pumpWidget(
      CupertinoApp(
        home: CupertinoButton(
389
          onPressed: () { },
xster's avatar
xster committed
390
          child: Builder(builder: (BuildContext context) {
Alexandre Ardhuin's avatar
Alexandre Ardhuin committed
391
            textStyle = DefaultTextStyle.of(context).style;
xster's avatar
xster committed
392 393 394 395 396 397 398 399 400 401 402
            return const Placeholder();
          }),
        ),
      ),
    );

    expect(textStyle.color, CupertinoColors.activeBlue);

    await tester.pumpWidget(
      CupertinoApp(
        home: CupertinoButton.filled(
403
          onPressed: () { },
xster's avatar
xster committed
404
          child: Builder(builder: (BuildContext context) {
Alexandre Ardhuin's avatar
Alexandre Ardhuin committed
405
            textStyle = DefaultTextStyle.of(context).style;
xster's avatar
xster committed
406 407 408 409 410 411
            return const Placeholder();
          }),
        ),
      ),
    );

412
    expect(textStyle.color, isSameColorAs(CupertinoColors.white));
xster's avatar
xster committed
413 414 415
    BoxDecoration decoration = tester.widget<DecoratedBox>(
      find.descendant(
        of: find.byType(CupertinoButton),
416
        matching: find.byType(DecoratedBox),
417
      ),
418
    ).decoration as BoxDecoration;
xster's avatar
xster committed
419 420 421 422 423 424
    expect(decoration.color, CupertinoColors.activeBlue);

    await tester.pumpWidget(
      CupertinoApp(
        theme: const CupertinoThemeData(brightness: Brightness.dark),
        home: CupertinoButton(
425
          onPressed: () { },
xster's avatar
xster committed
426
          child: Builder(builder: (BuildContext context) {
Alexandre Ardhuin's avatar
Alexandre Ardhuin committed
427
            textStyle = DefaultTextStyle.of(context).style;
xster's avatar
xster committed
428 429 430 431 432
            return const Placeholder();
          }),
        ),
      ),
    );
433
    expect(textStyle.color, isSameColorAs(CupertinoColors.systemBlue.darkColor));
xster's avatar
xster committed
434 435 436 437 438

    await tester.pumpWidget(
      CupertinoApp(
        theme: const CupertinoThemeData(brightness: Brightness.dark),
        home: CupertinoButton.filled(
439
          onPressed: () { },
xster's avatar
xster committed
440
          child: Builder(builder: (BuildContext context) {
Alexandre Ardhuin's avatar
Alexandre Ardhuin committed
441
            textStyle = DefaultTextStyle.of(context).style;
xster's avatar
xster committed
442 443 444 445 446
            return const Placeholder();
          }),
        ),
      ),
    );
447
    expect(textStyle.color, isSameColorAs(CupertinoColors.black));
xster's avatar
xster committed
448 449 450
    decoration = tester.widget<DecoratedBox>(
      find.descendant(
        of: find.byType(CupertinoButton),
451
        matching: find.byType(DecoratedBox),
452
      ),
453
    ).decoration as BoxDecoration;
454
    expect(decoration.color, isSameColorAs(CupertinoColors.systemBlue.darkColor));
xster's avatar
xster committed
455
  });
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481

  testWidgets('Hovering over Cupertino button updates cursor to clickable on Web', (WidgetTester tester) async {
    await tester.pumpWidget(
      CupertinoApp(
        home: Center(
          child: CupertinoButton.filled(
            onPressed: () { },
            child: const Text('Tap me'),
          ),
        ),
      ),
    );

    final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
    await gesture.addPointer(location: const Offset(10, 10));
    await tester.pumpAndSettle();
    expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);

    final Offset button = tester.getCenter(find.byType(CupertinoButton));
    await gesture.moveTo(button);
    await tester.pumpAndSettle();
    expect(
      RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
      kIsWeb ? SystemMouseCursors.click : SystemMouseCursors.basic,
    );
  });
482
}
Ian Hickson's avatar
Ian Hickson committed
483

484
Widget boilerplate({ required Widget child }) {
485
  return Directionality(
Ian Hickson's avatar
Ian Hickson committed
486
    textDirection: TextDirection.ltr,
487
    child: Center(child: child),
Ian Hickson's avatar
Ian Hickson committed
488
  );
489
}