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

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

void main() {
  test('ButtonThemeData defaults', () {
12
    const ButtonThemeData theme = ButtonThemeData();
13 14 15 16
    expect(theme.textTheme, ButtonTextTheme.normal);
    expect(theme.constraints, const BoxConstraints(minWidth: 88.0, minHeight: 36.0));
    expect(theme.padding, const EdgeInsets.symmetric(horizontal: 16.0));
    expect(theme.shape, const RoundedRectangleBorder(
17
      borderRadius: BorderRadius.all(Radius.circular(2.0)),
18
    ));
19
    expect(theme.alignedDropdown, false);
20
    expect(theme.layoutBehavior, ButtonBarLayoutBehavior.padded);
21 22 23
  });

  test('ButtonThemeData default overrides', () {
24
    const ButtonThemeData theme = ButtonThemeData(
25 26 27 28
      textTheme: ButtonTextTheme.primary,
      minWidth: 100.0,
      height: 200.0,
      padding: EdgeInsets.zero,
29
      shape: RoundedRectangleBorder(),
30
      alignedDropdown: true,
31 32 33 34 35
    );
    expect(theme.textTheme, ButtonTextTheme.primary);
    expect(theme.constraints, const BoxConstraints(minWidth: 100.0, minHeight: 200.0));
    expect(theme.padding, EdgeInsets.zero);
    expect(theme.shape, const RoundedRectangleBorder());
36
    expect(theme.alignedDropdown, true);
37 38 39
  });

  testWidgets('ButtonTheme defaults', (WidgetTester tester) async {
40 41 42 43 44 45 46
    late ButtonTextTheme textTheme;
    late ButtonBarLayoutBehavior layoutBehavior;
    late BoxConstraints constraints;
    late EdgeInsets padding;
    late ShapeBorder shape;
    late bool alignedDropdown;
    late ColorScheme colorScheme;
47 48

    await tester.pumpWidget(
49 50
      ButtonTheme(
        child: Builder(
51 52 53 54
          builder: (BuildContext context) {
            final ButtonThemeData theme = ButtonTheme.of(context);
            textTheme = theme.textTheme;
            constraints = theme.constraints;
55
            padding = theme.padding as EdgeInsets;
56
            shape = theme.shape;
57
            layoutBehavior = theme.layoutBehavior;
58
            colorScheme = theme.colorScheme!;
59
            alignedDropdown = theme.alignedDropdown;
60
            return Container(
61
              alignment: Alignment.topLeft,
62
              child: Directionality(
63
                textDirection: TextDirection.ltr,
64
                child: FlatButton(
65 66
                  onPressed: () { },
                  child: const Text('b'), // intrinsic width < minimum width
67 68 69 70 71 72 73 74 75
                ),
              ),
            );
          },
        ),
      ),
    );

    expect(textTheme, ButtonTextTheme.normal);
76
    expect(layoutBehavior, ButtonBarLayoutBehavior.padded);
77 78 79
    expect(constraints, const BoxConstraints(minWidth: 88.0, minHeight: 36.0));
    expect(padding, const EdgeInsets.symmetric(horizontal: 16.0));
    expect(shape, const RoundedRectangleBorder(
80
      borderRadius: BorderRadius.all(Radius.circular(2.0)),
81
    ));
82
    expect(alignedDropdown, false);
83
    expect(colorScheme, ThemeData.light().colorScheme);
84 85 86 87
    expect(tester.widget<Material>(find.byType(Material)).shape, shape);
    expect(tester.getSize(find.byType(Material)), const Size(88.0, 36.0));
  });

88 89 90
  test('ButtonThemeData.copyWith', () {
    ButtonThemeData theme = const ButtonThemeData().copyWith();
    expect(theme.textTheme, ButtonTextTheme.normal);
91
    expect(theme.layoutBehavior, ButtonBarLayoutBehavior.padded);
92 93 94
    expect(theme.constraints, const BoxConstraints(minWidth: 88.0, minHeight: 36.0));
    expect(theme.padding, const EdgeInsets.symmetric(horizontal: 16.0));
    expect(theme.shape, const RoundedRectangleBorder(
95
      borderRadius: BorderRadius.all(Radius.circular(2.0)),
96 97
    ));
    expect(theme.alignedDropdown, false);
98
    expect(theme.colorScheme, null);
99 100 101

    theme = const ButtonThemeData().copyWith(
      textTheme: ButtonTextTheme.primary,
102
      layoutBehavior: ButtonBarLayoutBehavior.constrained,
103 104 105 106 107
      minWidth: 100.0,
      height: 200.0,
      padding: EdgeInsets.zero,
      shape: const StadiumBorder(),
      alignedDropdown: true,
108 109
      colorScheme: const ColorScheme.dark(),
      materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
110 111
    );
    expect(theme.textTheme, ButtonTextTheme.primary);
112
    expect(theme.layoutBehavior, ButtonBarLayoutBehavior.constrained);
113 114 115 116
    expect(theme.constraints, const BoxConstraints(minWidth: 100.0, minHeight: 200.0));
    expect(theme.padding, EdgeInsets.zero);
    expect(theme.shape, const StadiumBorder());
    expect(theme.alignedDropdown, true);
117
    expect(theme.colorScheme, const ColorScheme.dark());
118 119
  });

120
  testWidgets('Theme buttonTheme defaults', (WidgetTester tester) async {
121
    final ThemeData lightTheme = ThemeData.light();
122 123 124 125
    late ButtonTextTheme textTheme;
    late BoxConstraints constraints;
    late EdgeInsets padding;
    late ShapeBorder shape;
126

127
    const Color disabledColor = Color(0xFF00FF00);
128
    await tester.pumpWidget(
129
      Theme(
130
        data: lightTheme.copyWith(
131 132
          disabledColor: disabledColor, // disabled RaisedButton fill color
          buttonTheme: const ButtonThemeData(disabledColor: disabledColor),
133
          textTheme: lightTheme.textTheme.copyWith(
134
            button: lightTheme.textTheme.button!.copyWith(
135 136 137 138 139 140
              // The button's height will match because there's no
              // vertical padding by default
              fontSize: 48.0,
            ),
          ),
        ),
141
        child: Builder(
142 143 144 145
          builder: (BuildContext context) {
            final ButtonThemeData theme = ButtonTheme.of(context);
            textTheme = theme.textTheme;
            constraints = theme.constraints;
146
            padding = theme.padding as EdgeInsets;
147
            shape = theme.shape;
148
            return Container(
149 150 151
              alignment: Alignment.topLeft,
              child: const Directionality(
                textDirection: TextDirection.ltr,
152
                child: RaisedButton(
153
                  onPressed: null,
154
                  child: Text('b'), // intrinsic width < minimum width
155 156 157 158 159 160 161 162 163 164 165 166
                ),
              ),
            );
          },
        ),
      ),
    );

    expect(textTheme, ButtonTextTheme.normal);
    expect(constraints, const BoxConstraints(minWidth: 88.0, minHeight: 36.0));
    expect(padding, const EdgeInsets.symmetric(horizontal: 16.0));
    expect(shape, const RoundedRectangleBorder(
167
      borderRadius: BorderRadius.all(Radius.circular(2.0)),
168 169 170
    ));

    expect(tester.widget<Material>(find.byType(Material)).shape, shape);
171
    expect(tester.widget<Material>(find.byType(Material)).color, disabledColor);
172
    expect(tester.getSize(find.byType(Material)), const Size(88.0, 48.0));
173
  });
174 175

  testWidgets('Theme buttonTheme ButtonTheme overrides', (WidgetTester tester) async {
176 177 178 179
    late ButtonTextTheme textTheme;
    late BoxConstraints constraints;
    late EdgeInsets padding;
    late ShapeBorder shape;
180 181

    await tester.pumpWidget(
182 183
      Theme(
        data: ThemeData.light().copyWith(
184 185
          buttonColor: const Color(0xFF00FF00), // enabled RaisedButton fill color
        ),
186
        child: ButtonTheme(
187 188 189 190
          textTheme: ButtonTextTheme.primary,
          minWidth: 100.0,
          height: 200.0,
          padding: EdgeInsets.zero,
191
          buttonColor: const Color(0xFF00FF00), // enabled RaisedButton fill color
192
          shape: const RoundedRectangleBorder(),
193
          child: Builder(
194 195 196 197
            builder: (BuildContext context) {
              final ButtonThemeData theme = ButtonTheme.of(context);
              textTheme = theme.textTheme;
              constraints = theme.constraints;
198
              padding = theme.padding as EdgeInsets;
199
              shape = theme.shape;
200
              return Container(
201
                alignment: Alignment.topLeft,
202
                child: Directionality(
203
                  textDirection: TextDirection.ltr,
204
                  child: RaisedButton(
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
                    onPressed: () { },
                    child: const Text('b'), // intrinsic width < minimum width
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );

    expect(textTheme, ButtonTextTheme.primary);
    expect(constraints, const BoxConstraints(minWidth: 100.0, minHeight: 200.0));
    expect(padding, EdgeInsets.zero);
    expect(shape, const RoundedRectangleBorder());

    expect(tester.widget<Material>(find.byType(Material)).shape, shape);
    expect(tester.widget<Material>(find.byType(Material)).color, const Color(0xFF00FF00));
    expect(tester.getSize(find.byType(Material)), const Size(100.0, 200.0));
  });
225 226

  testWidgets('ButtonTheme alignedDropdown', (WidgetTester tester) async {
227
    final Key dropdownKey = UniqueKey();
228

229
    Widget buildFrame({ required bool alignedDropdown, required TextDirection textDirection }) {
230
      return MaterialApp(
231
        builder: (BuildContext context, Widget? child) {
232
          return Directionality(
233
            textDirection: textDirection,
234
            child: child!,
235 236
          );
        },
237
        home: ButtonTheme(
238
          alignedDropdown: alignedDropdown,
239 240
          child: Material(
            child: Builder(
241
              builder: (BuildContext context) {
242
                return Container(
243
                  alignment: Alignment.center,
244
                  child: DropdownButtonHideUnderline(
245
                    child: SizedBox(
246
                      width: 200.0,
247
                      child: DropdownButton<String>(
248
                        key: dropdownKey,
249
                        onChanged: (String? value) { },
250 251
                        value: 'foo',
                        items: const <DropdownMenuItem<String>>[
252
                          DropdownMenuItem<String>(
253
                            value: 'foo',
254
                            child: Text('foo'),
255
                          ),
256
                          DropdownMenuItem<String>(
257
                            value: 'bar',
258
                            child: Text('bar'),
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
                          ),
                        ],
                      ),
                    ),
                  ),
                );
              },
            ),
          ),
        ),
      );
    }

    final Finder button = find.byKey(dropdownKey);
    final Finder menu = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DropdownMenu<String>');

    await tester.pumpWidget(
      buildFrame(
        alignedDropdown: false,
        textDirection: TextDirection.ltr,
      ),
    );
    await tester.tap(button);
    await tester.pumpAndSettle();

    // 240 = 200.0 (button width) + _kUnalignedMenuMargin (20.0 left and right)
    expect(tester.getSize(button).width, 200.0);
    expect(tester.getSize(menu).width, 240.0);

    // Dismiss the menu.
    await tester.tapAt(Offset.zero);
    await tester.pumpAndSettle();
    expect(menu, findsNothing);

    await tester.pumpWidget(
      buildFrame(
        alignedDropdown: true,
        textDirection: TextDirection.ltr,
      ),
    );
    await tester.tap(button);
    await tester.pumpAndSettle();

    // Aligneddropdown: true means the button and menu widths match
    expect(tester.getSize(button).width, 200.0);
    expect(tester.getSize(menu).width, 200.0);

    // There are two 'foo' widgets: the selected menu item's label and the drop
    // down button's label. The should both appear at the same location.
    final Finder fooText = find.text('foo');
    expect(fooText, findsNWidgets(2));
    expect(tester.getRect(fooText.at(0)), tester.getRect(fooText.at(1)));

    // Dismiss the menu.
    await tester.tapAt(Offset.zero);
    await tester.pumpAndSettle();
    expect(menu, findsNothing);

317
    // Same test as above except RTL
318 319 320 321 322 323 324 325 326 327 328 329
    await tester.pumpWidget(
      buildFrame(
        alignedDropdown: true,
        textDirection: TextDirection.rtl,
      ),
    );
    await tester.tap(button);
    await tester.pumpAndSettle();

    expect(fooText, findsNWidgets(2));
    expect(tester.getRect(fooText.at(0)), tester.getRect(fooText.at(1)));
  });
330 331 332 333

  testWidgets('button theme with stateful color changes color in states', (WidgetTester tester) async {
    final FocusNode focusNode = FocusNode();

334 335 336 337
    const Color pressedColor = Color(0x00000001);
    const Color hoverColor = Color(0x00000002);
    const Color focusedColor = Color(0x00000003);
    const Color defaultColor = Color(0x00000004);
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365

    Color getTextColor(Set<MaterialState> states) {
      if (states.contains(MaterialState.pressed)) {
        return pressedColor;
      }
      if (states.contains(MaterialState.hovered)) {
        return hoverColor;
      }
      if (states.contains(MaterialState.focused)) {
        return focusedColor;
      }
      return defaultColor;
    }

    const ColorScheme colorScheme = ColorScheme.light();

    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: Center(
            child: ButtonTheme(
              colorScheme: colorScheme.copyWith(
                primary: MaterialStateColor.resolveWith(getTextColor),
              ),
              textTheme: ButtonTextTheme.primary,
              child: FlatButton(
                onPressed: () {},
                focusNode: focusNode,
366
                child: const Text('FlatButton'),
367 368 369 370 371 372 373 374
              ),
            ),
          ),
        ),
      ),
    );

    Color textColor() {
375
      return tester.renderObject<RenderParagraph>(find.text('FlatButton')).text.style!.color!;
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
    }

    // Default, not disabled.
    expect(textColor(), equals(defaultColor));

    // Focused.
    focusNode.requestFocus();
    await tester.pumpAndSettle();
    expect(textColor(), focusedColor);

    // Hovered.
    final Offset center = tester.getCenter(find.byType(FlatButton));
    final TestGesture gesture = await tester.createGesture(
      kind: PointerDeviceKind.mouse,
    );
391
    await gesture.addPointer(location: Offset.zero);
392
    addTearDown(gesture.removePointer);
393 394 395 396 397 398 399 400 401 402 403
    await gesture.moveTo(center);
    await tester.pumpAndSettle();
    expect(textColor(), hoverColor);

    // Highlighted (pressed).
    await gesture.down(center);
    await tester.pump(); // Start the splash and highlight animations.
    await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way.
    expect(textColor(), pressedColor);
  },
  );
404
}