From bc7397fd753978eadc2d466a6fbad01a76085d39 Mon Sep 17 00:00:00 2001 From: Prashant Sawant Date: Mon, 10 Jun 2024 17:53:27 -0400 Subject: [PATCH] - 100% test coverage for theme, utils, Zeta and ZetaProvider --- lib/src/theme/colors.dart | 26 +- lib/src/theme/colors_base.dart | 4 +- lib/src/theme/theme_data.dart | 20 +- lib/src/zeta.dart | 248 ----------------- lib/src/zeta_provider.dart | 257 +++++++++++++++++ lib/zeta_flutter.dart | 3 +- test/src/theme/color_extensions_test.dart | 4 +- test/src/theme/color_scheme_test.dart | 1 + test/src/theme/colors_test.dart | 158 ++++++++++- test/src/theme/contrast_test.dart | 32 +++ test/src/theme/theme_data_test.dart | 62 +++++ test/src/utils/extensions_test.mocks.dart | 14 + test/src/zeta_provider_test.dart | 323 ++++++++++++++++++++++ test/src/zeta_provider_test.mocks.dart | 72 +++++ test/src/zeta_test.dart | 176 ++++++++++++ 15 files changed, 1119 insertions(+), 281 deletions(-) create mode 100644 lib/src/zeta_provider.dart create mode 100644 test/src/theme/contrast_test.dart create mode 100644 test/src/theme/theme_data_test.dart create mode 100644 test/src/zeta_provider_test.dart create mode 100644 test/src/zeta_provider_test.mocks.dart create mode 100644 test/src/zeta_test.dart diff --git a/lib/src/theme/colors.dart b/lib/src/theme/colors.dart index 80cdc074..685aa2f8 100644 --- a/lib/src/theme/colors.dart +++ b/lib/src/theme/colors.dart @@ -306,7 +306,7 @@ class ZetaColors extends Equatable { /// Defaults to `ZetaColors.cool.90`. /// /// {@macro zeta-color-dark} - Color get iconDefault => textDefault; + Color get iconDefault => cool.shade90; /// Subtle icon color. /// @@ -315,14 +315,14 @@ class ZetaColors extends Equatable { /// Maps to [ColorScheme.onSurface]. /// /// {@macro zeta-color-dark} - Color get iconSubtle => textSubtle; + Color get iconSubtle => cool.shade70; /// Disabled icon color. /// /// Defaults to `ZetaColors.cool.50`. /// /// {@macro zeta-color-dark} - Color get iconDisabled => textDisabled; + Color get iconDisabled => cool.shade50; /// Inverse icon color. /// @@ -331,7 +331,7 @@ class ZetaColors extends Equatable { /// Defaults to `ZetaColors.cool.20`. /// /// {@macro zeta-color-dark} - Color get iconInverse => textInverse; + Color get iconInverse => cool.shade20; /// Default Surface Color /// @@ -351,12 +351,12 @@ class ZetaColors extends Equatable { /// Selected Surface Color /// /// {@macro zeta-color-dark} - Color get surfaceSelected => blue.shade(10); + Color get surfaceSelected => primary.shade(10); /// Selected-hover Surface Color /// /// {@macro zeta-color-dark} - Color get surfaceSelectedHover => blue.shade(20); + Color get surfaceSelectedHover => primary.shade(20); /// Disabled Surface Color /// @@ -376,7 +376,7 @@ class ZetaColors extends Equatable { /// Primary-subtle Surface Color /// /// {@macro zeta-color-dark} - Color get surfacePrimarySubtle => blue.shade(10); + Color get surfacePrimarySubtle => primary.shade(10); /// Avatar Avatar Surface Color /// @@ -416,7 +416,7 @@ class ZetaColors extends Equatable { /// Secondary-subtle Surface Color /// /// {@macro zeta-color-dark} - Color get surfaceSecondarySubtle => yellow.shade(10); + Color get surfaceSecondarySubtle => secondary.shade(10); /// Positive Surface Color /// @@ -491,17 +491,17 @@ class ZetaColors extends Equatable { /// Primary-main Border Color /// /// {@macro zeta-color-dark} - ZetaColorSwatch get borderPrimaryMain => blue; + ZetaColorSwatch get borderPrimaryMain => primary; /// Primary Border Color /// /// {@macro zeta-color-dark} - Color get borderPrimary => blue.shade(50); + Color get borderPrimary => primary.shade(50); /// Secondary Border Color /// /// {@macro zeta-color-dark} - Color get borderSecondary => yellow.shade(50); + Color get borderSecondary => secondary.shade(50); /// Positive Border Color /// @@ -994,11 +994,13 @@ extension ZetaColorGetters on ColorScheme { T _resolveDefault(_ZetaColorProperties property) { switch (property) { case _ZetaColorProperties.primarySwatch: - case _ZetaColorProperties.secondarySwatch: return ZetaColorBase.blue.apply(brightness: brightness, contrast: contrast) as T; + case _ZetaColorProperties.secondarySwatch: + return ZetaColorBase.yellow.apply(brightness: brightness, contrast: contrast) as T; case _ZetaColorProperties.cool: return ZetaColorBase.cool.apply(brightness: brightness, contrast: contrast) as T; case _ZetaColorProperties.warm: + return ZetaColorBase.warm.apply(brightness: brightness, contrast: contrast) as T; case _ZetaColorProperties.textDefault: return ZetaColorBase.cool.apply(brightness: brightness, contrast: contrast).shade90 as T; case _ZetaColorProperties.textSubtle: diff --git a/lib/src/theme/colors_base.dart b/lib/src/theme/colors_base.dart index 56919cd5..62a3235e 100644 --- a/lib/src/theme/colors_base.dart +++ b/lib/src/theme/colors_base.dart @@ -16,9 +16,7 @@ import '../../zeta_flutter.dart'; /// * [warm] /// * [pure] /// * [cool]. -class ZetaColorBase { - ZetaColorBase._(); - +abstract final class ZetaColorBase { /// Link color for light mode. @Deprecated('This color has been deprecated as of v0.10.0') static const Color linkLight = Color(0xFF0257AC); diff --git a/lib/src/theme/theme_data.dart b/lib/src/theme/theme_data.dart index 9d95d801..de74be0f 100644 --- a/lib/src/theme/theme_data.dart +++ b/lib/src/theme/theme_data.dart @@ -1,3 +1,4 @@ +import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'color_extensions.dart'; @@ -14,7 +15,7 @@ export 'constants.dart'; /// /// This class encapsulates the colors and fonts used for the Zeta theme in both light and dark modes. @immutable -class ZetaThemeData { +class ZetaThemeData extends Equatable { /// Constructs a [ZetaThemeData]. /// /// If [primary] and/or [secondary] colors are provided, they will be used to create the light and dark Zeta color palettes. @@ -83,15 +84,10 @@ class ZetaThemeData { } @override - bool operator ==(Object other) => - identical(this, other) || - other is ZetaThemeData && - runtimeType == other.runtimeType && - fontFamily == other.fontFamily && - identifier == other.identifier && - _colorsLight == other._colorsLight && - _colorsDark == other._colorsDark; - - @override - int get hashCode => fontFamily.hashCode ^ identifier.hashCode ^ _colorsLight.hashCode ^ _colorsDark.hashCode; + List get props => [ + fontFamily, + identifier, + _colorsLight, + _colorsDark, + ]; } diff --git a/lib/src/zeta.dart b/lib/src/zeta.dart index e28de7e3..cae027e8 100644 --- a/lib/src/zeta.dart +++ b/lib/src/zeta.dart @@ -1,11 +1,8 @@ -import 'dart:async'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'theme/contrast.dart'; import 'theme/theme_data.dart'; -import 'theme/theme_service.dart'; /// An [InheritedWidget] that provides access to Zeta theme settings. /// @@ -116,248 +113,3 @@ class Zeta extends InheritedWidget { ..add(EnumProperty('brightness', brightness)); } } - -/// A typedef for the ZetaAppBuilder function which takes [BuildContext], [ZetaThemeData], -/// and [ThemeMode] and returns a [Widget]. -typedef ZetaAppBuilder = Widget Function(BuildContext context, ZetaThemeData themeData, ThemeMode themeMode); - -/// A widget that provides Zeta theming and contrast data down the widget tree. -class ZetaProvider extends StatefulWidget with Diagnosticable { - /// Constructs a [ZetaProvider] widget. - /// - /// The [builder] argument is required. The [initialThemeMode], [initialContrast], - /// and [initialThemeData] arguments provide initial values. - ZetaProvider({ - required this.builder, - this.initialThemeMode = ThemeMode.system, - this.initialContrast = ZetaContrast.aa, - this.themeService, - ZetaThemeData? initialThemeData, - super.key, - }) : initialThemeData = initialThemeData ?? ZetaThemeData(); - - /// Specifies the initial theme mode for the app. - /// - /// It can be one of the values: [ThemeMode.system], [ThemeMode.light], or [ThemeMode.dark]. - /// Defaults to [ThemeMode.system]. - final ThemeMode initialThemeMode; - - /// Provides the initial theme data for the app. - /// - /// This contains all the theming information. If not provided, - /// it defaults to a basic [ZetaThemeData] instance. - final ZetaThemeData initialThemeData; - - /// Specifies the initial contrast setting for the app. - /// - /// Defaults to [ZetaContrast.aa]. - final ZetaContrast initialContrast; - - /// A builder function to construct the widget tree using the provided theming information. - /// - /// It receives the [BuildContext], [ZetaThemeData], and [ThemeMode] as arguments - /// and is expected to return a [Widget]. - final ZetaAppBuilder builder; - - /// A `ZetaThemeService` - /// - /// It provides the structure for loading and saving themes in Zeta application. - final ZetaThemeService? themeService; - - @override - State createState() => ZetaProviderState(); - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('themeData', initialThemeData)) - ..add(ObjectFlagProperty.has('builder', builder)) - ..add(EnumProperty('initialThemeMode', initialThemeMode)) - ..add(EnumProperty('initialContrast', initialContrast)) - ..add(DiagnosticsProperty('themeService', themeService)); - } - - /// Retrieves the [ZetaProviderState] from the provided context. - static ZetaProviderState of(BuildContext context) { - final zetaState = context.findAncestorStateOfType(); - if (zetaState != null) { - return zetaState; - } else { - throw FlutterError.fromParts( - [ - ErrorDescription('Unable to find ZetaProviderState in the widget tree.'), - ErrorHint( - 'Ensure that the context passed to ZetaProvider.of() is a descendant of a ZetaProvider widget. This usually means that ZetaProviderState should be an ancestor of the widget which uses this context.', - ), - ErrorSpacer(), - ErrorDescription('The widget for the context used was:'), - DiagnosticsProperty('widget', context.widget, showName: false), - ErrorSpacer(), - ErrorHint( - 'If you recently changed the type of that widget, or the widget tree, ensure the ZetaProvider widget is still an ancestor.', - ), - ], - ); - } - } -} - -/// The state associated with [ZetaProvider]. -class ZetaProviderState extends State with Diagnosticable, WidgetsBindingObserver { - // Fields for ZetaThemeManager. - - /// Represents the late initialization of the ZetaContrast value. - late ZetaContrast _contrast; - - /// Represents the late initialization of the ThemeMode value. - late ThemeMode _themeMode; - - /// Represents the late initialization of the ZetaThemeData object. - late ZetaThemeData _themeData; - - /// Represents the late initialization of the system's current brightness (dark or light mode). - late Brightness _platformBrightness; - - /// Represents a nullable brightness value to be used for brightness change debouncing. - Brightness? _debounceBrightness; - - /// Timer used for debouncing brightness changes. - Timer? _debounceTimer; - - /// Represents the duration for the debounce timer. - static const _debounceDuration = Duration(milliseconds: 500); - - /// This method is called when this object is inserted into the tree. - /// - /// Here, it also adds this object as an observer in [WidgetsBinding] instance - /// and initializes various fields related to the theme, contrast, and brightness of the app. - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addObserver(this); - - // Set the initial brightness with the system's current brightness from the first view of the platform dispatcher. - _platformBrightness = MediaQueryData.fromView(PlatformDispatcher.instance.views.first).platformBrightness; - - // Set the initial theme mode. - _themeMode = widget.initialThemeMode; - - // Set the initial contrast. - _contrast = widget.initialContrast; - - // Apply the initial contrast to the theme data. - _themeData = widget.initialThemeData.apply(contrast: _contrast); - } - - /// Clean up function to be called when this object is removed from the tree. - /// - /// This also removes this object as an observer from the [WidgetsBinding] instance. - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - - /// Overrides the [didChangePlatformBrightness] method from the parent class. - /// - /// This method gets information about the platform's brightness and updates the app if it ever changes. - /// The changes are debounced with a timer to avoid them being too frequent. - @override - void didChangePlatformBrightness() { - super.didChangePlatformBrightness(); - - // Get the platform brightness from the first view of the platform dispatcher - // `_debounceBrightness` will then hold the new brightness - _debounceBrightness = MediaQueryData.fromView(PlatformDispatcher.instance.views.first).platformBrightness; - - // If the current stored brightness value is different from the newly fetched value - if (_platformBrightness != _debounceBrightness) { - // If brightness has changed, cancel the existing timer and start a new one - - // Cancel existing timer if any - _debounceTimer?.cancel(); - - // Start a new timer with `_debounceDuration` delay - _debounceTimer = Timer(_debounceDuration, () { - // Once timer fires, check if brightness is still different and not null - if (_debounceBrightness != null && _platformBrightness != _debounceBrightness) { - // If brightness value has indeed changed, update the state - setState(() { - // Set the new brightness value - _platformBrightness = _debounceBrightness!; - }); - } - }); - } - } - - @override - Widget build(BuildContext context) { - return Zeta( - themeMode: _themeMode, - themeData: _themeData, - contrast: _contrast, - mediaBrightness: _platformBrightness, - child: widget.builder(context, _themeData, _themeMode), - ); - } - - @override - void didUpdateWidget(ZetaProvider oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.initialContrast != widget.initialContrast || - oldWidget.initialThemeMode != widget.initialThemeMode || - oldWidget.initialThemeData != widget.initialThemeData) { - setState(() { - _themeMode = widget.initialThemeMode; - _contrast = widget.initialContrast; - _themeData = widget.initialThemeData.apply(contrast: _contrast); - }); - } - } - - /// Updates the current theme mode. - void updateThemeMode(ThemeMode themeMode) { - setState(() { - _themeMode = themeMode; - _saveThemeChange(); - }); - } - - /// Updates the current theme data. - void updateThemeData(ZetaThemeData data) { - setState(() { - _themeData = data.apply(contrast: _contrast); - _saveThemeChange(); - }); - } - - /// Updates the current contrast. - void updateContrast(ZetaContrast contrast) { - setState(() { - _contrast = contrast; - _themeData = _themeData.apply(contrast: contrast); - _saveThemeChange(); - }); - } - - void _saveThemeChange() { - unawaited( - widget.themeService?.saveTheme( - themeData: _themeData, - themeMode: _themeMode, - contrast: _contrast, - ), - ); - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add(DiagnosticsProperty('themeData', _themeData)) - ..add(EnumProperty('contrast', _contrast)) - ..add(EnumProperty('themeMode', _themeMode)); - } -} diff --git a/lib/src/zeta_provider.dart b/lib/src/zeta_provider.dart new file mode 100644 index 00000000..dab6baa1 --- /dev/null +++ b/lib/src/zeta_provider.dart @@ -0,0 +1,257 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'theme/contrast.dart'; +import 'theme/theme_data.dart'; +import 'theme/theme_service.dart'; +import 'zeta.dart'; + +/// A typedef for the ZetaAppBuilder function which takes [BuildContext], [ZetaThemeData], +/// and [ThemeMode] and returns a [Widget]. +typedef ZetaAppBuilder = Widget Function(BuildContext context, ZetaThemeData themeData, ThemeMode themeMode); + +/// A widget that provides Zeta theming and contrast data down the widget tree. +class ZetaProvider extends StatefulWidget with Diagnosticable { + /// Constructs a [ZetaProvider] widget. + /// + /// The [builder] argument is required. The [initialThemeMode], [initialContrast], + /// and [initialThemeData] arguments provide initial values. + ZetaProvider({ + required this.builder, + this.initialThemeMode = ThemeMode.system, + this.initialContrast = ZetaContrast.aa, + this.themeService, + ZetaThemeData? initialThemeData, + super.key, + }) : initialThemeData = initialThemeData ?? ZetaThemeData(); + + /// Specifies the initial theme mode for the app. + /// + /// It can be one of the values: [ThemeMode.system], [ThemeMode.light], or [ThemeMode.dark]. + /// Defaults to [ThemeMode.system]. + final ThemeMode initialThemeMode; + + /// Provides the initial theme data for the app. + /// + /// This contains all the theming information. If not provided, + /// it defaults to a basic [ZetaThemeData] instance. + final ZetaThemeData initialThemeData; + + /// Specifies the initial contrast setting for the app. + /// + /// Defaults to [ZetaContrast.aa]. + final ZetaContrast initialContrast; + + /// A builder function to construct the widget tree using the provided theming information. + /// + /// It receives the [BuildContext], [ZetaThemeData], and [ThemeMode] as arguments + /// and is expected to return a [Widget]. + final ZetaAppBuilder builder; + + /// A `ZetaThemeService` + /// + /// It provides the structure for loading and saving themes in Zeta application. + final ZetaThemeService? themeService; + + @override + State createState() => ZetaProviderState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('themeData', initialThemeData)) + ..add(ObjectFlagProperty.has('builder', builder)) + ..add(EnumProperty('initialThemeMode', initialThemeMode)) + ..add(EnumProperty('initialContrast', initialContrast)) + ..add(DiagnosticsProperty('themeService', themeService)); + } + + /// Retrieves the [ZetaProviderState] from the provided context. + static ZetaProviderState of(BuildContext context) { + final zetaState = context.findAncestorStateOfType(); + if (zetaState != null) { + return zetaState; + } else { + throw FlutterError.fromParts( + [ + ErrorDescription('Unable to find ZetaProviderState in the widget tree.'), + ErrorHint( + 'Ensure that the context passed to ZetaProvider.of() is a descendant of a ZetaProvider widget. This usually means that ZetaProviderState should be an ancestor of the widget which uses this context.', + ), + ErrorSpacer(), + ErrorDescription('The widget for the context used was:'), + DiagnosticsProperty('widget', context.widget, showName: false), + ErrorSpacer(), + ErrorHint( + 'If you recently changed the type of that widget, or the widget tree, ensure the ZetaProvider widget is still an ancestor.', + ), + ], + ); + } + } +} + +/// The state associated with [ZetaProvider]. +class ZetaProviderState extends State with Diagnosticable, WidgetsBindingObserver { + // Fields for ZetaThemeManager. + + /// Represents the late initialization of the ZetaContrast value. + late ZetaContrast _contrast; + + /// Represents the late initialization of the ThemeMode value. + late ThemeMode _themeMode; + + /// Represents the late initialization of the ZetaThemeData object. + late ZetaThemeData _themeData; + + /// Represents the late initialization of the system's current brightness (dark or light mode). + late Brightness _platformBrightness; + + /// Represents a nullable brightness value to be used for brightness change debouncing. + Brightness? _debounceBrightness; + + /// Timer used for debouncing brightness changes. + Timer? _debounceTimer; + + /// Represents the duration for the debounce timer. + static const _debounceDuration = Duration(milliseconds: 500); + + /// This method is called when this object is inserted into the tree. + /// + /// Here, it also adds this object as an observer in [WidgetsBinding] instance + /// and initializes various fields related to the theme, contrast, and brightness of the app. + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + + // Set the initial brightness with the system's current brightness from the first view of the platform dispatcher. + _platformBrightness = MediaQueryData.fromView(PlatformDispatcher.instance.views.first).platformBrightness; + + // Set the initial theme mode. + _themeMode = widget.initialThemeMode; + + // Set the initial contrast. + _contrast = widget.initialContrast; + + // Apply the initial contrast to the theme data. + _themeData = widget.initialThemeData.apply(contrast: _contrast); + } + + /// Clean up function to be called when this object is removed from the tree. + /// + /// This also removes this object as an observer from the [WidgetsBinding] instance. + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + /// Overrides the [didChangePlatformBrightness] method from the parent class. + /// + /// This method gets information about the platform's brightness and updates the app if it ever changes. + /// The changes are debounced with a timer to avoid them being too frequent. + @override + void didChangePlatformBrightness() { + super.didChangePlatformBrightness(); + + // Get the binding instance + final binding = WidgetsFlutterBinding.ensureInitialized(); + + // Get the platform brightness from the PlatformDispatcher + // `_debounceBrightness` will then hold the new brightness + _debounceBrightness = binding.platformDispatcher.platformBrightness; + + // If the current stored brightness value is different from the newly fetched value + if (_platformBrightness != _debounceBrightness) { + // If brightness has changed, cancel the existing timer and start a new one + + // Cancel existing timer if any + _debounceTimer?.cancel(); + + // Start a new timer with `_debounceDuration` delay + _debounceTimer = Timer(_debounceDuration, () { + // Once timer fires, check if brightness is still different and not null + if (_debounceBrightness != null && _platformBrightness != _debounceBrightness) { + // If brightness value has indeed changed, update the state + setState(() { + // Set the new brightness value + _platformBrightness = _debounceBrightness!; + }); + } + }); + } + } + + @override + Widget build(BuildContext context) { + return Zeta( + themeMode: _themeMode, + themeData: _themeData, + contrast: _contrast, + mediaBrightness: _platformBrightness, + child: widget.builder(context, _themeData, _themeMode), + ); + } + + @override + void didUpdateWidget(ZetaProvider oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.initialContrast != widget.initialContrast || + oldWidget.initialThemeMode != widget.initialThemeMode || + oldWidget.initialThemeData != widget.initialThemeData) { + setState(() { + _themeMode = widget.initialThemeMode; + _contrast = widget.initialContrast; + _themeData = widget.initialThemeData.apply(contrast: _contrast); + }); + } + } + + /// Updates the current theme mode. + void updateThemeMode(ThemeMode themeMode) { + setState(() { + _themeMode = themeMode; + _saveThemeChange(); + }); + } + + /// Updates the current theme data. + void updateThemeData(ZetaThemeData data) { + setState(() { + _themeData = data.apply(contrast: _contrast); + _saveThemeChange(); + }); + } + + /// Updates the current contrast. + void updateContrast(ZetaContrast contrast) { + setState(() { + _contrast = contrast; + _themeData = _themeData.apply(contrast: contrast); + _saveThemeChange(); + }); + } + + void _saveThemeChange() { + unawaited( + widget.themeService?.saveTheme( + themeData: _themeData, + themeMode: _themeMode, + contrast: _contrast, + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('themeData', _themeData)) + ..add(EnumProperty('contrast', _contrast)) + ..add(EnumProperty('themeMode', _themeMode)); + } +} diff --git a/lib/zeta_flutter.dart b/lib/zeta_flutter.dart index 013d0a80..28dfc02d 100644 --- a/lib/zeta_flutter.dart +++ b/lib/zeta_flutter.dart @@ -63,7 +63,8 @@ export 'src/theme/theme_data.dart'; export 'src/theme/theme_service.dart'; export 'src/theme/tokens.dart'; export 'src/theme/typography.dart'; +export 'src/utils/debounce.dart'; export 'src/utils/enums.dart'; export 'src/utils/extensions.dart'; -export 'src/utils/debounce.dart'; export 'src/zeta.dart'; +export 'src/zeta_provider.dart'; diff --git a/test/src/theme/color_extensions_test.dart b/test/src/theme/color_extensions_test.dart index 588e37aa..be017c94 100644 --- a/test/src/theme/color_extensions_test.dart +++ b/test/src/theme/color_extensions_test.dart @@ -67,13 +67,13 @@ void main() { test('getShadeColor returns correct shade color', () { const color = Colors.blue; - final shadeColor = color.getShadeColor(shadeValue: 15); + final shadeColor = color.getShadeColor(); expect(shadeColor, isNot(color)); }); test('getShadeColor returns correct shade color for dark', () { const color = Colors.blue; - final shadeColor = color.getShadeColor(shadeValue: 15, lighten: false); + final shadeColor = color.getShadeColor(lighten: false); expect(shadeColor, isNot(color)); }); diff --git a/test/src/theme/color_scheme_test.dart b/test/src/theme/color_scheme_test.dart index 7ab4449e..0e2cb063 100644 --- a/test/src/theme/color_scheme_test.dart +++ b/test/src/theme/color_scheme_test.dart @@ -82,6 +82,7 @@ void main() { ); expect(zetaColorScheme, identicalColorScheme); + expect(zetaColorScheme.hashCode, identicalColorScheme.hashCode); expect(zetaColorScheme == identicalColorScheme, isTrue); }); diff --git a/test/src/theme/colors_test.dart b/test/src/theme/colors_test.dart index 021a63ae..cb4d6367 100644 --- a/test/src/theme/colors_test.dart +++ b/test/src/theme/colors_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: deprecated_member_use_from_same_package + import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:zeta_flutter/zeta_flutter.dart'; @@ -23,7 +25,10 @@ void main() { }); test('light constructor initializes correctly', () { - final zetaColors = ZetaColors.light(); + final zetaColors = ZetaColors.light( + warm: ZetaColorBase.warm, + cool: ZetaColorBase.cool, + ); expect(zetaColors.brightness, Brightness.light); expect(zetaColors.contrast, ZetaContrast.aa); @@ -41,7 +46,10 @@ void main() { }); test('dark constructor initializes correctly', () { - final zetaColors = ZetaColors.dark(); + final zetaColors = ZetaColors.dark( + warm: ZetaColorBase.warm, + cool: ZetaColorBase.cool, + ); expect(zetaColors.brightness, Brightness.dark); expect(zetaColors.contrast, ZetaContrast.aa); @@ -59,7 +67,7 @@ void main() { }); test('copyWith creates a new instance with updated properties', () { - final zetaColors = ZetaColors(); + final zetaColors = ZetaColors().copyWith(); final newColors = zetaColors.copyWith( brightness: Brightness.dark, contrast: ZetaContrast.aaa, @@ -67,6 +75,7 @@ void main() { secondary: ZetaColorBase.orange, ); + expect(newColors.isDarkMode, true); expect(newColors.brightness, Brightness.dark); expect(newColors.contrast, ZetaContrast.aaa); expect(newColors.primary, ZetaColorBase.green.apply(brightness: Brightness.dark, contrast: ZetaContrast.aaa)); @@ -75,6 +84,25 @@ void main() { expect(newColors.black, zetaColors.black); }); + test('rainbow getters returns correct colors', () { + final zetaColors = ZetaColors(); + expect(zetaColors.rainbow[0], zetaColors.red); + expect(zetaColors.rainbow[1], zetaColors.orange); + expect(zetaColors.rainbow[2], zetaColors.yellow); + expect(zetaColors.rainbow[3], zetaColors.green); + expect(zetaColors.rainbow[4], zetaColors.blue); + expect(zetaColors.rainbow[5], zetaColors.teal); + expect(zetaColors.rainbow[6], zetaColors.pink); + + expect(zetaColors.rainbowMap['red'], zetaColors.red); + expect(zetaColors.rainbowMap['orange'], zetaColors.orange); + expect(zetaColors.rainbowMap['yellow'], zetaColors.yellow); + expect(zetaColors.rainbowMap['green'], zetaColors.green); + expect(zetaColors.rainbowMap['blue'], zetaColors.blue); + expect(zetaColors.rainbowMap['teal'], zetaColors.teal); + expect(zetaColors.rainbowMap['pink'], zetaColors.pink); + }); + test('apply returns a new instance with updated contrast', () { final zetaColors = ZetaColors(); final newColors = zetaColors.apply(contrast: ZetaContrast.aaa); @@ -100,6 +128,57 @@ void main() { expect(scheme.error, zetaColors.error); }); + test('Color getter returns correct values', () { + final zetaColors = ZetaColors(); + + expect(zetaColors.textDefault, ZetaColorBase.cool.shade90); + expect(zetaColors.textSubtle, ZetaColorBase.cool.shade70); + expect(zetaColors.textDisabled, ZetaColorBase.cool.shade50); + expect(zetaColors.textInverse, ZetaColorBase.cool.shade20); + expect(zetaColors.iconDefault, ZetaColorBase.cool.shade90); + expect(zetaColors.iconSubtle, ZetaColorBase.cool.shade70); + expect(zetaColors.iconDisabled, ZetaColorBase.cool.shade50); + expect(zetaColors.iconInverse, ZetaColorBase.cool.shade20); + expect(zetaColors.surfaceDefault, ZetaColorBase.pure.shade(0)); + expect(zetaColors.surfaceDefaultInverse, ZetaColorBase.warm.shade(100)); + expect(zetaColors.surfaceHover, ZetaColorBase.cool.shade(20)); + expect(zetaColors.surfaceSelected, ZetaColorBase.blue.shade(10)); + expect(zetaColors.surfaceSelectedHover, ZetaColorBase.blue.shade(20)); + expect(zetaColors.surfaceDisabled, ZetaColorBase.cool.shade(30)); + expect(zetaColors.surfaceCool, ZetaColorBase.cool.shade(10)); + expect(zetaColors.surfaceWarm, ZetaColorBase.warm.shade(10)); + expect(zetaColors.surfacePrimarySubtle, ZetaColorBase.blue.shade(10)); + expect(zetaColors.surfaceAvatarBlue, ZetaColorBase.blue.shade(80)); + expect(zetaColors.surfaceAvatarOrange, ZetaColorBase.orange.shade(50)); + expect(zetaColors.surfaceAvatarPink, ZetaColorBase.pink.shade(80)); + expect(zetaColors.surfaceAvatarPurple, ZetaColorBase.purple.shade(80)); + expect(zetaColors.surfaceAvatarTeal, ZetaColorBase.teal.shade(80)); + expect(zetaColors.surfaceAvatarYellow, ZetaColorBase.yellow.shade(50)); + expect(zetaColors.surfaceSecondarySubtle, ZetaColorBase.yellow.shade(10)); + expect(zetaColors.surfacePositiveSubtle, ZetaColorBase.green.shade(10)); + expect(zetaColors.surfaceWarningSubtle, ZetaColorBase.orange.shade(10)); + expect(zetaColors.surfaceNegativeSubtle, ZetaColorBase.red.shade(10)); + expect(zetaColors.surfaceInfoSubtle, ZetaColorBase.purple.shade(10)); + expect(zetaColors.borderDefault, ZetaColorBase.cool.shade(40)); + expect(zetaColors.borderSubtle, ZetaColorBase.cool.shade(30)); + expect(zetaColors.borderHover, ZetaColorBase.cool.shade(90)); + expect(zetaColors.borderSelected, ZetaColorBase.cool.shade(90)); + expect(zetaColors.borderDisabled, ZetaColorBase.cool.shade(20)); + expect(zetaColors.borderPure, ZetaColorBase.pure.shade(0)); + expect(zetaColors.borderPrimary, ZetaColorBase.blue.shade(50)); + expect(zetaColors.borderSecondary, ZetaColorBase.yellow.shade(50)); + expect(zetaColors.borderPositive, ZetaColorBase.green.shade(50)); + expect(zetaColors.borderWarning, ZetaColorBase.orange.shade(50)); + expect(zetaColors.borderNegative, ZetaColorBase.red.shade(50)); + expect(zetaColors.borderInfo, ZetaColorBase.purple.shade(50)); + expect(zetaColors.surfacePositive, ZetaColorBase.green); + expect(zetaColors.surfaceWarning, ZetaColorBase.orange); + expect(zetaColors.surfaceNegative, ZetaColorBase.red); + expect(zetaColors.surfaceAvatarGreen, ZetaColorBase.green); + expect(zetaColors.surfaceInfo, ZetaColorBase.purple); + expect(zetaColors.borderPrimaryMain, ZetaColorBase.blue); + }); + test('deprecated properties return correct values', () { final zetaColors = ZetaColors(); @@ -136,4 +215,77 @@ void main() { ); }); }); + + group('ZetaColorGetters', () { + test('ColorScheme extension getters should return correct colors when scheme is ZetaColorScheme', () { + final zetaColors = ZetaColors(); + final themeData = ThemeData.light().copyWith(colorScheme: zetaColors.toScheme()); + expect(themeData.colorScheme.primarySwatch, zetaColors.primary); + expect(themeData.colorScheme.secondarySwatch, zetaColors.secondary); + expect(themeData.colorScheme.cool, zetaColors.cool); + expect(themeData.colorScheme.warm, zetaColors.warm); + expect(themeData.colorScheme.textDefault, zetaColors.textDefault); + expect(themeData.colorScheme.textSubtle, zetaColors.textSubtle); + expect(themeData.colorScheme.textDisabled, zetaColors.textDisabled); + expect(themeData.colorScheme.textInverse, zetaColors.textInverse); + expect(themeData.colorScheme.surfacePrimary, zetaColors.surfacePrimary); + expect(themeData.colorScheme.surfaceSecondary, zetaColors.surfaceSecondary); + expect(themeData.colorScheme.surfaceTertiary, zetaColors.surfaceTertiary); + expect(themeData.colorScheme.surfaceDisabled, zetaColors.surfaceDisabled); + expect(themeData.colorScheme.surfaceHover, zetaColors.surfaceHover); + expect(themeData.colorScheme.surfaceSelected, zetaColors.surfaceSelected); + expect(themeData.colorScheme.surfaceSelectedHover, zetaColors.surfaceSelectedHover); + expect(themeData.colorScheme.borderDefault, zetaColors.borderDefault); + expect(themeData.colorScheme.borderSubtle, zetaColors.borderSubtle); + expect(themeData.colorScheme.borderDisabled, zetaColors.borderDisabled); + expect(themeData.colorScheme.borderSelected, zetaColors.borderSelected); + expect(themeData.colorScheme.blue, zetaColors.blue); + expect(themeData.colorScheme.green, zetaColors.green); + expect(themeData.colorScheme.red, zetaColors.red); + expect(themeData.colorScheme.orange, zetaColors.orange); + expect(themeData.colorScheme.purple, zetaColors.purple); + expect(themeData.colorScheme.yellow, zetaColors.yellow); + expect(themeData.colorScheme.teal, zetaColors.teal); + expect(themeData.colorScheme.pink, zetaColors.pink); + expect(themeData.colorScheme.positive, zetaColors.green); + expect(themeData.colorScheme.negative, zetaColors.red); + expect(themeData.colorScheme.warning, zetaColors.orange); + expect(themeData.colorScheme.info, zetaColors.purple); + }); + + test('ColorScheme extension getters should return default colors when ZetaColorScheme scheme is not injected', () { + final themeData = ThemeData.light(); + expect(themeData.colorScheme.primarySwatch, ZetaColorBase.blue); + expect(themeData.colorScheme.secondarySwatch, ZetaColorBase.yellow); + expect(themeData.colorScheme.cool, ZetaColorBase.cool); + expect(themeData.colorScheme.warm, ZetaColorBase.warm); + expect(themeData.colorScheme.textDefault, ZetaColorBase.cool.shade90); + expect(themeData.colorScheme.textSubtle, ZetaColorBase.cool.shade70); + expect(themeData.colorScheme.textDisabled, ZetaColorBase.cool.shade50); + expect(themeData.colorScheme.textInverse, ZetaColorBase.cool.shade20); + expect(themeData.colorScheme.surfacePrimary, ZetaColorBase.white); + expect(themeData.colorScheme.surfaceSecondary, ZetaColorBase.cool.shade10); + expect(themeData.colorScheme.surfaceTertiary, ZetaColorBase.warm.shade10); + expect(themeData.colorScheme.surfaceDisabled, ZetaColorBase.cool.shade30); + expect(themeData.colorScheme.surfaceHover, ZetaColorBase.cool.shade20); + expect(themeData.colorScheme.surfaceSelected, ZetaColorBase.blue.shade10); + expect(themeData.colorScheme.surfaceSelectedHover, ZetaColorBase.blue.shade20); + expect(themeData.colorScheme.borderDefault, ZetaColorBase.cool.shade50); + expect(themeData.colorScheme.borderSubtle, ZetaColorBase.cool.shade40); + expect(themeData.colorScheme.borderDisabled, ZetaColorBase.cool.shade30); + expect(themeData.colorScheme.borderSelected, ZetaColorBase.cool.shade90); + expect(themeData.colorScheme.blue, ZetaColorBase.blue); + expect(themeData.colorScheme.green, ZetaColorBase.green); + expect(themeData.colorScheme.red, ZetaColorBase.red); + expect(themeData.colorScheme.orange, ZetaColorBase.orange); + expect(themeData.colorScheme.purple, ZetaColorBase.purple); + expect(themeData.colorScheme.yellow, ZetaColorBase.yellow); + expect(themeData.colorScheme.teal, ZetaColorBase.teal); + expect(themeData.colorScheme.pink, ZetaColorBase.pink); + expect(themeData.colorScheme.positive, ZetaColorBase.green); + expect(themeData.colorScheme.negative, ZetaColorBase.red); + expect(themeData.colorScheme.warning, ZetaColorBase.orange); + expect(themeData.colorScheme.info, ZetaColorBase.purple); + }); + }); } diff --git a/test/src/theme/contrast_test.dart b/test/src/theme/contrast_test.dart new file mode 100644 index 00000000..fbb0ddc1 --- /dev/null +++ b/test/src/theme/contrast_test.dart @@ -0,0 +1,32 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:zeta_flutter/src/theme/contrast.dart'; + +void main() { + group('ZetaContrast Accessibility Indices', () { + test('AA contrast indices', () { + expect(ZetaContrast.aa.primary, 60); + expect(ZetaContrast.aa.text, 60); + expect(ZetaContrast.aa.icon, 60); + expect(ZetaContrast.aa.hover, 70); + expect(ZetaContrast.aa.selected, 80); + expect(ZetaContrast.aa.focus, 80); + expect(ZetaContrast.aa.border, 60); + expect(ZetaContrast.aa.subtle, 40); + expect(ZetaContrast.aa.surface, 10); + expect(ZetaContrast.aa.targetContrast, 4.53); + }); + + test('AAA contrast indices', () { + expect(ZetaContrast.aaa.primary, 80); + expect(ZetaContrast.aaa.text, 80); + expect(ZetaContrast.aaa.icon, 80); + expect(ZetaContrast.aaa.hover, 90); + expect(ZetaContrast.aaa.selected, 100); + expect(ZetaContrast.aaa.focus, 100); + expect(ZetaContrast.aaa.border, 80); + expect(ZetaContrast.aaa.subtle, 60); + expect(ZetaContrast.aaa.surface, 10); + expect(ZetaContrast.aaa.targetContrast, 8.33); + }); + }); +} diff --git a/test/src/theme/theme_data_test.dart b/test/src/theme/theme_data_test.dart new file mode 100644 index 00000000..4ddeb3cb --- /dev/null +++ b/test/src/theme/theme_data_test.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:zeta_flutter/src/theme/color_extensions.dart'; +import 'package:zeta_flutter/src/theme/contrast.dart'; +import 'package:zeta_flutter/src/theme/theme_data.dart'; + +void main() { + group('ZetaThemeData', () { + test('Default constructor should initialize with correct defaults', () { + final themeData = ZetaThemeData(); + + expect(themeData.fontFamily, kZetaFontFamily); + expect(themeData.identifier, 'default'); + expect(themeData.colorsLight.brightness, Brightness.light); + expect(themeData.colorsDark.brightness, Brightness.dark); + }); + + test('Constructor should initialize with custom values', () { + const customFontFamily = 'CustomFont'; + const customIdentifier = 'custom_theme'; + const customPrimary = Colors.blue; + const customSecondary = Colors.green; + + final themeData = ZetaThemeData( + fontFamily: customFontFamily, + identifier: customIdentifier, + primary: customPrimary, + secondary: customSecondary, + ); + + expect(themeData.fontFamily, customFontFamily); + expect(themeData.identifier, customIdentifier); + expect(themeData.colorsLight.primary, customPrimary.zetaColorSwatch); + expect(themeData.colorsLight.secondary, customSecondary.zetaColorSwatch); + expect(themeData.colorsDark.primary, customPrimary.zetaColorSwatch.apply(brightness: Brightness.dark)); + expect(themeData.colorsDark.secondary, customSecondary.zetaColorSwatch.apply(brightness: Brightness.dark)); + }); + + test('Apply method should return a new instance with updated contrast', () { + final themeData = ZetaThemeData(); + const newContrast = ZetaContrast.aaa; + final newThemeData = themeData.apply(contrast: newContrast); + + expect(newThemeData.colorsLight.contrast, newContrast); + expect(newThemeData.colorsDark.contrast, newContrast); + expect(newThemeData.fontFamily, themeData.fontFamily); + expect(newThemeData.identifier, themeData.identifier); + }); + + test('Equality and hashCode should work correctly', () { + final themeData1 = ZetaThemeData(); + final themeData2 = ZetaThemeData(); + + expect(themeData1, themeData2); + expect(themeData1.hashCode, themeData2.hashCode); + + final themeData3 = ZetaThemeData(identifier: 'different'); + expect(themeData1, isNot(themeData3)); + expect(themeData1.hashCode, isNot(themeData3.hashCode)); + }); + }); +} diff --git a/test/src/utils/extensions_test.mocks.dart b/test/src/utils/extensions_test.mocks.dart index ffa679fd..c0781128 100644 --- a/test/src/utils/extensions_test.mocks.dart +++ b/test/src/utils/extensions_test.mocks.dart @@ -1326,6 +1326,20 @@ class MockZetaColors extends _i1.Mock implements _i6.ZetaColors { returnValueForMissingStub: <_i4.ZetaColorSwatch>[], ) as List<_i4.ZetaColorSwatch>); + @override + Map get rainbowMap => (super.noSuchMethod( + Invocation.getter(#rainbowMap), + returnValue: {}, + returnValueForMissingStub: {}, + ) as Map); + + @override + List get props => (super.noSuchMethod( + Invocation.getter(#props), + returnValue: [], + returnValueForMissingStub: [], + ) as List); + @override _i6.ZetaColors copyWith({ _i5.Brightness? brightness, diff --git a/test/src/zeta_provider_test.dart b/test/src/zeta_provider_test.dart new file mode 100644 index 00000000..09cb2b0c --- /dev/null +++ b/test/src/zeta_provider_test.dart @@ -0,0 +1,323 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import 'zeta_provider_test.mocks.dart'; + +void main() { + final mockThemeService = MockZetaThemeService(); + final initialThemeData = ZetaThemeData(); + + group('ZetaProvider', () { + testWidgets('initializes with correct default values', (WidgetTester tester) async { + await tester.pumpWidget( + ZetaProvider( + builder: (context, themeData, themeMode) => Container(), + ), + ); + + final providerState = tester.state(find.byType(ZetaProvider)); + expect(providerState.widget.initialThemeMode, ThemeMode.system); + expect(providerState.widget.initialContrast, ZetaContrast.aa); + expect(providerState.widget.initialThemeData, isNotNull); + }); + + testWidgets('initializes with provided values', (WidgetTester tester) async { + await tester.pumpWidget( + ZetaProvider( + builder: (context, themeData, themeMode) => Container(), + initialThemeMode: ThemeMode.light, + initialContrast: ZetaContrast.aaa, + initialThemeData: initialThemeData, + themeService: mockThemeService, + ), + ); + + final providerState = tester.state(find.byType(ZetaProvider)); + expect(providerState.widget.initialThemeMode, ThemeMode.light); + expect(providerState.widget.initialContrast, ZetaContrast.aaa); + expect(providerState.widget.initialThemeData, initialThemeData); + expect(providerState.widget.themeService, mockThemeService); + }); + + testWidgets('updateThemeMode updates the state correctly', (WidgetTester tester) async { + await tester.pumpWidget( + ZetaProvider( + builder: (context, themeData, themeMode) => Container(), + initialThemeData: initialThemeData, + themeService: mockThemeService, + ), + ); + + tester.state(find.byType(ZetaProvider)).updateThemeMode(ThemeMode.dark); + await tester.pump(); + + // Verifying through the public interface of Zeta widget + final zeta = tester.widget(find.byType(Zeta)); + expect(zeta.themeMode, ThemeMode.dark); + verify( + mockThemeService.saveTheme( + themeData: initialThemeData, + themeMode: ThemeMode.dark, + contrast: ZetaContrast.aa, + ), + ).called(1); + }); + + testWidgets('updateThemeData updates the state correctly', (WidgetTester tester) async { + final newThemeData = ZetaThemeData(); + + await tester.pumpWidget( + ZetaProvider( + builder: (context, themeData, themeMode) => Container(), + initialThemeData: initialThemeData, + themeService: mockThemeService, + ), + ); + + tester.state(find.byType(ZetaProvider)).updateThemeData(newThemeData); + await tester.pump(); + + // Verifying through the public interface of Zeta widget + final zeta = tester.widget(find.byType(Zeta)); + expect(zeta.themeData, newThemeData); + verify( + mockThemeService.saveTheme( + themeData: newThemeData, + themeMode: ThemeMode.system, + contrast: ZetaContrast.aa, + ), + ).called(1); + }); + + testWidgets('updateContrast updates the state correctly', (WidgetTester tester) async { + await tester.pumpWidget( + ZetaProvider( + builder: (context, themeData, themeMode) => Container(), + initialThemeData: initialThemeData, + themeService: mockThemeService, + ), + ); + + tester.state(find.byType(ZetaProvider)).updateContrast(ZetaContrast.aaa); + await tester.pump(); + + // Verifying through the public interface of Zeta widget + final zeta = tester.widget(find.byType(Zeta)); + expect(zeta.contrast, ZetaContrast.aaa); + verify( + mockThemeService.saveTheme( + themeData: initialThemeData.apply(contrast: ZetaContrast.aaa), + themeMode: ThemeMode.system, + contrast: ZetaContrast.aaa, + ), + ).called(1); + }); + + testWidgets('didUpdateWidget in ZetaProviderState works correctly with change in ThemeMode', + (WidgetTester tester) async { + await tester.pumpWidget( + ZetaProvider( + initialThemeMode: ThemeMode.light, + builder: (context, themeData, themeMode) => Builder( + builder: (context) { + return Container(); + }, + ), + ), + ); + + await tester.pumpAndSettle(); + // Verifying through the public interface of Zeta widget + expect(tester.widget(find.byType(Zeta)).themeMode, ThemeMode.light); + + await tester.pumpWidget( + ZetaProvider( + initialThemeMode: ThemeMode.dark, + builder: (context, themeData, themeMode) => Builder( + builder: (context) { + return Container(); + }, + ), + ), + ); + + await tester.pumpAndSettle(const Duration(milliseconds: 250)); + + // Verifying through the public interface of Zeta widget + expect(tester.widget(find.byType(Zeta)).themeMode, ThemeMode.dark); + }); + + testWidgets('didUpdateWidget in ZetaProviderState works correctly with change in contrast', + (WidgetTester tester) async { + await tester.pumpWidget( + ZetaProvider( + builder: (context, themeData, themeMode) => Builder( + builder: (context) { + return Container(); + }, + ), + ), + ); + + await tester.pumpAndSettle(); + + // Verifying through the public interface of Zeta widget + expect(tester.widget(find.byType(Zeta)).contrast, ZetaContrast.aa); + + await tester.pumpWidget( + ZetaProvider( + initialContrast: ZetaContrast.aaa, + builder: (context, themeData, themeMode) => Builder( + builder: (context) { + return Container(); + }, + ), + ), + ); + + await tester.pumpAndSettle(const Duration(milliseconds: 250)); + + // Verifying through the public interface of Zeta widget + expect(tester.widget(find.byType(Zeta)).contrast, ZetaContrast.aaa); + }); + + testWidgets('didUpdateWidget in ZetaProviderState works correctly with change in theme data', + (WidgetTester tester) async { + await tester.pumpWidget( + ZetaProvider( + initialThemeData: initialThemeData, + builder: (context, themeData, themeMode) => Builder( + builder: (context) { + return Container(); + }, + ), + ), + ); + + await tester.pumpAndSettle(); + + // Verifying through the public interface of Zeta widget + expect(tester.widget(find.byType(Zeta)).themeData.identifier, 'default'); + + await tester.pumpWidget( + ZetaProvider( + initialThemeData: ZetaThemeData(identifier: 'different'), + builder: (context, themeData, themeMode) => Builder( + builder: (context) { + return Container(); + }, + ), + ), + ); + + await tester.pumpAndSettle(const Duration(milliseconds: 250)); + + // Verifying through the public interface of Zeta widget + expect(tester.widget(find.byType(Zeta)).themeData.identifier, 'different'); + }); + + testWidgets('retrieves ZetaProviderState from context', (WidgetTester tester) async { + await tester.pumpWidget( + ZetaProvider( + builder: (context, themeData, themeMode) => Builder( + builder: (context) { + final providerState = ZetaProvider.of(context); + expect(providerState, isNotNull); + return Container(); + }, + ), + ), + ); + }); + + testWidgets('throws error if ZetaProvider is not found in widget tree', (WidgetTester tester) async { + await tester.pumpWidget( + Builder( + builder: (context) { + expect(() => ZetaProvider.of(context), throwsA(isA())); + return Container(); + }, + ), + ); + }); + + testWidgets('handles platform brightness changes', (WidgetTester tester) async { + await tester.pumpWidget( + ZetaProvider( + builder: (context, themeData, themeMode) => Container(), + initialThemeData: initialThemeData, + ), + ); + + // Rebuild the widget tree + await tester.pumpAndSettle(); + + // Get test binding + final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); + + // Simulate platform brightness change to dark + binding.platformDispatcher.platformBrightnessTestValue = Brightness.dark; + + await tester.pumpAndSettle(const Duration(milliseconds: 550)); + + // Verifying through the public interface of Zeta widget + final zeta = tester.widget(find.byType(Zeta)); + expect(zeta.brightness, Brightness.dark); + }); + + testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { + final diagnostics = DiagnosticPropertiesBuilder(); + + ZetaProvider( + builder: (context, themeData, themeMode) => Container(), + initialThemeData: initialThemeData, + themeService: mockThemeService, + ).debugFillProperties(diagnostics); + + final description = + diagnostics.properties.where((p) => p.name == 'themeData').map((p) => p.toDescription()).first; + expect(description, contains('ZetaThemeData')); + + final themeMode = + diagnostics.properties.where((p) => p.name == 'initialThemeMode').map((p) => p.toDescription()).first; + expect(themeMode, 'system'); + + final contrast = + diagnostics.properties.where((p) => p.name == 'initialContrast').map((p) => p.toDescription()).first; + expect(contrast, 'aa'); + + final themeService = + diagnostics.properties.where((p) => p.name == 'themeService').map((p) => p.toDescription()).first; + expect(themeService, isNotNull); + }); + + testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { + final diagnostics = DiagnosticPropertiesBuilder(); + + await tester.pumpWidget( + ZetaProvider( + builder: (context, themeData, themeMode) => Container(), + initialThemeData: initialThemeData, + ), + ); + + // Rebuild the widget tree + await tester.pumpAndSettle(); + + tester.state(find.byType(ZetaProvider)).debugFillProperties(diagnostics); + + final description = + diagnostics.properties.where((p) => p.name == 'themeData').map((p) => p.toDescription()).first; + expect(description, contains('ZetaThemeData')); + + final contrast = diagnostics.properties.where((p) => p.name == 'contrast').map((p) => p.toDescription()).first; + expect(contrast, 'aa'); + + final themeMode = diagnostics.properties.where((p) => p.name == 'themeMode').map((p) => p.toDescription()).first; + expect(themeMode, 'system'); + }); + }); +} diff --git a/test/src/zeta_provider_test.mocks.dart b/test/src/zeta_provider_test.mocks.dart new file mode 100644 index 00000000..dacd694b --- /dev/null +++ b/test/src/zeta_provider_test.mocks.dart @@ -0,0 +1,72 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in zeta_flutter/test/src/zeta_provider_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:flutter/material.dart' as _i5; +import 'package:mockito/mockito.dart' as _i1; +import 'package:zeta_flutter/src/theme/contrast.dart' as _i6; +import 'package:zeta_flutter/src/theme/theme_data.dart' as _i4; +import 'package:zeta_flutter/src/theme/theme_service.dart' as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [ZetaThemeService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockZetaThemeService extends _i1.Mock implements _i2.ZetaThemeService { + @override + _i3.Future<(_i4.ZetaThemeData?, _i5.ThemeMode?, _i6.ZetaContrast?)> + loadTheme() => (super.noSuchMethod( + Invocation.method( + #loadTheme, + [], + ), + returnValue: _i3.Future< + ( + _i4.ZetaThemeData?, + _i5.ThemeMode?, + _i6.ZetaContrast? + )>.value((null, null, null)), + returnValueForMissingStub: _i3.Future< + ( + _i4.ZetaThemeData?, + _i5.ThemeMode?, + _i6.ZetaContrast? + )>.value((null, null, null)), + ) as _i3 + .Future<(_i4.ZetaThemeData?, _i5.ThemeMode?, _i6.ZetaContrast?)>); + + @override + _i3.Future saveTheme({ + required _i4.ZetaThemeData? themeData, + required _i5.ThemeMode? themeMode, + required _i6.ZetaContrast? contrast, + }) => + (super.noSuchMethod( + Invocation.method( + #saveTheme, + [], + { + #themeData: themeData, + #themeMode: themeMode, + #contrast: contrast, + }, + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); +} diff --git a/test/src/zeta_test.dart b/test/src/zeta_test.dart new file mode 100644 index 00000000..83eda491 --- /dev/null +++ b/test/src/zeta_test.dart @@ -0,0 +1,176 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:zeta_flutter/src/theme/contrast.dart'; +import 'package:zeta_flutter/src/theme/theme_data.dart'; +import 'package:zeta_flutter/src/zeta.dart'; + +void main() { + group('Zeta InheritedWidget', () { + testWidgets('provides correct colors in light mode', (WidgetTester tester) async { + final themeData = ZetaThemeData(); + + await tester.pumpWidget( + Zeta( + mediaBrightness: Brightness.light, + contrast: ZetaContrast.aa, + themeMode: ThemeMode.light, + themeData: themeData, + child: Container(), + ), + ); + + await tester.pumpAndSettle(); + + final zeta = Zeta.of(tester.element(find.byType(Container))); + expect(zeta.colors, themeData.colorsLight); + }); + + testWidgets('provides correct colors in dark mode', (WidgetTester tester) async { + final themeData = ZetaThemeData(); + + await tester.pumpWidget( + Zeta( + mediaBrightness: Brightness.dark, + contrast: ZetaContrast.aa, + themeMode: ThemeMode.dark, + themeData: themeData, + child: Container(), + ), + ); + + await tester.pumpAndSettle(); + + final zeta = Zeta.of(tester.element(find.byType(Container))); + expect(zeta.colors, themeData.colorsDark); + }); + + testWidgets('provides correct colors in system mode with light media brightness', (WidgetTester tester) async { + final themeData = ZetaThemeData(); + + await tester.pumpWidget( + Zeta( + mediaBrightness: Brightness.light, + contrast: ZetaContrast.aa, + themeMode: ThemeMode.system, + themeData: themeData, + child: Container(), + ), + ); + + await tester.pumpAndSettle(); + + final zeta = Zeta.of(tester.element(find.byType(Container))); + expect(zeta.colors, themeData.colorsLight); + }); + + testWidgets('provides correct colors in system mode with dark media brightness', (WidgetTester tester) async { + final themeData = ZetaThemeData(); + + await tester.pumpWidget( + Zeta( + mediaBrightness: Brightness.dark, + contrast: ZetaContrast.aa, + themeMode: ThemeMode.system, + themeData: themeData, + child: Container(), + ), + ); + + await tester.pumpAndSettle(); + + final zeta = Zeta.of(tester.element(find.byType(Container))); + expect(zeta.colors, themeData.colorsDark); + }); + + testWidgets('throws FlutterError if Zeta is not found in widget tree', (WidgetTester tester) async { + await tester.pumpWidget(Container()); + await tester.pumpAndSettle(); + expect(() => Zeta.of(tester.element(find.byType(Container))), throwsA(isA())); + }); + }); + + group('Zeta properties', () { + testWidgets('brightness getter works correctly', (WidgetTester tester) async { + final themeData = ZetaThemeData(); + + await tester.pumpWidget( + Zeta( + mediaBrightness: Brightness.light, + contrast: ZetaContrast.aa, + themeMode: ThemeMode.system, + themeData: themeData, + child: Container(), + ), + ); + + final zeta = Zeta.of(tester.element(find.byType(Container))); + expect(zeta.brightness, Brightness.light); + + await tester.pumpWidget( + Zeta( + mediaBrightness: Brightness.dark, + contrast: ZetaContrast.aa, + themeMode: ThemeMode.dark, + themeData: themeData, + child: Builder( + builder: (context) { + return Container(); + }, + ), + ), + ); + + final zetaDark = Zeta.of(tester.element(find.byType(Container))); + expect(zetaDark.brightness, Brightness.dark); + + await tester.pumpWidget( + Zeta( + mediaBrightness: Brightness.light, + contrast: ZetaContrast.aa, + themeMode: ThemeMode.light, + themeData: themeData, + child: Container(), + ), + ); + + final zetaLight = Zeta.of(tester.element(find.byType(Container))); + expect(zetaLight.brightness, Brightness.light); + }); + + testWidgets('debugFillProperties works correctly', (WidgetTester tester) async { + final themeData = ZetaThemeData(); + + final diagnostics = DiagnosticPropertiesBuilder(); + Zeta( + mediaBrightness: Brightness.light, + contrast: ZetaContrast.aa, + themeMode: ThemeMode.system, + themeData: themeData, + child: Container(), + ).debugFillProperties(diagnostics); + + final description = diagnostics.properties.where((p) => p.name == 'contrast').map((p) => p.toDescription()).first; + expect(description, 'aa'); + + final themeMode = diagnostics.properties.where((p) => p.name == 'themeMode').map((p) => p.toDescription()).first; + expect(themeMode, 'system'); + + final themeDataDescription = + diagnostics.properties.where((p) => p.name == 'themeData').map((p) => p.toDescription()).first; + expect(themeDataDescription, contains('ZetaThemeData')); + + final colorsDescription = + diagnostics.properties.where((p) => p.name == 'colors').map((p) => p.toDescription()).first; + expect(colorsDescription, contains('ZetaColors')); + + final mediaBrightness = + diagnostics.properties.where((p) => p.name == 'mediaBrightness').map((p) => p.toDescription()).first; + expect(mediaBrightness, 'light'); + + final brightness = + diagnostics.properties.where((p) => p.name == 'brightness').map((p) => p.toDescription()).first; + expect(brightness, 'light'); + }); + }); +}