theme_test.dart 27.9 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 'dart:ui' as ui;
xster's avatar
xster committed
6
import 'package:flutter/cupertino.dart';
7
import 'package:flutter/material.dart';
8
import 'package:flutter/rendering.dart';
9
import 'package:flutter/src/foundation/diagnostics.dart';
10 11 12
import 'package:flutter_test/flutter_test.dart';

void main() {
13 14
  const TextTheme defaultGeometryTheme = Typography.englishLike2014;

15
  test('ThemeDataTween control test', () {
16
    final ThemeData light = ThemeData.light();
17
    final ThemeData dark = ThemeData.dark();
18
    final ThemeDataTween tween = ThemeDataTween(begin: light, end: dark);
19 20 21
    expect(tween.lerp(0.25), equals(ThemeData.lerp(light, dark, 0.25)));
  });

22
  testWidgets('PopupMenu inherits app theme', (WidgetTester tester) async {
23
    final Key popupMenuButtonKey = UniqueKey();
24
    await tester.pumpWidget(
25 26 27 28
      MaterialApp(
        theme: ThemeData(brightness: Brightness.dark),
        home: Scaffold(
          appBar: AppBar(
29
            actions: <Widget>[
30
              PopupMenuButton<String>(
31 32 33
                key: popupMenuButtonKey,
                itemBuilder: (BuildContext context) {
                  return <PopupMenuItem<String>>[
34
                    const PopupMenuItem<String>(child: Text('menuItem')),
35
                  ];
36
                },
37
              ),
38 39 40
            ],
          ),
        ),
41
      ),
42 43 44 45 46
    );

    await tester.tap(find.byKey(popupMenuButtonKey));
    await tester.pump(const Duration(seconds: 1));

47
    expect(Theme.of(tester.element(find.text('menuItem'))).brightness, equals(Brightness.dark));
48 49
  });

50
  testWidgets('Fallback theme', (WidgetTester tester) async {
51
    late BuildContext capturedContext;
52
    await tester.pumpWidget(
53
      Builder(
54 55
        builder: (BuildContext context) {
          capturedContext = context;
56
          return Container();
57 58 59 60
        }
      )
    );

61
    expect(Theme.of(capturedContext), equals(ThemeData.localize(ThemeData.fallback(), defaultGeometryTheme)));
62 63
  });

64
  testWidgets('ThemeData.localize memoizes the result', (WidgetTester tester) async {
65 66
    final ThemeData light = ThemeData.light();
    final ThemeData dark = ThemeData.dark();
67 68 69

    // Same input, same output.
    expect(
70 71
      ThemeData.localize(light, defaultGeometryTheme),
      same(ThemeData.localize(light, defaultGeometryTheme)),
72 73 74 75
    );

    // Different text geometry, different output.
    expect(
76 77
      ThemeData.localize(light, defaultGeometryTheme),
      isNot(same(ThemeData.localize(light, Typography.tall2014))),
78 79 80 81
    );

    // Different base theme, different output.
    expect(
82 83
      ThemeData.localize(light, defaultGeometryTheme),
      isNot(same(ThemeData.localize(dark, defaultGeometryTheme))),
84 85 86
    );
  });

87 88
  testWidgets('PopupMenu inherits shadowed app theme', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/5572
89
    final Key popupMenuButtonKey = UniqueKey();
90
    await tester.pumpWidget(
91 92 93 94 95 96
      MaterialApp(
        theme: ThemeData(brightness: Brightness.dark),
        home: Theme(
          data: ThemeData(brightness: Brightness.light),
          child: Scaffold(
            appBar: AppBar(
97
              actions: <Widget>[
98
                PopupMenuButton<String>(
99 100 101
                  key: popupMenuButtonKey,
                  itemBuilder: (BuildContext context) {
                    return <PopupMenuItem<String>>[
102
                      const PopupMenuItem<String>(child: Text('menuItem')),
103
                    ];
104
                  },
105
                ),
106 107 108 109
              ],
            ),
          ),
        ),
110
      ),
111 112 113 114 115
    );

    await tester.tap(find.byKey(popupMenuButtonKey));
    await tester.pump(const Duration(seconds: 1));

116
    expect(Theme.of(tester.element(find.text('menuItem'))).brightness, equals(Brightness.light));
117 118 119
  });

  testWidgets('DropdownMenu inherits shadowed app theme', (WidgetTester tester) async {
120
    final Key dropdownMenuButtonKey = UniqueKey();
121
    await tester.pumpWidget(
122 123 124 125 126 127
      MaterialApp(
        theme: ThemeData(brightness: Brightness.dark),
        home: Theme(
          data: ThemeData(brightness: Brightness.light),
          child: Scaffold(
            appBar: AppBar(
128
              actions: <Widget>[
129
                DropdownButton<String>(
130
                  key: dropdownMenuButtonKey,
131
                  onChanged: (String? newValue) { },
132
                  value: 'menuItem',
133
                  items: const <DropdownMenuItem<String>>[
134
                    DropdownMenuItem<String>(
135
                      value: 'menuItem',
136
                      child: Text('menuItem'),
137 138
                    ),
                  ],
139 140 141 142 143
                ),
              ],
            ),
          ),
        ),
144
      ),
145 146 147 148 149
    );

    await tester.tap(find.byKey(dropdownMenuButtonKey));
    await tester.pump(const Duration(seconds: 1));

150
    for (final Element item in tester.elementList(find.text('menuItem')))
151
      expect(Theme.of(item).brightness, equals(Brightness.light));
152 153 154 155
  });

  testWidgets('ModalBottomSheet inherits shadowed app theme', (WidgetTester tester) async {
    await tester.pumpWidget(
156 157 158 159 160 161 162
      MaterialApp(
        theme: ThemeData(brightness: Brightness.dark),
        home: Theme(
          data: ThemeData(brightness: Brightness.light),
          child: Scaffold(
            body: Center(
              child: Builder(
163
                builder: (BuildContext context) {
164
                  return ElevatedButton(
165
                    onPressed: () {
166
                      showModalBottomSheet<void>(
167
                        context: context,
168
                        builder: (BuildContext context) => const Text('bottomSheet'),
169 170
                      );
                    },
171
                    child: const Text('SHOW'),
172 173
                  );
                }
174 175 176 177
              ),
            ),
          ),
        ),
178
      ),
179 180 181 182
    );

    await tester.tap(find.text('SHOW'));
    await tester.pump(const Duration(seconds: 1));
183
    expect(Theme.of(tester.element(find.text('bottomSheet'))).brightness, equals(Brightness.light));
184 185 186 187 188 189

    await tester.tap(find.text('bottomSheet')); // dismiss the bottom sheet
    await tester.pump(const Duration(seconds: 1));
  });

  testWidgets('Dialog inherits shadowed app theme', (WidgetTester tester) async {
190
    final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
191
    await tester.pumpWidget(
192 193 194 195 196
      MaterialApp(
        theme: ThemeData(brightness: Brightness.dark),
        home: Theme(
          data: ThemeData(brightness: Brightness.light),
          child: Scaffold(
197
            key: scaffoldKey,
198 199
            body: Center(
              child: Builder(
200
                builder: (BuildContext context) {
201
                  return ElevatedButton(
202
                    onPressed: () {
203
                      showDialog<void>(
204
                        context: context,
205
                        builder: (BuildContext context) => const Text('dialog'),
206 207
                      );
                    },
208
                    child: const Text('SHOW'),
209 210
                  );
                }
211 212 213 214
              ),
            ),
          ),
        ),
215
      ),
216 217 218 219
    );

    await tester.tap(find.text('SHOW'));
    await tester.pump(const Duration(seconds: 1));
220
    expect(Theme.of(tester.element(find.text('dialog'))).brightness, equals(Brightness.light));
221 222
  });

