diff --git a/packages/mix/lib/src/specs/box/box_style.dart b/packages/mix/lib/src/specs/box/box_style.dart index c2e0a54c2..6e9ab9980 100644 --- a/packages/mix/lib/src/specs/box/box_style.dart +++ b/packages/mix/lib/src/specs/box/box_style.dart @@ -19,6 +19,7 @@ import '../../style/mixins/constraint_style_mixin.dart'; import '../../style/mixins/decoration_style_mixin.dart'; import '../../style/mixins/shadow_style_mixin.dart'; import '../../style/mixins/spacing_style_mixin.dart'; +import '../../style/mixins/token_style_mixin.dart'; import '../../style/mixins/transform_style_mixin.dart'; import '../../style/mixins/variant_style_mixin.dart'; import '../../style/mixins/widget_modifier_style_mixin.dart'; @@ -48,7 +49,8 @@ class BoxStyler extends Style SpacingStyleMixin, TransformStyleMixin, ConstraintStyleMixin, - AnimationStyleMixin { + AnimationStyleMixin, + TokenStyleMixin { final Prop? $alignment; final Prop? $padding; final Prop? $margin; diff --git a/packages/mix/lib/src/specs/flex/flex_style.dart b/packages/mix/lib/src/specs/flex/flex_style.dart index 3b0bd9794..9bdc8e8cd 100644 --- a/packages/mix/lib/src/specs/flex/flex_style.dart +++ b/packages/mix/lib/src/specs/flex/flex_style.dart @@ -10,6 +10,7 @@ import '../../core/style_spec.dart'; import '../../modifiers/widget_modifier_config.dart'; import '../../style/mixins/animation_style_mixin.dart'; import '../../style/mixins/flex_style_mixin.dart'; +import '../../style/mixins/token_style_mixin.dart'; import '../../style/mixins/variant_style_mixin.dart'; import '../../style/mixins/widget_modifier_style_mixin.dart'; import '../../style/mixins/widget_state_variant_mixin.dart'; @@ -34,7 +35,8 @@ class FlexStyler extends Style VariantStyleMixin, WidgetStateVariantMixin, FlexStyleMixin, - AnimationStyleMixin { + AnimationStyleMixin, + TokenStyleMixin { final Prop? $direction; final Prop? $mainAxisAlignment; final Prop? $crossAxisAlignment; diff --git a/packages/mix/lib/src/specs/flexbox/flexbox_style.dart b/packages/mix/lib/src/specs/flexbox/flexbox_style.dart index 8a2148bbf..134a4649e 100644 --- a/packages/mix/lib/src/specs/flexbox/flexbox_style.dart +++ b/packages/mix/lib/src/specs/flexbox/flexbox_style.dart @@ -12,14 +12,15 @@ import '../../properties/layout/edge_insets_geometry_mix.dart'; import '../../properties/painting/border_mix.dart'; import '../../properties/painting/border_radius_mix.dart'; import '../../properties/painting/decoration_mix.dart'; +import '../../style/mixins/animation_style_mixin.dart'; import '../../style/mixins/border_radius_style_mixin.dart'; import '../../style/mixins/border_style_mixin.dart'; import '../../style/mixins/constraint_style_mixin.dart'; import '../../style/mixins/decoration_style_mixin.dart'; -import '../../style/mixins/animation_style_mixin.dart'; import '../../style/mixins/flex_style_mixin.dart'; import '../../style/mixins/shadow_style_mixin.dart'; import '../../style/mixins/spacing_style_mixin.dart'; +import '../../style/mixins/token_style_mixin.dart'; import '../../style/mixins/transform_style_mixin.dart'; import '../../style/mixins/variant_style_mixin.dart'; import '../../style/mixins/widget_modifier_style_mixin.dart'; @@ -56,7 +57,8 @@ class FlexBoxStyler extends Style TransformStyleMixin, ConstraintStyleMixin, FlexStyleMixin, - AnimationStyleMixin { + AnimationStyleMixin, + TokenStyleMixin { final Prop>? $box; final Prop>? $flex; @@ -131,12 +133,6 @@ class FlexBoxStyler extends Style static FlexBoxMutableStyler get chain => FlexBoxMutableStyler(FlexBoxStyler()); - /// Sets the animation property. - @override - FlexBoxStyler animate(AnimationConfig animation) { - return merge(FlexBoxStyler(animation: animation)); - } - // BoxMix instance methods /// Sets the alignment property. @@ -173,6 +169,12 @@ class FlexBoxStyler extends Style return FlexBox(key: key, style: this, children: children); } + /// Sets the animation property. + @override + FlexBoxStyler animate(AnimationConfig animation) { + return merge(FlexBoxStyler(animation: animation)); + } + /// Sets the foreground decoration. @override FlexBoxStyler foregroundDecoration(DecorationMix value) { diff --git a/packages/mix/lib/src/specs/icon/icon_style.dart b/packages/mix/lib/src/specs/icon/icon_style.dart index 01bfd6871..ff66d78df 100644 --- a/packages/mix/lib/src/specs/icon/icon_style.dart +++ b/packages/mix/lib/src/specs/icon/icon_style.dart @@ -9,6 +9,7 @@ import '../../core/style_spec.dart'; import '../../modifiers/widget_modifier_config.dart'; import '../../properties/painting/shadow_mix.dart'; import '../../style/mixins/animation_style_mixin.dart'; +import '../../style/mixins/token_style_mixin.dart'; import '../../style/mixins/variant_style_mixin.dart'; import '../../style/mixins/widget_modifier_style_mixin.dart'; import '../../style/mixins/widget_state_variant_mixin.dart'; @@ -25,7 +26,8 @@ class IconStyler extends Style WidgetModifierStyleMixin, VariantStyleMixin, WidgetStateVariantMixin, - AnimationStyleMixin { + AnimationStyleMixin, + TokenStyleMixin { final Prop? $color; final Prop? $size; final Prop? $weight; diff --git a/packages/mix/lib/src/specs/image/image_style.dart b/packages/mix/lib/src/specs/image/image_style.dart index b44f92349..c36742660 100644 --- a/packages/mix/lib/src/specs/image/image_style.dart +++ b/packages/mix/lib/src/specs/image/image_style.dart @@ -8,6 +8,7 @@ import '../../core/style.dart'; import '../../core/style_spec.dart'; import '../../modifiers/widget_modifier_config.dart'; import '../../style/mixins/animation_style_mixin.dart'; +import '../../style/mixins/token_style_mixin.dart'; import '../../style/mixins/variant_style_mixin.dart'; import '../../style/mixins/widget_modifier_style_mixin.dart'; import '../../style/mixins/widget_state_variant_mixin.dart'; @@ -24,7 +25,8 @@ class ImageStyler extends Style WidgetModifierStyleMixin, VariantStyleMixin, WidgetStateVariantMixin, - AnimationStyleMixin { + AnimationStyleMixin, + TokenStyleMixin { final Prop>? $image; final Prop? $width; final Prop? $height; diff --git a/packages/mix/lib/src/specs/stack/stack_style.dart b/packages/mix/lib/src/specs/stack/stack_style.dart index 7c413bd1a..c140743fd 100644 --- a/packages/mix/lib/src/specs/stack/stack_style.dart +++ b/packages/mix/lib/src/specs/stack/stack_style.dart @@ -8,6 +8,7 @@ import '../../core/style.dart'; import '../../core/style_spec.dart'; import '../../modifiers/widget_modifier_config.dart'; import '../../style/mixins/animation_style_mixin.dart'; +import '../../style/mixins/token_style_mixin.dart'; import '../../style/mixins/variant_style_mixin.dart'; import '../../style/mixins/widget_modifier_style_mixin.dart'; import '../../style/mixins/widget_state_variant_mixin.dart'; @@ -30,7 +31,8 @@ class StackStyler extends Style WidgetModifierStyleMixin, VariantStyleMixin, WidgetStateVariantMixin, - AnimationStyleMixin { + AnimationStyleMixin, + TokenStyleMixin { final Prop? $alignment; final Prop? $fit; final Prop? $textDirection; diff --git a/packages/mix/lib/src/specs/stackbox/stackbox_style.dart b/packages/mix/lib/src/specs/stackbox/stackbox_style.dart index c0d96c928..164023198 100644 --- a/packages/mix/lib/src/specs/stackbox/stackbox_style.dart +++ b/packages/mix/lib/src/specs/stackbox/stackbox_style.dart @@ -19,6 +19,7 @@ import '../../style/mixins/constraint_style_mixin.dart'; import '../../style/mixins/decoration_style_mixin.dart'; import '../../style/mixins/shadow_style_mixin.dart'; import '../../style/mixins/spacing_style_mixin.dart'; +import '../../style/mixins/token_style_mixin.dart'; import '../../style/mixins/transform_style_mixin.dart'; import '../../style/mixins/variant_style_mixin.dart'; import '../../style/mixins/widget_modifier_style_mixin.dart'; @@ -54,7 +55,8 @@ class StackBoxStyler extends Style SpacingStyleMixin, TransformStyleMixin, ConstraintStyleMixin, - AnimationStyleMixin { + AnimationStyleMixin, + TokenStyleMixin { final Prop>? $box; final Prop>? $stack; @@ -119,12 +121,6 @@ class StackBoxStyler extends Style static StackBoxMutableStyler get chain => StackBoxMutableStyler(StackBoxStyler()); - /// Sets animation - @override - StackBoxStyler animate(AnimationConfig animation) { - return merge(StackBoxStyler(animation: animation)); - } - // BoxMix instance methods /// Sets the alignment for the box. @@ -177,6 +173,12 @@ class StackBoxStyler extends Style return merge(StackBoxStyler.create(stack: Prop.maybeMix(value))); } + /// Sets animation + @override + StackBoxStyler animate(AnimationConfig animation) { + return merge(StackBoxStyler(animation: animation)); + } + /// Foreground decoration instance method @override StackBoxStyler foregroundDecoration(DecorationMix value) { diff --git a/packages/mix/lib/src/specs/text/text_style.dart b/packages/mix/lib/src/specs/text/text_style.dart index 9266bc630..3cb49cc98 100644 --- a/packages/mix/lib/src/specs/text/text_style.dart +++ b/packages/mix/lib/src/specs/text/text_style.dart @@ -13,6 +13,7 @@ import '../../properties/typography/text_height_behavior_mix.dart'; import '../../properties/typography/text_style_mix.dart'; import '../../style/mixins/animation_style_mixin.dart'; import '../../style/mixins/text_style_mixin.dart'; +import '../../style/mixins/token_style_mixin.dart'; import '../../style/mixins/variant_style_mixin.dart'; import '../../style/mixins/widget_modifier_style_mixin.dart'; import '../../style/mixins/widget_state_variant_mixin.dart'; @@ -37,7 +38,8 @@ class TextStyler extends Style VariantStyleMixin, WidgetStateVariantMixin, TextStyleMixin, - AnimationStyleMixin { + AnimationStyleMixin, + TokenStyleMixin { final Prop? $overflow; final Prop? $strutStyle; final Prop? $textAlign; diff --git a/packages/mix/lib/src/style/mixins/token_style_mixin.dart b/packages/mix/lib/src/style/mixins/token_style_mixin.dart new file mode 100644 index 000000000..d81659b44 --- /dev/null +++ b/packages/mix/lib/src/style/mixins/token_style_mixin.dart @@ -0,0 +1,31 @@ +import '../../core/spec.dart'; +import '../../core/style.dart'; +import '../../theme/tokens/mix_token.dart'; +import 'variant_style_mixin.dart'; + +mixin TokenStyleMixin, S extends Spec> + on VariantStyleMixin { + /// Applies styling based on a resolved [MixToken] value. + /// + /// The [token] is resolved against the current [BuildContext] at runtime, + /// and the given [builder] function is used to produce a style based on the value. + /// + /// Example: + /// ```dart + /// BoxStyler() + /// .useToken($primary, BoxStyler().color); + /// ``` + /// + /// ```dart + /// BoxStyler() + /// .useToken($primary, (color) => BoxStyler().color(color)); + /// ``` + /// + /// This enables context-aware design tokens (such as colors or spacing) + /// to drive dynamic styling, including theming and design system integration. + T useToken(MixToken token, T Function(U value) builder) { + return onBuilder((context) { + return builder(token.resolve(context)); + }); + } +} diff --git a/packages/mix/lib/src/style/mixins/variant_style_mixin.dart b/packages/mix/lib/src/style/mixins/variant_style_mixin.dart index 1cec2f9a3..5b07d9573 100644 --- a/packages/mix/lib/src/style/mixins/variant_style_mixin.dart +++ b/packages/mix/lib/src/style/mixins/variant_style_mixin.dart @@ -94,19 +94,19 @@ mixin VariantStyleMixin, S extends Spec> on Style { } /// Creates a variant for mobile breakpoint. - T onMobile(T style) { - return variant(ContextVariant.mobile(), style); - } - - /// Creates a variant for tablet breakpoint. - T onTablet(T style) { - return variant(ContextVariant.tablet(), style); - } - - /// Creates a variant for desktop breakpoint. - T onDesktop(T style) { - return variant(ContextVariant.desktop(), style); - } + // T onMobile(T style) { + // return variant(ContextVariant.mobile(), style); + // } + + // /// Creates a variant for tablet breakpoint. + // T onTablet(T style) { + // return variant(ContextVariant.tablet(), style); + // } + + // /// Creates a variant for desktop breakpoint. + // T onDesktop(T style) { + // return variant(ContextVariant.desktop(), style); + // } /// Creates a variant for left-to-right text direction. T onLtr(T style) { diff --git a/packages/mix/lib/src/theme/tokens/mix_token.dart b/packages/mix/lib/src/theme/tokens/mix_token.dart index 175ed03b9..e9bbb95c8 100644 --- a/packages/mix/lib/src/theme/tokens/mix_token.dart +++ b/packages/mix/lib/src/theme/tokens/mix_token.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import '../mix_theme.dart'; -import 'token_refs.dart'; /// A design token that resolves to a value within a Mix theme. /// @@ -12,11 +11,6 @@ abstract class MixToken { final String name; const MixToken(this.name); - /// Returns a reference value for Mix utilities. - T call() { - return getReferenceValue(this); - } - /// Resolves this token to a concrete value. T resolve(BuildContext context) { return MixScope.tokenOf(this, context); diff --git a/packages/mix/lib/src/theme/tokens/value_tokens.dart b/packages/mix/lib/src/theme/tokens/value_tokens.dart index 18a5514f6..bc493bb84 100644 --- a/packages/mix/lib/src/theme/tokens/value_tokens.dart +++ b/packages/mix/lib/src/theme/tokens/value_tokens.dart @@ -5,40 +5,27 @@ import 'package:flutter/material.dart'; import '../../core/breakpoint.dart'; import '../../core/prop.dart'; -import '../../core/prop_refs.dart'; import 'mix_token.dart'; import 'token_refs.dart'; /// Design token for [Color] values. class ColorToken extends MixToken { const ColorToken(super.name); - - @override - ColorRef call() => ColorRef(Prop.token(this)); } /// Design token for [Radius] values. class RadiusToken extends MixToken { const RadiusToken(super.name); - - @override - RadiusRef call() => RadiusRef(Prop.token(this)); } /// Design token for spacing values. class SpaceToken extends MixToken { const SpaceToken(super.name); - - @override - double call() => DoubleRef.token(this); } /// Design token for general [double] values. class DoubleToken extends MixToken { const DoubleToken(super.name); - - @override - double call() => DoubleRef.token(this); } /// Design token for [Breakpoint] values. @@ -49,9 +36,6 @@ class BreakpointToken extends MixToken { const BreakpointToken(super.name); - @override - BreakpointRef call() => BreakpointRef(this); - @override Breakpoint resolve(BuildContext context) { try { @@ -77,17 +61,11 @@ class TextStyleToken extends MixToken { /// Returns a Mix framework compatible reference for use with Mix styling utilities. TextStyleMixRef mix() => TextStyleMixRef(Prop.token(this)); - - @override - TextStyleRef call() => TextStyleRef(Prop.token(this)); } /// Design token for [BorderSide] values. class BorderSideToken extends MixToken { const BorderSideToken(super.name); - - @override - BorderSideRef call() => BorderSideRef(Prop.token(this)); } /// Design token for shadow lists. @@ -96,9 +74,6 @@ class ShadowToken extends MixToken> { /// Returns a Mix framework compatible reference for use with Mix styling utilities. ShadowListMixRef mix() => ShadowListMixRef(Prop.token(this)); - - @override - ShadowListRef call() => ShadowListRef(Prop.token(this)); } /// Design token for box shadow lists. @@ -107,23 +82,14 @@ class BoxShadowToken extends MixToken> { /// Returns a Mix framework compatible reference for use with Mix styling utilities. BoxShadowListMixRef mix() => BoxShadowListMixRef(Prop.token(this)); - - @override - BoxShadowListRef call() => BoxShadowListRef(Prop.token(this)); } /// Design token for [FontWeight] values. class FontWeightToken extends MixToken { const FontWeightToken(super.name); - - @override - FontWeightRef call() => FontWeightRef(Prop.token(this)); } /// Design token for [Duration] values. class DurationToken extends MixToken { const DurationToken(super.name); - - @override - DurationRef call() => DurationRef(Prop.token(this)); } diff --git a/packages/mix/lib/src/variants/variant.dart b/packages/mix/lib/src/variants/variant.dart index 8e2e4175b..45ecfb91e 100644 --- a/packages/mix/lib/src/variants/variant.dart +++ b/packages/mix/lib/src/variants/variant.dart @@ -6,7 +6,6 @@ import '../core/providers/widget_state_provider.dart'; import '../core/spec.dart'; import '../core/style.dart'; import '../theme/tokens/token_refs.dart'; -import '../theme/tokens/value_tokens.dart'; /// Base class for all variant types. @immutable @@ -115,18 +114,18 @@ class ContextVariant extends Variant { return ContextVariant('web', (_) => kIsWeb); } - // Responsive breakpoints - static ContextVariant mobile() { - return ContextVariant.breakpoint(BreakpointToken.mobile()); - } + // // Responsive breakpoints + // static ContextVariant mobile() { + // return ContextVariant.breakpoint(BreakpointToken.mobile()); + // } - static ContextVariant tablet() { - return ContextVariant.breakpoint(BreakpointToken.tablet()); - } + // static ContextVariant tablet() { + // return ContextVariant.breakpoint(BreakpointToken.tablet()); + // } - static ContextVariant desktop() { - return ContextVariant.breakpoint(BreakpointToken.desktop()); - } + // static ContextVariant desktop() { + // return ContextVariant.breakpoint(BreakpointToken.desktop()); + // } /// Check if this variant should be active for the given context bool when(BuildContext context) { diff --git a/packages/mix/lib/src/variants/variant_util.dart b/packages/mix/lib/src/variants/variant_util.dart index d1af15e09..d5167aa56 100644 --- a/packages/mix/lib/src/variants/variant_util.dart +++ b/packages/mix/lib/src/variants/variant_util.dart @@ -96,19 +96,19 @@ class OnContextVariantUtility, T extends Style> } /// Creates a variant attribute for mobile size - VariantAttributeBuilder get mobile { - return VariantAttributeBuilder(ContextVariant.mobile()); - } - - /// Creates a variant attribute for tablet size - VariantAttributeBuilder get tablet { - return VariantAttributeBuilder(ContextVariant.tablet()); - } - - /// Creates a variant attribute for desktop size - VariantAttributeBuilder get desktop { - return VariantAttributeBuilder(ContextVariant.desktop()); - } + // VariantAttributeBuilder get mobile { + // return VariantAttributeBuilder(ContextVariant.mobile()); + // } + + // /// Creates a variant attribute for tablet size + // VariantAttributeBuilder get tablet { + // return VariantAttributeBuilder(ContextVariant.tablet()); + // } + + // /// Creates a variant attribute for desktop size + // VariantAttributeBuilder get desktop { + // return VariantAttributeBuilder(ContextVariant.desktop()); + // } /// Creates a variant attribute for left-to-right direction VariantAttributeBuilder get ltr { diff --git a/packages/mix/test/src/style/token_style_mixin_test.dart b/packages/mix/test/src/style/token_style_mixin_test.dart new file mode 100644 index 000000000..ff6f05812 --- /dev/null +++ b/packages/mix/test/src/style/token_style_mixin_test.dart @@ -0,0 +1,398 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mix/mix.dart'; + +import '../../helpers/testing_utils.dart'; + +extension TokenTestHelper on WidgetTester { + /// Pumps a widget wrapped with [MixScope] and the provided tokens. + /// + /// Use [useDirectionality] when testing widgets that require text direction + /// (e.g., FlexBox, StackBox, Icon). + /// Use [useMaterialApp] when testing widgets that require Material context + /// (e.g., StyledText). + Future pumpWithTokens( + Map tokens, { + required Widget child, + bool useDirectionality = false, + bool useMaterialApp = false, + }) { + Widget content = MixScope(tokens: tokens, child: child); + + if (useMaterialApp) { + content = MaterialApp(home: content); + } else if (useDirectionality) { + content = Directionality( + textDirection: TextDirection.ltr, + child: content, + ); + } + + return pumpWidget(content); + } +} + +void main() { + group('TokenStyleMixin', () { + group('useToken', () { + test('resolves token value when builder is called', () { + const colorToken = ColorToken('test.color'); + const testColor = Colors.red; + Color? capturedColor; + + final style = + BoxStyler() // + .useToken(colorToken, (color) { + capturedColor = color; + return BoxStyler().color(color); + }); + + // Get the variant builder and execute it + final variantBuilder = + style.$variants!.first.variant as ContextVariantBuilder; + + final mockContext = MockBuildContext(tokens: {colorToken: testColor}); + variantBuilder.build(mockContext); + + expect(capturedColor, equals(testColor)); + }); + + test('can be chained with other style methods', () { + const colorToken = ColorToken('test.color'); + + final style = BoxStyler() + .width(100) + .useToken(colorToken, BoxStyler().color) + .height(200); + + // Should have width set + expect(style.$constraints, isNotNull); + + // Should have variant + expect(style.$variants, isNotNull); + expect(style.$variants!.length, 1); + }); + + test('can be used with different token types', () { + const doubleToken = DoubleToken('test.space'); + const testValue = 16.0; + + final style = + BoxStyler() // + .useToken(doubleToken, BoxStyler().paddingAll); + + expect(style.$variants, isNotNull); + expect(style.$variants!.first.variant, isA()); + + // Verify the builder uses the token value + final variantBuilder = + style.$variants!.first.variant as ContextVariantBuilder; + final mockContext = MockBuildContext(tokens: {doubleToken: testValue}); + + final builtStyle = variantBuilder.build(mockContext); + expect(builtStyle.$padding, isNotNull); + }); + + test('multiple useToken calls create multiple variants', () { + const colorToken = ColorToken('test.color'); + const spaceToken = SpaceToken('test.space'); + + final style = BoxStyler() + .useToken(colorToken, BoxStyler().color) + .useToken(spaceToken, BoxStyler().paddingAll); + + expect(style.$variants, isNotNull); + expect(style.$variants!.length, 2); + + final mockContext = MockBuildContext( + tokens: {colorToken: Colors.red, spaceToken: 16.0}, + ); + + final resolvedStyle = style.$variants! + .map((v) => v.variant as ContextVariantBuilder) + .map((v) => v.build(mockContext)) + .reduce((a, b) => a.merge(b)) + .resolve(mockContext); + + expect( + (resolvedStyle.spec.decoration as BoxDecoration).color, + equals(Colors.red), + ); + expect(resolvedStyle.spec.padding, equals(EdgeInsets.all(16.0))); + }); + }); + + group('useToken widget integration', () { + testWidgets('resolves token in widget tree', (tester) async { + const colorToken = ColorToken('test.primary'); + const testColor = Colors.blue; + + final style = BoxStyler() + .width(100) + .height(100) + .useToken(colorToken, BoxStyler().color); + + await tester.pumpWithTokens({ + colorToken: testColor, + }, child: Box(style: style)); + + // Find the Container and verify its decoration + final container = tester.widget(find.byType(Container)); + final decoration = container.decoration as BoxDecoration?; + expect(decoration?.color, equals(testColor)); + }); + + testWidgets('updates when token value changes', (tester) async { + const colorToken = ColorToken('test.primary'); + final tokenNotifier = ValueNotifier(Colors.red); + + final style = BoxStyler() + .width(100) + .height(100) + .useToken(colorToken, BoxStyler().color); + + await tester.pumpWidget( + ValueListenableBuilder( + valueListenable: tokenNotifier, + builder: (context, color, _) { + return MixScope( + tokens: {colorToken: color}, + child: Box(style: style), + ); + }, + ), + ); + + // Verify initial color + var container = tester.widget(find.byType(Container)); + var decoration = container.decoration as BoxDecoration?; + expect(decoration?.color, equals(Colors.red)); + + // Update token value + tokenNotifier.value = Colors.green; + await tester.pump(); + + // Verify updated color + container = tester.widget(find.byType(Container)); + decoration = container.decoration as BoxDecoration?; + expect(decoration?.color, equals(Colors.green)); + }); + + testWidgets('works with TextStyler', (tester) async { + const colorToken = ColorToken('test.text'); + const testColor = Colors.purple; + + final style = TextStyler().useToken( + colorToken, + (color) => TextStyler().color(color), + ); + + await tester.pumpWithTokens( + {colorToken: testColor}, + useMaterialApp: true, + child: StyledText('Hello', style: style), + ); + + // Find the Text widget and verify color + final text = tester.widget(find.byType(Text)); + expect(text.style?.color, equals(testColor)); + }); + + testWidgets('works alongside other variants', (tester) async { + const colorToken = ColorToken('test.primary'); + const testColor = Colors.blue; + + final style = BoxStyler() + .width(100) + .height(100) + .color(Colors.grey) // default color + .useToken(colorToken, (color) => BoxStyler().color(color)) + .onDark(BoxStyler().color(Colors.black)); + + await tester.pumpWithTokens({ + colorToken: testColor, + }, child: Box(style: style)); + + // Token should be applied (overriding default grey) + final container = tester.widget(find.byType(Container)); + final decoration = container.decoration as BoxDecoration?; + expect(decoration?.color, equals(testColor)); + }); + }); + + group('useToken widget tests for all Stylers', () { + // These widget tests verify that useToken resolves DoubleToken values + // correctly in the widget tree for all Styler types. + + testWidgets('BoxStyler resolves DoubleToken for width', (tester) async { + const sizeToken = DoubleToken('test.size'); + const testSize = 150.0; + + final style = BoxStyler().useToken(sizeToken, BoxStyler().width); + + await tester.pumpWithTokens({ + sizeToken: testSize, + }, child: Box(style: style)); + + final container = tester.widget(find.byType(Container)); + expect(container.constraints?.maxWidth, equals(testSize)); + expect(container.constraints?.minWidth, equals(testSize)); + }); + + testWidgets('FlexBoxStyler resolves DoubleToken for spacing', ( + tester, + ) async { + const spacingToken = DoubleToken('test.spacing'); + const testSpacing = 24.0; + + final style = FlexBoxStyler().row().useToken( + spacingToken, + FlexBoxStyler().spacing, + ); + + await tester.pumpWithTokens( + {spacingToken: testSpacing}, + useDirectionality: true, + child: FlexBox( + style: style, + children: const [SizedBox(width: 10), SizedBox(width: 10)], + ), + ); + + // Verify the flex renders without error with token-resolved spacing + expect(find.byType(FlexBox), findsOneWidget); + }); + + testWidgets('StackBoxStyler resolves DoubleToken for padding', ( + tester, + ) async { + const paddingToken = DoubleToken('test.padding'); + const testPadding = 16.0; + + final style = StackBoxStyler().useToken( + paddingToken, + StackBoxStyler().paddingAll, + ); + + await tester.pumpWithTokens( + {paddingToken: testPadding}, + useDirectionality: true, + child: StackBox( + style: style, + children: const [SizedBox(width: 50, height: 50)], + ), + ); + + final padding = tester.widget(find.byType(Padding)); + expect(padding.padding, equals(EdgeInsets.all(testPadding))); + }); + + testWidgets('TextStyler resolves DoubleToken for fontSize', ( + tester, + ) async { + const fontSizeToken = DoubleToken('test.fontSize'); + const testFontSize = 24.0; + + final style = TextStyler().useToken( + fontSizeToken, + TextStyler().fontSize, + ); + + await tester.pumpWithTokens( + {fontSizeToken: testFontSize}, + useMaterialApp: true, + child: StyledText('Hello', style: style), + ); + + final text = tester.widget(find.byType(Text)); + expect(text.style?.fontSize, equals(testFontSize)); + }); + + testWidgets('IconStyler resolves DoubleToken for size', (tester) async { + const sizeToken = DoubleToken('test.iconSize'); + const testSize = 48.0; + + final style = IconStyler().useToken(sizeToken, IconStyler().size); + + await tester.pumpWithTokens( + {sizeToken: testSize}, + useDirectionality: true, + child: StyledIcon(icon: Icons.star, style: style), + ); + + final icon = tester.widget(find.byType(Icon)); + expect(icon.size, equals(testSize)); + }); + + testWidgets('FlexStyler resolves DoubleToken for spacing', ( + tester, + ) async { + const spacingToken = DoubleToken('test.flexSpacing'); + const testSpacing = 12.0; + + final style = FlexStyler().row().useToken( + spacingToken, + FlexStyler().spacing, + ); + + // FlexStyler is typically used via FlexBoxStyler, but we can test + // that the token resolution works by checking the style compiles + // and a widget with this configuration renders + await tester.pumpWithTokens( + {spacingToken: testSpacing}, + useDirectionality: true, + child: FlexBox( + style: FlexBoxStyler().flex(style), + children: const [SizedBox(width: 10), SizedBox(width: 10)], + ), + ); + + expect(find.byType(FlexBox), findsOneWidget); + }); + + testWidgets('StackStyler resolves DoubleToken via StackBoxStyler', ( + tester, + ) async { + // StackStyler doesn't have double properties directly, but we can + // verify useToken works by testing via StackBoxStyler integration + const sizeToken = DoubleToken('test.stackSize'); + const testSize = 200.0; + + final style = StackBoxStyler().useToken( + sizeToken, + StackBoxStyler().width, + ); + + await tester.pumpWithTokens( + {sizeToken: testSize}, + useDirectionality: true, + child: StackBox( + style: style, + children: const [SizedBox(width: 50, height: 50)], + ), + ); + + final constrainedBox = tester.widget( + find.byType(ConstrainedBox), + ); + expect(constrainedBox.constraints.maxWidth, equals(testSize)); + }); + + testWidgets('ImageStyler resolves DoubleToken for width', (tester) async { + const widthToken = DoubleToken('test.imageWidth'); + const testWidth = 100.0; + + final style = ImageStyler().useToken(widthToken, ImageStyler().width); + + await tester.pumpWithTokens( + {widthToken: testWidth}, + useDirectionality: true, + child: StyledImage(style: style, image: mockImageProvider()), + ); + + final image = tester.widget(find.byType(Image)); + expect(image.width, equals(testWidth)); + }); + }); + }); +}