Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions packages/mix/lib/src/theme/tokens/token_refs.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// ABOUTME: Token references for types used by MixToken classes in the Mix theme.
// ABOUTME: Contains refs, extension types and utilities specifically for design tokens.
import 'dart:ui';

import 'package:flutter/widgets.dart';

import '../../core/breakpoint.dart';
import '../../core/directive.dart';
import '../../core/internal/compare_mixin.dart';
import '../../core/prop.dart';
import '../../core/prop_refs.dart';
Expand Down Expand Up @@ -40,6 +43,41 @@ To use as an actual $typeName value:
/// Token reference for [Color] values with directive support.
final class ColorRef extends Prop<Color> with ValueRef<Color> implements Color {
ColorRef(super.prop) : super.fromProp();

@override
Color withValues({
double? alpha,
double? red,
double? green,
double? blue,
ColorSpace? colorSpace,
}) => ColorRef(
directives([
WithValuesColorDirective(
alpha: alpha,
red: red,
green: green,
blue: blue,
colorSpace: colorSpace,
),
]),
);

@override
Color withAlpha(int a) => ColorRef(directives([AlphaColorDirective(a)]));

@override
Color withRed(int r) => ColorRef(directives([WithRedColorDirective(r)]));

@override
Color withGreen(int g) => ColorRef(directives([WithGreenColorDirective(g)]));

@override
Color withBlue(int b) => ColorRef(directives([WithBlueColorDirective(b)]));

@override
Color withOpacity(double opacity) =>
ColorRef(directives([OpacityColorDirective(opacity)]));
}

/// Token reference for [Radius] values
Expand Down
141 changes: 141 additions & 0 deletions packages/mix/test/src/core/prop_refs_test.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mix/src/core/directive.dart';
import 'package:mix/src/core/prop.dart';
import 'package:mix/src/theme/tokens/token_refs.dart';

Expand Down Expand Up @@ -93,6 +96,144 @@ void main() {
});
});

group('ColorRef Color method overrides', () {
final baseToken = TestToken<Color>('base-color');

test('withAlpha returns ColorRef with AlphaColorDirective', () {
final ref = ColorRef(Prop.token(baseToken));

final result = ref.withAlpha(128);

expect(result, isA<ColorRef>());
expect((result as ColorRef).$directives, [
const AlphaColorDirective(128),
]);
expect(result, PropMatcher.isToken(baseToken));
});

test('withRed returns ColorRef with WithRedColorDirective', () {
final ref = ColorRef(Prop.token(baseToken));

final result = ref.withRed(200);

expect(result, isA<ColorRef>());
expect((result as ColorRef).$directives, [
const WithRedColorDirective(200),
]);
expect(result, PropMatcher.isToken(baseToken));
});

test('withGreen returns ColorRef with WithGreenColorDirective', () {
final ref = ColorRef(Prop.token(baseToken));

final result = ref.withGreen(150);

expect(result, isA<ColorRef>());
expect((result as ColorRef).$directives, [
const WithGreenColorDirective(150),
]);
expect(result, PropMatcher.isToken(baseToken));
});

test('withBlue returns ColorRef with WithBlueColorDirective', () {
final ref = ColorRef(Prop.token(baseToken));

final result = ref.withBlue(75);

expect(result, isA<ColorRef>());
expect((result as ColorRef).$directives, [
const WithBlueColorDirective(75),
]);
expect(result, PropMatcher.isToken(baseToken));
});

test('withOpacity returns ColorRef with OpacityColorDirective', () {
final ref = ColorRef(Prop.token(baseToken));

// ignore: deprecated_member_use
final result = ref.withOpacity(0.5);

expect(result, isA<ColorRef>());
expect((result as ColorRef).$directives, [
const OpacityColorDirective(0.5),
]);
expect(result, PropMatcher.isToken(baseToken));
});

test(
'withValues returns ColorRef with WithValuesColorDirective carrying all args',
() {
final ref = ColorRef(Prop.token(baseToken));

final result = ref.withValues(
alpha: 0.5,
red: 0.1,
green: 0.2,
blue: 0.3,
colorSpace: ColorSpace.sRGB,
);

expect(result, isA<ColorRef>());
expect((result as ColorRef).$directives, [
const WithValuesColorDirective(
alpha: 0.5,
red: 0.1,
green: 0.2,
blue: 0.3,
colorSpace: ColorSpace.sRGB,
),
]);
expect(result, PropMatcher.isToken(baseToken));
},
);

test('chained calls accumulate directives in call order', () {
final ref = ColorRef(Prop.token(baseToken));

final result = ref.withAlpha(10).withRed(20).withBlue(30) as ColorRef;

expect(result.$directives, [
const AlphaColorDirective(10),
const WithRedColorDirective(20),
const WithBlueColorDirective(30),
]);
expect(result, PropMatcher.isToken(baseToken));
});

test('resolution applies directives after the token resolves', () {
final ref = ColorRef(Prop.token(baseToken));
const baseColor = Color(0xFF808080);
final context = MockBuildContext(tokens: {baseToken: baseColor});

final chained = ref.withAlpha(64).withRed(10) as ColorRef;

final resolved = chained.resolveProp(context);
final expected = const WithRedColorDirective(
10,
).apply(const AlphaColorDirective(64).apply(baseColor));

expect(resolved, equals(expected));
});

test('accessors still throw via noSuchMethod (regression guard)', () {
final ref = ColorRef(Prop.token(baseToken));

expect(
() => (ref as dynamic).alpha,
throwsA(isA<UnimplementedError>()),
);
expect(() => (ref as dynamic).red, throwsA(isA<UnimplementedError>()));
expect(
() => (ref as dynamic).opacity,
throwsA(isA<UnimplementedError>()),
);
expect(
() => (ref as dynamic).value,
throwsA(isA<UnimplementedError>()),
);
});
});

