button_test.dart 14.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
import 'package:flutter/rendering.dart';
7 8 9
import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';

10 11
import '../widgets/semantics_tester.dart';

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

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

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

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

104 105 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
  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);
  });

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

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

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

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

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

197 198 199 200 201 202 203 204 205 206 207
  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));

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

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

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

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

224 225 226 227 228
    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));
229 230 231 232
    transition = tester.firstWidget(find.byType(FadeTransition));
    expect(transition.opacity.value, moreOrLessEquals(1.0, epsilon: 0.001));
  });

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

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

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

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

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

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

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

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

    semantics.dispose();
  });
303 304

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

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

316
    expect(boxDecoration.color, const Color(0x000000FF));
317 318

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

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

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

332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
  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: () { },
350
          child: const Text('Skeuomorph me'),
351
        )),
352 353 354 355
      ),
    );

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

359
    expect(boxDecoration.color!.value, 0xFF654321);
360 361 362

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

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

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

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

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

    expect(textStyle.color, CupertinoColors.activeBlue);

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

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

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

    await tester.pumpWidget(
      CupertinoApp(
        theme: const CupertinoThemeData(brightness: Brightness.dark),
        home: CupertinoButton.filled(
437
          onPressed: () { },
xster's avatar
xster committed
438
          child: Builder(builder: (BuildContext context) {
Alexandre Ardhuin's avatar
Alexandre Ardhuin committed
439
            textStyle = DefaultTextStyle.of(context).style;
xster's avatar
xster committed
440 441 442 443 444
            return const Placeholder();
          }),
        ),
      ),
    );
445
    expect(textStyle.color, isSameColorAs(CupertinoColors.black));
xster's avatar
xster committed
446 447 448
    decoration = tester.widget<DecoratedBox>(
      find.descendant(
        of: find.byType(CupertinoButton),
449
        matching: find.byType(DecoratedBox),
450
      ),
451
    ).decoration as BoxDecoration;
452
    expect(decoration.color, isSameColorAs(CupertinoColors.systemBlue.darkColor));
xster's avatar
xster committed
453
  });
454
}
Ian Hickson's avatar
Ian Hickson committed
455

456
Widget boilerplate({ required Widget child }) {
457
  return Directionality(
Ian Hickson's avatar
Ian Hickson committed
458
    textDirection: TextDirection.ltr,
459
    child: Center(child: child),
Ian Hickson's avatar
Ian Hickson committed
460
  );
461
}