223
  testWidgets("Scaffold inherits theme's scaffoldBackgroundColor", (WidgetTester tester) async {
224
    const Color green = Color(0xFF00FF00);
225 226

    await tester.pumpWidget(
227 228 229 230 231
      MaterialApp(
        theme: ThemeData(scaffoldBackgroundColor: green),
        home: Scaffold(
          body: Center(
            child: Builder(
232
              builder: (BuildContext context) {
233
                return GestureDetector(
234
                  onTap: () {
235
                    showDialog<void>(
236
                      context: context,
237 238
                      builder: (BuildContext context) {
                        return const Scaffold(
239
                          body: SizedBox(
240 241 242 243 244
                            width: 200.0,
                            height: 200.0,
                          ),
                        );
                      },
245 246
                    );
                  },
247
                  child: const Text('SHOW'),
248 249 250 251 252
                );
              },
            ),
          ),
        ),
253
      ),
254 255 256 257 258
    );

    await tester.tap(find.text('SHOW'));
    await tester.pump(const Duration(seconds: 1));

259
    final List<Material> materials = tester.widgetList<Material>(find.byType(Material)).toList();
260 261 262 263
    expect(materials.length, equals(2));
    expect(materials[0].color, green); // app scaffold
    expect(materials[1].color, green); // dialog scaffold
  });