group('Extension Type Token References', () {
group('DoubleRef', () {
test('creates from token using hybrid hashing', () {
Expand Down
111 changes: 111 additions & 0 deletions packages/mix/test/src/theme/tokens/color_token_integration_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mix/mix.dart';

/// End-to-end tests for the fluent directive API on [ColorRef]:
/// `colorToken().withAlpha(...)`, `.withValues(...)`, `.withOpacity(...)`, etc.
/// chained into a [BoxStyler] and rendered through [Box] + [MixScope].
void main() {
group('ColorToken + ColorRef fluent directive integration', () {
testWidgets(
'withAlpha applies to the token-resolved color at render time',
(tester) async {
const token = ColorToken('primary');
const baseColor = Color(0xFF112233);

final style = BoxStyler().color(token().withAlpha(128));

await tester.pumpWidget(
MixScope(
tokens: {token: baseColor},
child: Directionality(
textDirection: TextDirection.ltr,
child: Box(style: style),
),
),
);

final container = tester.widget<Container>(find.byType(Container));
final decoration = container.decoration! as BoxDecoration;
expect(decoration.color, baseColor.withAlpha(128));
},
);

testWidgets('chained directives apply in call order', (tester) async {
const token = ColorToken('primary');
const baseColor = Color(0xFF80A0C0);

final style = BoxStyler().color(
token().withRed(10).withGreen(20).withBlue(30),
);

await tester.pumpWidget(
MixScope(
tokens: {token: baseColor},
child: Directionality(
textDirection: TextDirection.ltr,
child: Box(style: style),
),
),
);

final container = tester.widget<Container>(find.byType(Container));
final decoration = container.decoration! as BoxDecoration;
final expected = baseColor.withRed(10).withGreen(20).withBlue(30);
expect(decoration.color, expected);
});

testWidgets('withValues applies at render time with multiple channels', (
tester,
) async {
const token = ColorToken('primary');
const baseColor = Color(0xFF336699);

final style = BoxStyler().color(
token().withValues(alpha: 0.25, red: 0.5),
);

await tester.pumpWidget(
MixScope(
tokens: {token: baseColor},
child: Directionality(
textDirection: TextDirection.ltr,
child: Box(style: style),
),
),
);

final container = tester.widget<Container>(find.byType(Container));
final decoration = container.decoration! as BoxDecoration;
expect(decoration.color, baseColor.withValues(alpha: 0.25, red: 0.5));
});

testWidgets('withOpacity applies via OpacityColorDirective', (
tester,
) async {
const token = ColorToken('primary');
const baseColor = Color(0xFFFF0000);

// OpacityColorDirective is implemented as withValues(alpha: opacity),
// so the expected output matches that, not the deprecated withOpacity.
final style = BoxStyler().color(
// ignore: deprecated_member_use
token().withOpacity(0.5),
);

await tester.pumpWidget(
MixScope(
tokens: {token: baseColor},
child: Directionality(
textDirection: TextDirection.ltr,
child: Box(style: style),
),
),
);

final container = tester.widget<Container>(find.byType(Container));
final decoration = container.decoration! as BoxDecoration;
expect(decoration.color, baseColor.withValues(alpha: 0.5));
});
});
}
Loading