// Copyright 2014 The Flutter 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/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; int buildCount = 0; CupertinoThemeData? actualTheme; IconThemeData? actualIconTheme; final Widget singletonThemeSubtree = Builder( builder: (BuildContext context) { buildCount++; actualTheme = CupertinoTheme.of(context); actualIconTheme = IconTheme.of(context); return const Placeholder(); }, ); Future testTheme(WidgetTester tester, CupertinoThemeData theme) async { await tester.pumpWidget(CupertinoTheme(data: theme, child: singletonThemeSubtree)); return actualTheme!; } Future testIconTheme(WidgetTester tester, CupertinoThemeData theme) async { await tester.pumpWidget(CupertinoTheme(data: theme, child: singletonThemeSubtree)); return actualIconTheme!; } void main() { setUp(() { buildCount = 0; actualTheme = null; actualIconTheme = null; }); testWidgets('Default theme has defaults', (WidgetTester tester) async { final CupertinoThemeData theme = await testTheme(tester, const CupertinoThemeData()); expect(theme.brightness, isNull); expect(theme.primaryColor, CupertinoColors.activeBlue); expect(theme.textTheme.textStyle.fontSize, 17.0); expect(theme.applyThemeToAll, false); }); testWidgets('Theme attributes cascade', (WidgetTester tester) async { final CupertinoThemeData theme = await testTheme( tester, const CupertinoThemeData(primaryColor: CupertinoColors.systemRed), ); expect(theme.textTheme.actionTextStyle.color, isSameColorAs(CupertinoColors.systemRed.color)); }); testWidgets('Dependent attribute can be overridden from cascaded value', ( WidgetTester tester, ) async { final CupertinoThemeData theme = await testTheme( tester, const CupertinoThemeData( brightness: Brightness.dark, textTheme: CupertinoTextThemeData(textStyle: TextStyle(color: CupertinoColors.black)), ), ); // The brightness still cascaded down to the background color. expect(theme.scaffoldBackgroundColor, isSameColorAs(CupertinoColors.black)); // But not to the font color which we overrode. expect(theme.textTheme.textStyle.color, isSameColorAs(CupertinoColors.black)); }); testWidgets('Reading themes creates dependencies', (WidgetTester tester) async { // Reading the theme creates a dependency. CupertinoThemeData theme = await testTheme( tester, const CupertinoThemeData( // Default brightness is light, barBackgroundColor: Color(0x11223344), textTheme: CupertinoTextThemeData(textStyle: TextStyle(fontFamily: 'Skeuomorphic')), ), ); expect(buildCount, 1); expect(theme.textTheme.textStyle.fontFamily, 'Skeuomorphic'); // Changing another property also triggers a rebuild. theme = await testTheme( tester, const CupertinoThemeData( brightness: Brightness.light, barBackgroundColor: Color(0x11223344), textTheme: CupertinoTextThemeData(textStyle: TextStyle(fontFamily: 'Skeuomorphic')), ), ); expect(buildCount, 2); // Re-reading the same value doesn't change anything. expect(theme.textTheme.textStyle.fontFamily, 'Skeuomorphic'); theme = await testTheme( tester, const CupertinoThemeData( brightness: Brightness.light, barBackgroundColor: Color(0x11223344), textTheme: CupertinoTextThemeData(textStyle: TextStyle(fontFamily: 'Flat')), ), ); expect(buildCount, 3); expect(theme.textTheme.textStyle.fontFamily, 'Flat'); }); testWidgets('copyWith works', (WidgetTester tester) async { const originalTheme = CupertinoThemeData(brightness: Brightness.dark, applyThemeToAll: true); final CupertinoThemeData theme = await testTheme( tester, originalTheme.copyWith(primaryColor: CupertinoColors.systemGreen, applyThemeToAll: false), ); expect(theme.brightness, Brightness.dark); expect(theme.primaryColor, isSameColorAs(CupertinoColors.systemGreen.darkColor)); // Now check calculated derivatives. expect( theme.textTheme.actionTextStyle.color, isSameColorAs(CupertinoColors.systemGreen.darkColor), ); expect(theme.scaffoldBackgroundColor, isSameColorAs(CupertinoColors.black)); expect(theme.applyThemeToAll, false); }); testWidgets("Theme has default IconThemeData, which is derived from the theme's primary color", ( WidgetTester tester, ) async { const CupertinoDynamicColor primaryColor = CupertinoColors.systemRed; const themeData = CupertinoThemeData(primaryColor: primaryColor); final IconThemeData resultingIconTheme = await testIconTheme(tester, themeData); expect(resultingIconTheme.color, isSameColorAs(primaryColor)); // Works in dark mode if primaryColor is a CupertinoDynamicColor. final Color darkColor = (await testIconTheme( tester, themeData.copyWith(brightness: Brightness.dark), )).color!; expect(darkColor, isSameColorAs(primaryColor.darkColor)); }); testWidgets('IconTheme.of creates a dependency on iconTheme', (WidgetTester tester) async { IconThemeData iconTheme = await testIconTheme( tester, const CupertinoThemeData(primaryColor: CupertinoColors.destructiveRed), ); expect(buildCount, 1); expect(iconTheme.color, CupertinoColors.destructiveRed); iconTheme = await testIconTheme( tester, const CupertinoThemeData(primaryColor: CupertinoColors.activeOrange), ); expect(buildCount, 2); expect(iconTheme.color, CupertinoColors.activeOrange); }); testWidgets('CupertinoTheme diagnostics', (WidgetTester tester) async { final builder = DiagnosticPropertiesBuilder(); const CupertinoThemeData().debugFillProperties(builder); final Set description = builder.properties .map((DiagnosticsNode node) => node.name.toString()) .toSet(); expect( setEquals(description, { 'brightness', 'primaryColor', 'primaryContrastingColor', 'barBackgroundColor', 'scaffoldBackgroundColor', 'applyThemeToAll', 'textStyle', 'actionTextStyle', 'actionSmallTextStyle', 'tabLabelTextStyle', 'navTitleTextStyle', 'navLargeTitleTextStyle', 'navActionTextStyle', 'pickerTextStyle', 'dateTimePickerTextStyle', 'selectionHandleColor', }), isTrue, ); }); testWidgets('CupertinoTheme.toStringDeep uses single-line style', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/47651. expect( const CupertinoTheme( data: CupertinoThemeData(primaryColor: CupertinoColors.transparent), child: SizedBox(), ).toStringDeep().trimRight(), isNot(contains('\n')), ); }); testWidgets('CupertinoThemeData equality', (WidgetTester tester) async { const a = CupertinoThemeData(brightness: Brightness.dark); final CupertinoThemeData b = a.copyWith(); final CupertinoThemeData c = a.copyWith(brightness: Brightness.light); expect(a, equals(b)); expect(b, equals(a)); expect(a, isNot(equals(c))); expect(c, isNot(equals(a))); expect(b, isNot(equals(c))); expect(c, isNot(equals(b))); }); testWidgets('NoDefaultCupertinoThemeData equality', (WidgetTester tester) async { const a = NoDefaultCupertinoThemeData(); final NoDefaultCupertinoThemeData b = a.copyWith(); final NoDefaultCupertinoThemeData c = a.copyWith(brightness: Brightness.light); expect(a, equals(b)); expect(a, isNot(c)); }); late Brightness currentBrightness; void colorMatches(Color? componentColor, Color expectedDynamicColor) { if (expectedDynamicColor is CupertinoDynamicColor) { switch (currentBrightness) { case Brightness.light: expect(componentColor, isSameColorAs(expectedDynamicColor.color)); case Brightness.dark: expect(componentColor, isSameColorAs(expectedDynamicColor.darkColor)); } } else { expect(componentColor, isSameColorAs(expectedDynamicColor)); } } void dynamicColorsTestGroup() { testWidgets('CupertinoTheme.of resolves colors', (WidgetTester tester) async { final data = CupertinoThemeData( brightness: currentBrightness, primaryColor: CupertinoColors.systemRed, ); final CupertinoThemeData theme = await testTheme(tester, data); expect(data.primaryColor, isSameColorAs(CupertinoColors.systemRed)); colorMatches(theme.primaryColor, CupertinoColors.systemRed); }); testWidgets('CupertinoTheme.of resolves default values', (WidgetTester tester) async { const CupertinoDynamicColor primaryColor = CupertinoColors.systemRed; final data = CupertinoThemeData(brightness: currentBrightness, primaryColor: primaryColor); const barBackgroundColor = CupertinoDynamicColor.withBrightness( color: Color(0xF0F9F9F9), darkColor: Color(0xF01D1D1D), ); final CupertinoThemeData theme = await testTheme(tester, data); colorMatches(theme.primaryContrastingColor, CupertinoColors.white); colorMatches(theme.barBackgroundColor, barBackgroundColor); colorMatches(theme.scaffoldBackgroundColor, CupertinoColors.systemBackground); colorMatches(theme.selectionHandleColor, CupertinoColors.systemBlue); colorMatches(theme.textTheme.textStyle.color, CupertinoColors.label); colorMatches(theme.textTheme.actionTextStyle.color, primaryColor); colorMatches(theme.textTheme.tabLabelTextStyle.color, CupertinoColors.inactiveGray); colorMatches(theme.textTheme.navTitleTextStyle.color, CupertinoColors.label); colorMatches(theme.textTheme.navLargeTitleTextStyle.color, CupertinoColors.label); colorMatches(theme.textTheme.navActionTextStyle.color, primaryColor); colorMatches(theme.textTheme.pickerTextStyle.color, CupertinoColors.label); colorMatches(theme.textTheme.dateTimePickerTextStyle.color, CupertinoColors.label); }); } currentBrightness = Brightness.light; group('light colors', dynamicColorsTestGroup); currentBrightness = Brightness.dark; group('dark colors', dynamicColorsTestGroup); }