264 265 266

  testWidgets('IconThemes are applied', (WidgetTester tester) async {
    await tester.pumpWidget(
267 268
      MaterialApp(
        theme: ThemeData(iconTheme: const IconThemeData(color: Colors.green, size: 10.0)),
269
        home: const Icon(Icons.computer),
270
      ),
271 272 273 274
    );

    RenderParagraph glyphText = tester.renderObject(find.byType(RichText));

275 276
    expect(glyphText.text.style!.color, Colors.green);
    expect(glyphText.text.style!.fontSize, 10.0);
277 278

    await tester.pumpWidget(
279 280
      MaterialApp(
        theme: ThemeData(iconTheme: const IconThemeData(color: Colors.orange, size: 20.0)),
281 282 283 284 285 286 287
        home: const Icon(Icons.computer),
      ),
    );
    await tester.pump(const Duration(milliseconds: 100)); // Halfway through the theme transition

    glyphText = tester.renderObject(find.byType(RichText));

288 289
    expect(glyphText.text.style!.color, Color.lerp(Colors.green, Colors.orange, 0.5));
    expect(glyphText.text.style!.fontSize, 15.0);
290 291 292 293

    await tester.pump(const Duration(milliseconds: 100)); // Finish the transition
    glyphText = tester.renderObject(find.byType(RichText));

294 295
    expect(glyphText.text.style!.color, Colors.orange);
    expect(glyphText.text.style!.fontSize, 20.0);
296
  });
297 298

  testWidgets(
Ian Hickson's avatar
Ian Hickson committed
299
    'Same ThemeData reapplied does not trigger descendants rebuilds',
300 301
    (WidgetTester tester) async {
      testBuildCalled = 0;
302
      ThemeData themeData = ThemeData(primaryColor: const Color(0xFF000000));
303

304
      Widget buildTheme() {
305
        return Theme(
306 307
          data: themeData,
          child: const Test(),
308 309 310 311
        );
      }

      await tester.pumpWidget(buildTheme());
312 313 314
      expect(testBuildCalled, 1);

      // Pump the same widgets again.
315
      await tester.pumpWidget(buildTheme());
316 317 318 319
      // No repeated build calls to the child since it's the same theme data.
      expect(testBuildCalled, 1);

      // New instance of theme data but still the same content.
320
      themeData = ThemeData(primaryColor: const Color(0xFF000000));
321
      await tester.pumpWidget(buildTheme());
322 323 324 325
      // Still no repeated calls.
      expect(testBuildCalled, 1);

      // Different now.
326
      themeData = ThemeData(primaryColor: const Color(0xFF222222));
327
      await tester.pumpWidget(buildTheme());
328 329 330 331
      // Should call build again.
      expect(testBuildCalled, 2);
    },
  );
