runThemeTest function

  1. @visibleForTesting
void runThemeTest(
  1. ThemeData themeData, {
  2. ThemeAccessibilityLevel accessibilityLevel = ThemeAccessibilityLevel.normal,
})

Scans through given themeData, looking for insufficient contrast values between text and background colors.

Throws an AssertionError if any text-background combo's contrast ratio is lower than minimum required value for given accessibilityLevel.

Example error message:

Contrast ratio of primaryColorLight (Color(0xffbbdefb))
and primaryTextTheme.bodyLarge?.color (Color(0xffffffff)) is 1.40:1,
which is not sufficient for specified ThemeAccessibilityLevel.normal.
ThemeAccessibilityLevel.normal's lowest acceptable contrast ratio is 4.5:1.

Implementation

@visibleForTesting
void runThemeTest(
  ThemeData themeData, {
  ThemeAccessibilityLevel accessibilityLevel = ThemeAccessibilityLevel.normal,
}) {
  final backgroundColors = <String, Color?>{
    'backgroundColor': themeData.backgroundColor,
    'dialogBackgroundColor': themeData.dialogBackgroundColor,
    'scaffoldBackgroundColor': themeData.scaffoldBackgroundColor,
    'cardColor': themeData.cardColor,
    'bottomAppBarColor': themeData.bottomAppBarColor,
    'canvasColor': themeData.canvasColor,
    'selectedRowColor': themeData.selectedRowColor,
    'textSelectionTheme.selectionColor':
        themeData.textSelectionTheme.selectionColor,
    'textTheme.bodyLarge?.backgroundColor':
        themeData.textTheme.bodyLarge?.backgroundColor,
    'textTheme.bodyMedium?.backgroundColor':
        themeData.textTheme.bodyMedium?.backgroundColor,
    'textTheme.bodySmall?.backgroundColor':
        themeData.textTheme.bodySmall?.backgroundColor,
    'textTheme.bodyText1?.backgroundColor':
        themeData.textTheme.bodyText1?.backgroundColor,
    'textTheme.bodyText2?.backgroundColor':
        themeData.textTheme.bodyText2?.backgroundColor,
    'textTheme.button?.backgroundColor':
        themeData.textTheme.button?.backgroundColor,
    'textTheme.caption?.backgroundColor':
        themeData.textTheme.caption?.backgroundColor,
    'textTheme.displayLarge?.backgroundColor':
        themeData.textTheme.displayLarge?.backgroundColor,
    'textTheme.displayMedium?.backgroundColor':
        themeData.textTheme.displayMedium?.backgroundColor,
    'textTheme.displaySmall?.backgroundColor':
        themeData.textTheme.displaySmall?.backgroundColor,
    'textTheme.headline1?.backgroundColor':
        themeData.textTheme.headline1?.backgroundColor,
    'textTheme.headline2?.backgroundColor':
        themeData.textTheme.headline2?.backgroundColor,
    'textTheme.headline3?.backgroundColor':
        themeData.textTheme.headline3?.backgroundColor,
    'textTheme.headline4?.backgroundColor':
        themeData.textTheme.headline4?.backgroundColor,
    'textTheme.headline5?.backgroundColor':
        themeData.textTheme.headline5?.backgroundColor,
    'textTheme.headline6?.backgroundColor':
        themeData.textTheme.headline6?.backgroundColor,
    'textTheme.headlineLarge?.backgroundColor':
        themeData.textTheme.headlineLarge?.backgroundColor,
    'textTheme.headlineMedium?.backgroundColor':
        themeData.textTheme.headlineMedium?.backgroundColor,
    'textTheme.headlineSmall?.backgroundColor':
        themeData.textTheme.headlineSmall?.backgroundColor,
    'textTheme.labelLarge?.backgroundColor':
        themeData.textTheme.labelLarge?.backgroundColor,
    'textTheme.labelMedium?.backgroundColor':
        themeData.textTheme.labelMedium?.backgroundColor,
    'textTheme.labelSmall?.backgroundColor':
        themeData.textTheme.labelSmall?.backgroundColor,
    'textTheme.overline?.backgroundColor':
        themeData.textTheme.overline?.backgroundColor,
    'textTheme.subtitle1?.backgroundColor':
        themeData.textTheme.subtitle1?.backgroundColor,
    'textTheme.subtitle2?.backgroundColor':
        themeData.textTheme.subtitle2?.backgroundColor,
    'textTheme.titleLarge?.backgroundColor':
        themeData.textTheme.titleLarge?.backgroundColor,
    'textTheme.titleMedium?.backgroundColor':
        themeData.textTheme.titleMedium?.backgroundColor,
    'textTheme.titleSmall?.backgroundColor':
        themeData.textTheme.titleSmall?.backgroundColor,
    'textButtonTheme.style?.textStyle': themeData
        .textButtonTheme.style?.textStyle
        ?.resolve(MaterialState.values.toSet())
        ?.backgroundColor,
  };

  final textColors = <String, Color?>{
    'hintColor': themeData.hintColor,
    'errorColor': themeData.errorColor,
    'textTheme.bodyLarge?.color': themeData.textTheme.bodyLarge?.color,
    'textTheme.bodyMedium?.color': themeData.textTheme.bodyMedium?.color,
    'textTheme.bodySmall?.color': themeData.textTheme.bodySmall?.color,
    'textTheme.bodyText1?.color': themeData.textTheme.bodyText1?.color,
    'textTheme.bodyText2?.color': themeData.textTheme.bodyText2?.color,
    'textTheme.button?.color': themeData.textTheme.button?.color,
    'textTheme.caption?.color': themeData.textTheme.caption?.color,
    'textTheme.displayLarge?.color': themeData.textTheme.displayLarge?.color,
    'textTheme.displayMedium?.color': themeData.textTheme.displayMedium?.color,
    'textTheme.displaySmall?.color': themeData.textTheme.displaySmall?.color,
    'textTheme.headline1?.color': themeData.textTheme.headline1?.color,
    'textTheme.headline2?.color': themeData.textTheme.headline2?.color,
    'textTheme.headline3?.color': themeData.textTheme.headline3?.color,
    'textTheme.headline4?.color': themeData.textTheme.headline4?.color,
    'textTheme.headline5?.color': themeData.textTheme.headline5?.color,
    'textTheme.headline6?.color': themeData.textTheme.headline6?.color,
    'textTheme.headlineLarge?.color': themeData.textTheme.headlineLarge?.color,
    'textTheme.headlineMedium?.color':
        themeData.textTheme.headlineMedium?.color,
    'textTheme.headlineSmall?.color': themeData.textTheme.headlineSmall?.color,
    'textTheme.labelLarge?.color': themeData.textTheme.labelLarge?.color,
    'textTheme.labelMedium?.color': themeData.textTheme.labelMedium?.color,
    'textTheme.labelSmall?.color': themeData.textTheme.labelSmall?.color,
    'textTheme.overline?.color': themeData.textTheme.overline?.color,
    'textTheme.subtitle1?.color': themeData.textTheme.subtitle1?.color,
    'textTheme.subtitle2?.color': themeData.textTheme.subtitle2?.color,
    'textTheme.titleLarge?.color': themeData.textTheme.titleLarge?.color,
    'textTheme.titleMedium?.color': themeData.textTheme.titleMedium?.color,
    'textTheme.titleSmall?.color': themeData.textTheme.titleSmall?.color,
    'textButtonTheme.style?.textStyle': themeData
        .textButtonTheme.style?.textStyle
        ?.resolve(MaterialState.values.toSet())
        ?.color,
  };

  for (final backgroundColor in backgroundColors.entries) {
    for (final textColor in textColors.entries) {
      if (backgroundColor.value == null || textColor.value == null) {
        continue;
      }
      final contrast =
          calculateContrast(backgroundColor.value!, textColor.value!);
      final doesMatchMinContrast = doesMatchMinContrastRatio(
        contrast,
        readabilityLevel: accessibilityLevel.readabilityLevel,
      );
      assert(
        doesMatchMinContrast == true,
        'Contrast ratio of ${backgroundColor.key} (${backgroundColor.value}) and ${textColor.key} (${textColor.value}) '
        'is $contrast:1, which is not sufficient for specified $accessibilityLevel. '
        '$accessibilityLevel\'s lowest acceptable contrast ratio is ${accessibilityLevel.readabilityLevel.minimumAcceptableContrastRatio}:1.',
      );
    }
  }

  final primaryColors = <String, Color?>{
    'primaryColor': themeData.primaryColor,
    'primaryColorLight': themeData.primaryColorLight,
    'primaryColorDark': themeData.primaryColorDark,
    'primaryTextTheme.bodyLarge?.backgroundColor':
        themeData.primaryTextTheme.bodyLarge?.backgroundColor,
    'primaryTextTheme.bodyMedium?.backgroundColor':
        themeData.primaryTextTheme.bodyMedium?.backgroundColor,
    'primaryTextTheme.bodySmall?.backgroundColor':
        themeData.primaryTextTheme.bodySmall?.backgroundColor,
    'primaryTextTheme.bodyText1?.backgroundColor':
        themeData.primaryTextTheme.bodyText1?.backgroundColor,
    'primaryTextTheme.bodyText2?.backgroundColor':
        themeData.primaryTextTheme.bodyText2?.backgroundColor,
    'primaryTextTheme.button?.backgroundColor':
        themeData.primaryTextTheme.button?.backgroundColor,
    'primaryTextTheme.caption?.backgroundColor':
        themeData.primaryTextTheme.caption?.backgroundColor,
    'primaryTextTheme.displayLarge?.backgroundColor':
        themeData.primaryTextTheme.displayLarge?.backgroundColor,
    'primaryTextTheme.displayMedium?.backgroundColor':
        themeData.primaryTextTheme.displayMedium?.backgroundColor,
    'primaryTextTheme.displaySmall?.backgroundColor':
        themeData.primaryTextTheme.displaySmall?.backgroundColor,
    'primaryTextTheme.headline1?.backgroundColor':
        themeData.primaryTextTheme.headline1?.backgroundColor,
    'primaryTextTheme.headline2?.backgroundColor':
        themeData.primaryTextTheme.headline2?.backgroundColor,
    'primaryTextTheme.headline3?.backgroundColor':
        themeData.primaryTextTheme.headline3?.backgroundColor,
    'primaryTextTheme.headline4?.backgroundColor':
        themeData.primaryTextTheme.headline4?.backgroundColor,
    'primaryTextTheme.headline5?.backgroundColor':
        themeData.primaryTextTheme.headline5?.backgroundColor,
    'primaryTextTheme.headline6?.backgroundColor':
        themeData.primaryTextTheme.headline6?.backgroundColor,
    'primaryTextTheme.headlineLarge?.backgroundColor':
        themeData.primaryTextTheme.headlineLarge?.backgroundColor,
    'primaryTextTheme.headlineMedium?.backgroundColor':
        themeData.primaryTextTheme.headlineMedium?.backgroundColor,
    'primaryTextTheme.headlineSmall?.backgroundColor':
        themeData.primaryTextTheme.headlineSmall?.backgroundColor,
    'primaryTextTheme.labelLarge?.backgroundColor':
        themeData.primaryTextTheme.labelLarge?.backgroundColor,
    'primaryTextTheme.labelMedium?.backgroundColor':
        themeData.primaryTextTheme.labelMedium?.backgroundColor,
    'primaryTextTheme.labelSmall?.backgroundColor':
        themeData.primaryTextTheme.labelSmall?.backgroundColor,
    'primaryTextTheme.overline?.backgroundColor':
        themeData.primaryTextTheme.overline?.backgroundColor,
    'primaryTextTheme.subtitle1?.backgroundColor':
        themeData.primaryTextTheme.subtitle1?.backgroundColor,
    'primaryTextTheme.subtitle2?.backgroundColor':
        themeData.primaryTextTheme.subtitle2?.backgroundColor,
    'primaryTextTheme.titleLarge?.backgroundColor':
        themeData.primaryTextTheme.titleLarge?.backgroundColor,
    'primaryTextTheme.titleMedium?.backgroundColor':
        themeData.primaryTextTheme.titleMedium?.backgroundColor,
    'primaryTextTheme.titleSmall?.backgroundColor':
        themeData.primaryTextTheme.titleSmall?.backgroundColor,
  };

  final primaryTextColors = <String, Color?>{
    'primaryTextTheme.bodyLarge?.color':
        themeData.primaryTextTheme.bodyLarge?.color,
    'primaryTextTheme.bodyMedium?.color':
        themeData.primaryTextTheme.bodyMedium?.color,
    'primaryTextTheme.bodySmall?.color':
        themeData.primaryTextTheme.bodySmall?.color,
    'primaryTextTheme.bodyText1?.color':
        themeData.primaryTextTheme.bodyText1?.color,
    'primaryTextTheme.bodyText2?.color':
        themeData.primaryTextTheme.bodyText2?.color,
    'primaryTextTheme.button?.color': themeData.primaryTextTheme.button?.color,
    'primaryTextTheme.caption?.color':
        themeData.primaryTextTheme.caption?.color,
    'primaryTextTheme.displayLarge?.color':
        themeData.primaryTextTheme.displayLarge?.color,
    'primaryTextTheme.displayMedium?.color':
        themeData.primaryTextTheme.displayMedium?.color,
    'primaryTextTheme.displaySmall?.color':
        themeData.primaryTextTheme.displaySmall?.color,
    'primaryTextTheme.headline1?.color':
        themeData.primaryTextTheme.headline1?.color,
    'primaryTextTheme.headline2?.color':
        themeData.primaryTextTheme.headline2?.color,
    'primaryTextTheme.headline3?.color':
        themeData.primaryTextTheme.headline3?.color,
    'primaryTextTheme.headline4?.color':
        themeData.primaryTextTheme.headline4?.color,
    'primaryTextTheme.headline5?.color':
        themeData.primaryTextTheme.headline5?.color,
    'primaryTextTheme.headline6?.color':
        themeData.primaryTextTheme.headline6?.color,
    'primaryTextTheme.headlineLarge?.color':
        themeData.primaryTextTheme.headlineLarge?.color,
    'primaryTextTheme.headlineMedium?.color':
        themeData.primaryTextTheme.headlineMedium?.color,
    'primaryTextTheme.headlineSmall?.color':
        themeData.primaryTextTheme.headlineSmall?.color,
    'primaryTextTheme.labelLarge?.color':
        themeData.primaryTextTheme.labelLarge?.color,
    'primaryTextTheme.labelMedium?.color':
        themeData.primaryTextTheme.labelMedium?.color,
    'primaryTextTheme.labelSmall?.color':
        themeData.primaryTextTheme.labelSmall?.color,
    'primaryTextTheme.overline?.color':
        themeData.primaryTextTheme.overline?.color,
    'primaryTextTheme.subtitle1?.color':
        themeData.primaryTextTheme.subtitle1?.color,
    'primaryTextTheme.subtitle2?.color':
        themeData.primaryTextTheme.subtitle2?.color,
    'primaryTextTheme.titleLarge?.color':
        themeData.primaryTextTheme.titleLarge?.color,
    'primaryTextTheme.titleMedium?.color':
        themeData.primaryTextTheme.titleMedium?.color,
    'primaryTextTheme.titleSmall?.color':
        themeData.primaryTextTheme.titleSmall?.color,
  };

  for (final backgroundColor in primaryColors.entries) {
    for (final textColor in primaryTextColors.entries) {
      if (backgroundColor.value == null || textColor.value == null) {
        continue;
      }
      final contrast =
          calculateContrast(backgroundColor.value!, textColor.value!);
      final doesMatchMinContrast = doesMatchMinContrastRatio(
        contrast,
        readabilityLevel: accessibilityLevel.readabilityLevel,
      );
      assert(
        doesMatchMinContrast == true,
        'Contrast ratio of ${backgroundColor.key} (${backgroundColor.value}) and ${textColor.key} (${textColor.value}) '
        'is ${contrast.toStringAsFixed(2)}:1, which is not sufficient for specified $accessibilityLevel. '
        '$accessibilityLevel\'s lowest acceptable contrast ratio is ${accessibilityLevel.readabilityLevel.minimumAcceptableContrastRatio}:1.',
      );
    }
  }
}