332 333 334

  testWidgets('Text geometry set in Theme has higher precedence than that of Localizations', (WidgetTester tester) async {
    const double _kMagicFontSize = 4321.0;
335
    final ThemeData fallback = ThemeData.fallback();
336 337
    final ThemeData customTheme = fallback.copyWith(
      primaryTextTheme: fallback.primaryTextTheme.copyWith(
338
        bodyText2: fallback.primaryTextTheme.bodyText2!.copyWith(
339
          fontSize: _kMagicFontSize,
340
        ),
341 342
      ),
    );
343
    expect(customTheme.primaryTextTheme.bodyText2!.fontSize, _kMagicFontSize);
344

345
    late double actualFontSize;
346
    await tester.pumpWidget(Directionality(
347
      textDirection: TextDirection.ltr,
348
      child: Theme(
349
        data: customTheme,
350
        child: Builder(builder: (BuildContext context) {
351
          final ThemeData theme = Theme.of(context);
352
          actualFontSize = theme.primaryTextTheme.bodyText2!.fontSize!;
353
          return Text(
354
            'A',
355
            style: theme.primaryTextTheme.bodyText2,
356 357 358 359 360 361 362
          );
        }),
      ),
    ));

    expect(actualFontSize, _kMagicFontSize);
  });
363 364

  testWidgets('Default Theme provides all basic TextStyle properties', (WidgetTester tester) async {
365
    late ThemeData theme;
366
    await tester.pumpWidget(Directionality(
367
      textDirection: TextDirection.ltr,
368
      child: Builder(
369
        builder: (BuildContext context) {
370
          theme = Theme.of(context);
371 372 373 374 375 376 377
          return const Text('A');
        },
      ),
    ));

    List<TextStyle> extractStyles(TextTheme textTheme) {
      return <TextStyle>[
378 379 380 381 382 383 384 385 386 387 388
        textTheme.headline1!,
        textTheme.headline2!,
        textTheme.headline3!,
        textTheme.headline4!,
        textTheme.headline5!,
        textTheme.headline6!,
        textTheme.subtitle1!,
        textTheme.bodyText1!,
        textTheme.bodyText2!,
        textTheme.caption!,
        textTheme.button!,
389 390 391
      ];
    }

392 393
    for (final TextTheme textTheme in <TextTheme>[theme.textTheme, theme.primaryTextTheme, theme.accentTextTheme]) {
      for (final TextStyle style in extractStyles(textTheme).map<TextStyle>((TextStyle style) => _TextStyleProxy(style))) {
394 395 396 397 398 399 400 401 402 403 404 405 406 407
        expect(style.inherit, false);
        expect(style.color, isNotNull);
        expect(style.fontFamily, isNotNull);
        expect(style.fontSize, isNotNull);
        expect(style.fontWeight, isNotNull);
        expect(style.fontStyle, null);
        expect(style.letterSpacing, null);
        expect(style.wordSpacing, null);
        expect(style.textBaseline, isNotNull);
        expect(style.height, null);
        expect(style.decoration, TextDecoration.none);
        expect(style.decorationColor, null);
        expect(style.decorationStyle, null);
        expect(style.debugLabel, isNotNull);
408 409
        expect(style.locale, null);
        expect(style.background, null);
410 411 412
      }
    }

413
    expect(theme.textTheme.headline1!.debugLabel, '(englishLike display4 2014).merge(blackMountainView headline1)');
414
  });
xster's avatar
xster committed
415 416

  group('Cupertino theme', () {
417 418 419 420
    late int buildCount;
    CupertinoThemeData? actualTheme;
    IconThemeData? actualIconTheme;
    BuildContext? context;
xster's avatar
xster committed
421 422

    final Widget singletonThemeSubtree = Builder(
423
      builder: (BuildContext localContext) {
xster's avatar
xster committed
424
        buildCount++;
425 426 427
        actualTheme = CupertinoTheme.of(localContext);
        actualIconTheme = IconTheme.of(localContext);
        context = localContext;
xster's avatar
xster committed
428 429 430 431 432
        return const Placeholder();
      },
    );

    Future<CupertinoThemeData> testTheme(WidgetTester tester, ThemeData theme) async {
433
      await tester.pumpWidget(Theme(data: theme, child: singletonThemeSubtree));
434
      return actualTheme!;
xster's avatar
xster committed
435 436 437 438 439
    }

    setUp(() {
      buildCount = 0;
      actualTheme = null;
440
      actualIconTheme = null;
441
      context = null;
xster's avatar
xster committed
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
    });

    testWidgets('Default theme has defaults', (WidgetTester tester) async {
      final CupertinoThemeData theme = await testTheme(tester, ThemeData.light());

      expect(theme.brightness, Brightness.light);
      expect(theme.primaryColor, Colors.blue);
      expect(theme.scaffoldBackgroundColor, Colors.grey[50]);
      expect(theme.primaryContrastingColor, Colors.white);
      expect(theme.textTheme.textStyle.fontFamily, '.SF Pro Text');
      expect(theme.textTheme.textStyle.fontSize, 17.0);
    });

    testWidgets('Dark theme has defaults', (WidgetTester tester) async {
      final CupertinoThemeData theme = await testTheme(tester, ThemeData.dark());

      expect(theme.brightness, Brightness.dark);
      expect(theme.primaryColor, Colors.blue);
      expect(theme.primaryContrastingColor, Colors.white);
      expect(theme.scaffoldBackgroundColor, Colors.grey[850]);
      expect(theme.textTheme.textStyle.fontFamily, '.SF Pro Text');
      expect(theme.textTheme.textStyle.fontSize, 17.0);
    });

466 467
    testWidgets('MaterialTheme overrides the brightness', (WidgetTester tester) async {
      await testTheme(tester, ThemeData.dark());
468
      expect(CupertinoTheme.brightnessOf(context!), Brightness.dark);
469 470

      await testTheme(tester, ThemeData.light());
471
      expect(CupertinoTheme.brightnessOf(context!), Brightness.light);
472 473 474 475 476 477

      // Overridable by cupertinoOverrideTheme.
      await testTheme(tester, ThemeData(
        brightness: Brightness.light,
        cupertinoOverrideTheme: const CupertinoThemeData(brightness: Brightness.dark),
      ));
478
      expect(CupertinoTheme.brightnessOf(context!), Brightness.dark);
479 480 481 482 483

      await testTheme(tester, ThemeData(
        brightness: Brightness.dark,
        cupertinoOverrideTheme: const CupertinoThemeData(brightness: Brightness.light),
      ));
484
      expect(CupertinoTheme.brightnessOf(context!), Brightness.light);
485 486
    });

xster's avatar
xster committed
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532
    testWidgets('Can override material theme', (WidgetTester tester) async {
      final CupertinoThemeData theme = await testTheme(tester, ThemeData(
        cupertinoOverrideTheme: const CupertinoThemeData(
          scaffoldBackgroundColor: CupertinoColors.lightBackgroundGray,
        ),
      ));

      expect(theme.brightness, Brightness.light);
      // We took the scaffold background override but the rest are still cascaded
      // to the material theme.
      expect(theme.primaryColor, Colors.blue);
      expect(theme.primaryContrastingColor, Colors.white);
      expect(theme.scaffoldBackgroundColor, CupertinoColors.lightBackgroundGray);
      expect(theme.textTheme.textStyle.fontFamily, '.SF Pro Text');
      expect(theme.textTheme.textStyle.fontSize, 17.0);
    });

    testWidgets('Can override properties that are independent of material', (WidgetTester tester) async {
      final CupertinoThemeData theme = await testTheme(tester, ThemeData(
        cupertinoOverrideTheme: const CupertinoThemeData(
          // The bar colors ignore all things material except brightness.
          barBackgroundColor: CupertinoColors.black,
        ),
      ));

      expect(theme.primaryColor, Colors.blue);
      // MaterialBasedCupertinoThemeData should also function like a normal CupertinoThemeData.
      expect(theme.barBackgroundColor, CupertinoColors.black);
    });

    testWidgets('Changing material theme triggers rebuilds', (WidgetTester tester) async {
      CupertinoThemeData theme = await testTheme(tester, ThemeData(
        primarySwatch: Colors.red,
      ));

      expect(buildCount, 1);
      expect(theme.primaryColor, Colors.red);

      theme = await testTheme(tester, ThemeData(
        primarySwatch: Colors.orange,
      ));

      expect(buildCount, 2);
      expect(theme.primaryColor, Colors.orange);
    });

533 534 535 536 537 538 539
    testWidgets("CupertinoThemeData does not override material theme's icon theme",
      (WidgetTester tester) async {
        const Color materialIconColor = Colors.blue;
        const Color cupertinoIconColor = Colors.black;

        await testTheme(tester, ThemeData(
            iconTheme: const IconThemeData(color: materialIconColor),
540
            cupertinoOverrideTheme: const CupertinoThemeData(primaryColor: cupertinoIconColor),
541 542 543
        ));

        expect(buildCount, 1);
544
        expect(actualIconTheme!.color, materialIconColor);
545 546
    });

xster's avatar
xster committed
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
    testWidgets(
      'Changing cupertino theme override triggers rebuilds',
      (WidgetTester tester) async {
        CupertinoThemeData theme = await testTheme(tester, ThemeData(
          primarySwatch: Colors.purple,
          cupertinoOverrideTheme: const CupertinoThemeData(
            primaryColor: CupertinoColors.activeOrange,
          ),
        ));

        expect(buildCount, 1);
        expect(theme.primaryColor, CupertinoColors.activeOrange);

        theme = await testTheme(tester, ThemeData(
          primarySwatch: Colors.purple,
          cupertinoOverrideTheme: const CupertinoThemeData(
            primaryColor: CupertinoColors.activeGreen,
          ),
        ));

        expect(buildCount, 2);
        expect(theme.primaryColor, CupertinoColors.activeGreen);
      },
    );

    testWidgets(
      'Cupertino theme override blocks derivative changes',
      (WidgetTester tester) async {
        CupertinoThemeData theme = await testTheme(tester, ThemeData(
          primarySwatch: Colors.purple,
          cupertinoOverrideTheme: const CupertinoThemeData(
            primaryColor: CupertinoColors.activeOrange,
          ),
        ));

        expect(buildCount, 1);
        expect(theme.primaryColor, CupertinoColors.activeOrange);

        // Change the upstream material primary color.
        theme = await testTheme(tester, ThemeData(
          primarySwatch: Colors.blue,
          cupertinoOverrideTheme: const CupertinoThemeData(
            // But the primary material color is preempted by the override.
            primaryColor: CupertinoColors.activeOrange,
          ),
        ));

        expect(buildCount, 2);
        expect(theme.primaryColor, CupertinoColors.activeOrange);
      },
    );

    testWidgets(
      'Cupertino overrides do not block derivatives triggering rebuilds when derivatives are not overridden',
      (WidgetTester tester) async {
        CupertinoThemeData theme = await testTheme(tester, ThemeData(
          primarySwatch: Colors.purple,
          cupertinoOverrideTheme: const CupertinoThemeData(
            primaryContrastingColor: CupertinoColors.destructiveRed,
          ),
        ));

        expect(buildCount, 1);
        expect(theme.textTheme.actionTextStyle.color, Colors.purple);
        expect(theme.primaryContrastingColor, CupertinoColors.destructiveRed);

        theme = await testTheme(tester, ThemeData(
          primarySwatch: Colors.green,
          cupertinoOverrideTheme: const CupertinoThemeData(
            primaryContrastingColor: CupertinoColors.destructiveRed,
          ),
        ));

        expect(buildCount, 2);
        expect(theme.textTheme.actionTextStyle.color, Colors.green);
        expect(theme.primaryContrastingColor, CupertinoColors.destructiveRed);
      },
    );

    testWidgets(
      'copyWith only copies the overrides, not the material or cupertino derivatives',
      (WidgetTester tester) async {
        final CupertinoThemeData originalTheme = await testTheme(tester, ThemeData(
          primarySwatch: Colors.purple,
          cupertinoOverrideTheme: const CupertinoThemeData(
            primaryContrastingColor: CupertinoColors.activeOrange,
          ),
        ));

        final CupertinoThemeData copiedTheme = originalTheme.copyWith(
          barBackgroundColor: CupertinoColors.destructiveRed,
        );

        final CupertinoThemeData theme = await testTheme(tester, ThemeData(
          primarySwatch: Colors.blue,
          cupertinoOverrideTheme: copiedTheme,
        ));

        expect(theme.primaryColor, Colors.blue);
        expect(theme.primaryContrastingColor, CupertinoColors.activeOrange);
        expect(theme.barBackgroundColor, CupertinoColors.destructiveRed);
      },
    );

    testWidgets(
      "Material themes with no cupertino overrides can also be copyWith'ed",
      (WidgetTester tester) async {
        final CupertinoThemeData originalTheme = await testTheme(tester, ThemeData(
          primarySwatch: Colors.purple,
        ));

        final CupertinoThemeData copiedTheme = originalTheme.copyWith(
          primaryContrastingColor: CupertinoColors.destructiveRed,
        );

        final CupertinoThemeData theme = await testTheme(tester, ThemeData(
          primarySwatch: Colors.blue,
          cupertinoOverrideTheme: copiedTheme,
        ));

        expect(theme.primaryColor, Colors.blue);
        expect(theme.primaryContrastingColor, CupertinoColors.destructiveRed);
      },
    );
  });
672 673
}

674
int testBuildCalled = 0;
675
class Test extends StatefulWidget {
676
  const Test({ Key? key }) : super(key: key);
677 678

  @override
679
  _TestState createState() => _TestState();
680 681 682 683 684 685
}

class _TestState extends State<Test> {
  @override
  Widget build(BuildContext context) {
    testBuildCalled += 1;
686 687
    return Container(
      decoration: BoxDecoration(
688
        color: Theme.of(context).primaryColor,
689 690 691
      ),
    );
  }
692
}
693 694 695 696 697 698 699 700 701 702

/// This class exists only to make sure that we test all the properties of the
/// [TextStyle] class. If a property is added/removed/renamed, the analyzer will
/// complain that this class has incorrect overrides.
class _TextStyleProxy implements TextStyle {
  _TextStyleProxy(this._delegate);

  final TextStyle _delegate;

  // Do make sure that all the properties correctly forward to the _delegate.
703
  @override
704
  Color? get color => _delegate.color;
705
  @override
706
  Color? get backgroundColor => _delegate.backgroundColor;
707
  @override
708
  String? get debugLabel => _delegate.debugLabel;
709
  @override
710
  TextDecoration? get decoration => _delegate.decoration;
711
  @override
712
  Color? get decorationColor => _delegate.decorationColor;
713
  @override
714
  TextDecorationStyle? get decorationStyle => _delegate.decorationStyle;
715
  @override
716
  double? get decorationThickness => _delegate.decorationThickness;
717
  @override
718
  String? get fontFamily => _delegate.fontFamily;
719
  @override
720
  List<String>? get fontFamilyFallback => _delegate.fontFamilyFallback;
721
  @override
722
  double? get fontSize => _delegate.fontSize;
723
  @override
724
  FontStyle? get fontStyle => _delegate.fontStyle;
725
  @override
726
  FontWeight? get fontWeight => _delegate.fontWeight;
727
  @override
728
  double? get height => _delegate.height;
729
  @override
730
  Locale? get locale => _delegate.locale;
731
  @override
732
  ui.Paint? get foreground => _delegate.foreground;
733
  @override
734
  ui.Paint? get background => _delegate.background;
735 736 737
  @override
  bool get inherit => _delegate.inherit;
  @override
738
  double? get letterSpacing => _delegate.letterSpacing;
739
  @override
740
  TextBaseline? get textBaseline => _delegate.textBaseline;
741
  @override
742
  double? get wordSpacing => _delegate.wordSpacing;
743
  @override
744
  List<Shadow>? get shadows => _delegate.shadows;
745
  @override
746
  List<ui.FontFeature>? get fontFeatures => _delegate.fontFeatures;
747

748
  @override
749
  String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) =>
750 751
      super.toString();

752
  @override
753
  DiagnosticsNode toDiagnosticsNode({ String? name, DiagnosticsTreeStyle? style }) {
754
    throw UnimplementedError();
755 756 757 758
  }

  @override
  String toStringShort() {
759
    throw UnimplementedError();
760 761 762
  }

  @override
763
  TextStyle apply({
764 765 766 767 768
    Color? color,
    Color? backgroundColor,
    TextDecoration? decoration,
    Color? decorationColor,
    TextDecorationStyle? decorationStyle,
769 770
    double decorationThicknessFactor = 1.0,
    double decorationThicknessDelta = 0.0,
771 772
    String? fontFamily,
    List<String>? fontFamilyFallback,
773 774 775
    double fontSizeFactor = 1.0,
    double fontSizeDelta = 0.0,
    int fontWeightDelta = 0,
776
    FontStyle? fontStyle,
777 778 779 780 781
    double letterSpacingFactor = 1.0,
    double letterSpacingDelta = 0.0,
    double wordSpacingFactor = 1.0,
    double wordSpacingDelta = 0.0,
    double heightFactor = 1.0,
782
    double heightDelta = 0.0,
783 784 785 786
    TextBaseline? textBaseline,
    Locale? locale,
    List<ui.Shadow>? shadows,
    List<ui.FontFeature>? fontFeatures,
787
  }) {
788
    throw UnimplementedError();
789 790 791 792
  }

  @override
  RenderComparison compareTo(TextStyle other) {
793
    throw UnimplementedError();
794 795 796
  }

  @override
797
  TextStyle copyWith({
798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819
    bool? inherit,
    Color? color,
    Color? backgroundColor,
    String? fontFamily,
    List<String>? fontFamilyFallback,
    double? fontSize,
    FontWeight? fontWeight,
    FontStyle? fontStyle,
    double? letterSpacing,
    double? wordSpacing,
    TextBaseline? textBaseline,
    double? height,
    Locale? locale,
    ui.Paint? foreground,
    ui.Paint? background,
    List<Shadow>? shadows,
    List<ui.FontFeature>? fontFeatures,
    TextDecoration? decoration,
    Color? decorationColor,
    TextDecorationStyle? decorationStyle,
    double? decorationThickness,
    String? debugLabel,
820
  }) {
821
    throw UnimplementedError();
822 823 824
  }

  @override
825
  void debugFillProperties(DiagnosticPropertiesBuilder properties, { String prefix = '' }) {
826
    throw UnimplementedError();
827 828 829
  }

  @override
830
  ui.ParagraphStyle getParagraphStyle({
831 832
    TextAlign? textAlign,
    TextDirection? textDirection,
833
    double textScaleFactor = 1.0,
834 835 836 837 838 839 840 841 842 843
    String? ellipsis,
    int? maxLines,
    ui.TextHeightBehavior? textHeightBehavior,
    Locale? locale,
    String? fontFamily,
    double? fontSize,
    FontWeight? fontWeight,
    FontStyle? fontStyle,
    double? height,
    StrutStyle? strutStyle,
844
  }) {
845
    throw UnimplementedError();
846 847 848
  }

  @override
849
  ui.TextStyle getTextStyle({ double textScaleFactor = 1.0 }) {
850
    throw UnimplementedError();
851 852 853
  }

  @override
854
  TextStyle merge(TextStyle? other) {
855
    throw UnimplementedError();
856 857
  }
}