blob: 7920abe89a68fc6d9e550d42a02aacc6c4427e1e [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
void main() {
test('SliderThemeData copyWith, ==, hashCode basics', () {
expect(const SliderThemeData(), const SliderThemeData().copyWith());
expect(const SliderThemeData().hashCode, const SliderThemeData().copyWith().hashCode);
});
test('SliderThemeData lerp special cases', () {
const SliderThemeData data = SliderThemeData();
expect(identical(SliderThemeData.lerp(data, data, 0.5), data), true);
});
testWidgets('Default SliderThemeData debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const SliderThemeData().debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[]);
});
testWidgets('SliderThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const SliderThemeData(
trackHeight: 7.0,
activeTrackColor: Color(0xFF000001),
inactiveTrackColor: Color(0xFF000002),
secondaryActiveTrackColor: Color(0xFF000003),
disabledActiveTrackColor: Color(0xFF000004),
disabledInactiveTrackColor: Color(0xFF000005),
disabledSecondaryActiveTrackColor: Color(0xFF000006),
activeTickMarkColor: Color(0xFF000007),
inactiveTickMarkColor: Color(0xFF000008),
disabledActiveTickMarkColor: Color(0xFF000009),
disabledInactiveTickMarkColor: Color(0xFF000010),
thumbColor: Color(0xFF000011),
overlappingShapeStrokeColor: Color(0xFF000012),
disabledThumbColor: Color(0xFF000013),
overlayColor: Color(0xFF000014),
valueIndicatorColor: Color(0xFF000015),
overlayShape: RoundSliderOverlayShape(),
tickMarkShape: RoundSliderTickMarkShape(),
thumbShape: RoundSliderThumbShape(),
trackShape: RoundedRectSliderTrackShape(),
valueIndicatorShape: PaddleSliderValueIndicatorShape(),
rangeTickMarkShape: RoundRangeSliderTickMarkShape(),
rangeThumbShape: RoundRangeSliderThumbShape(),
rangeTrackShape: RoundedRectRangeSliderTrackShape(),
rangeValueIndicatorShape: PaddleRangeSliderValueIndicatorShape(),
showValueIndicator: ShowValueIndicator.always,
valueIndicatorTextStyle: TextStyle(color: Colors.black),
mouseCursor: MaterialStateMouseCursor.clickable,
allowedInteraction: SliderInteraction.tapOnly,
).debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[
'trackHeight: 7.0',
'activeTrackColor: Color(0xff000001)',
'inactiveTrackColor: Color(0xff000002)',
'secondaryActiveTrackColor: Color(0xff000003)',
'disabledActiveTrackColor: Color(0xff000004)',
'disabledInactiveTrackColor: Color(0xff000005)',
'disabledSecondaryActiveTrackColor: Color(0xff000006)',
'activeTickMarkColor: Color(0xff000007)',
'inactiveTickMarkColor: Color(0xff000008)',
'disabledActiveTickMarkColor: Color(0xff000009)',
'disabledInactiveTickMarkColor: Color(0xff000010)',
'thumbColor: Color(0xff000011)',
'overlappingShapeStrokeColor: Color(0xff000012)',
'disabledThumbColor: Color(0xff000013)',
'overlayColor: Color(0xff000014)',
'valueIndicatorColor: Color(0xff000015)',
"overlayShape: Instance of 'RoundSliderOverlayShape'",
"tickMarkShape: Instance of 'RoundSliderTickMarkShape'",
"thumbShape: Instance of 'RoundSliderThumbShape'",
"trackShape: Instance of 'RoundedRectSliderTrackShape'",
"valueIndicatorShape: Instance of 'PaddleSliderValueIndicatorShape'",
"rangeTickMarkShape: Instance of 'RoundRangeSliderTickMarkShape'",
"rangeThumbShape: Instance of 'RoundRangeSliderThumbShape'",
"rangeTrackShape: Instance of 'RoundedRectRangeSliderTrackShape'",
"rangeValueIndicatorShape: Instance of 'PaddleRangeSliderValueIndicatorShape'",
'showValueIndicator: always',
'valueIndicatorTextStyle: TextStyle(inherit: true, color: Color(0xff000000))',
'mouseCursor: MaterialStateMouseCursor(clickable)',
'allowedInteraction: tapOnly'
]);
});
testWidgets('Slider defaults', (WidgetTester tester) async {
debugDisableShadows = false;
final ThemeData theme = ThemeData(useMaterial3: true);
final ColorScheme colorScheme = theme.colorScheme;
const double trackHeight = 4.0;
final Color activeTrackColor = Color(colorScheme.primary.value);
final Color inactiveTrackColor = colorScheme.surfaceVariant;
final Color secondaryActiveTrackColor = colorScheme.primary.withOpacity(0.54);
final Color disabledActiveTrackColor = colorScheme.onSurface.withOpacity(0.38);
final Color disabledInactiveTrackColor = colorScheme.onSurface.withOpacity(0.12);
final Color disabledSecondaryActiveTrackColor = colorScheme.onSurface.withOpacity(0.12);
final Color shadowColor = colorScheme.shadow;
final Color thumbColor = Color(colorScheme.primary.value);
final Color disabledThumbColor = Color.alphaBlend(colorScheme.onSurface.withOpacity(0.38), colorScheme.surface);
final Color activeTickMarkColor = colorScheme.onPrimary.withOpacity(0.38);
final Color inactiveTickMarkColor = colorScheme.onSurfaceVariant.withOpacity(0.38);
final Color disabledActiveTickMarkColor = colorScheme.onSurface.withOpacity(0.38);
final Color disabledInactiveTickMarkColor = colorScheme.onSurface.withOpacity(0.38);
try {
double value = 0.45;
Widget buildApp({
int? divisions,
bool enabled = true,
}) {
final ValueChanged<double>? onChanged = !enabled
? null
: (double d) {
value = d;
};
return MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: Theme(
data: theme,
child: Slider(
value: value,
secondaryTrackValue: 0.75,
label: '$value',
divisions: divisions,
onChanged: onChanged,
),
),
),
),
),
);
}
await tester.pumpWidget(buildApp());
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Test default track height.
const Radius radius = Radius.circular(trackHeight / 2);
const Radius activatedRadius = Radius.circular((trackHeight + 2) / 2);
expect(
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 362.4, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(362.4, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: inactiveTrackColor),
);
// Test default colors for enabled slider.
expect(material, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(material, paints..shadow(color: shadowColor));
expect(material, paints..circle(color: thumbColor));
expect(material, isNot(paints..circle(color: disabledThumbColor)));
expect(material, isNot(paints..rrect(color: disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledSecondaryActiveTrackColor)));
expect(material, isNot(paints..circle(color: activeTickMarkColor)));
expect(material, isNot(paints..circle(color: inactiveTickMarkColor)));
// Test defaults colors for discrete slider.
await tester.pumpWidget(buildApp(divisions: 3));
expect(material, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(
material,
paints
..circle(color: activeTickMarkColor)
..circle(color: activeTickMarkColor)
..circle(color: inactiveTickMarkColor)
..circle(color: inactiveTickMarkColor)
..shadow(color: Colors.black)
..circle(color: thumbColor),
);
expect(material, isNot(paints..circle(color: disabledThumbColor)));
expect(material, isNot(paints..rrect(color: disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledSecondaryActiveTrackColor)));
// Test defaults colors for disabled slider.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
material,
paints
..rrect(color: disabledActiveTrackColor)
..rrect(color: disabledInactiveTrackColor)
..rrect(color: disabledSecondaryActiveTrackColor),
);
expect(material, paints..shadow(color: shadowColor)..circle(color: disabledThumbColor));
expect(material, isNot(paints..circle(color: thumbColor)));
expect(material, isNot(paints..rrect(color: activeTrackColor)));
expect(material, isNot(paints..rrect(color: inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: secondaryActiveTrackColor)));
// Test defaults colors for disabled discrete slider.
await tester.pumpWidget(buildApp(divisions: 3, enabled: false));
expect(
material,
paints
..circle(color: disabledActiveTickMarkColor)
..circle(color: disabledActiveTickMarkColor)
..circle(color: disabledInactiveTickMarkColor)
..circle(color: disabledInactiveTickMarkColor)
..shadow(color: shadowColor)
..circle(color: disabledThumbColor),
);
expect(material, isNot(paints..circle(color: thumbColor)));
expect(material, isNot(paints..rrect(color: activeTrackColor)));
expect(material, isNot(paints..rrect(color: inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: secondaryActiveTrackColor)));
} finally {
debugDisableShadows = true;
}
});
testWidgets('Slider uses the right theme colors for the right components', (WidgetTester tester) async {
debugDisableShadows = false;
try {
const Color customColor1 = Color(0xcafefeed);
const Color customColor2 = Color(0xdeadbeef);
const Color customColor3 = Color(0xdecaface);
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
sliderTheme: const SliderThemeData(
disabledThumbColor: Color(0xff000001),
disabledActiveTickMarkColor: Color(0xff000002),
disabledActiveTrackColor: Color(0xff000003),
disabledInactiveTickMarkColor: Color(0xff000004),
disabledInactiveTrackColor: Color(0xff000005),
activeTrackColor: Color(0xff000006),
activeTickMarkColor: Color(0xff000007),
inactiveTrackColor: Color(0xff000008),
inactiveTickMarkColor: Color(0xff000009),
overlayColor: Color(0xff000010),
thumbColor: Color(0xff000011),
valueIndicatorColor: Color(0xff000012),
disabledSecondaryActiveTrackColor: Color(0xff000013),
secondaryActiveTrackColor: Color(0xff000014),
),
);
final SliderThemeData sliderTheme = theme.sliderTheme;
double value = 0.45;
Widget buildApp({
Color? activeColor,
Color? inactiveColor,
Color? secondaryActiveColor,
int? divisions,
bool enabled = true,
}) {
final ValueChanged<double>? onChanged = !enabled
? null
: (double d) {
value = d;
};
return MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: Theme(
data: theme,
child: Slider(
value: value,
secondaryTrackValue: 0.75,
label: '$value',
divisions: divisions,
activeColor: activeColor,
inactiveColor: inactiveColor,
secondaryActiveColor: secondaryActiveColor,
onChanged: onChanged,
),
),
),
),
),
);
}
await tester.pumpWidget(buildApp());
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
// Check default theme for enabled widget.
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..shadow(color: const Color(0xff000000)));
expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
// Test setting only the activeColor.
await tester.pumpWidget(buildApp(activeColor: customColor1));
expect(material, paints..rrect(color: customColor1)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: customColor1));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
// Test setting only the inactiveColor.
await tester.pumpWidget(buildApp(inactiveColor: customColor1));
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: customColor1)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
// Test setting only the secondaryActiveColor.
await tester.pumpWidget(buildApp(secondaryActiveColor: customColor1));
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: customColor1));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: sliderTheme.thumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
// Test setting both activeColor, inactiveColor, and secondaryActiveColor.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, secondaryActiveColor: customColor3));
expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2)..rrect(color: customColor3));
expect(material, paints..shadow(color: Colors.black));
expect(material, paints..circle(color: customColor1));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
// Test colors for discrete slider.
await tester.pumpWidget(buildApp(divisions: 3));
expect(material, paints..rrect(color: sliderTheme.activeTrackColor)..rrect(color: sliderTheme.inactiveTrackColor)..rrect(color: sliderTheme.secondaryActiveTrackColor));
expect(
material,
paints
..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor)
..shadow(color: Colors.black)
..circle(color: sliderTheme.thumbColor),
);
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
// Test colors for discrete slider with inactiveColor and activeColor set.
await tester.pumpWidget(buildApp(
activeColor: customColor1,
inactiveColor: customColor2,
secondaryActiveColor: customColor3,
divisions: 3,
));
expect(material, paints..rrect(color: customColor1)..rrect(color: customColor2)..rrect(color: customColor3));
expect(
material,
paints
..circle(color: customColor2)
..circle(color: customColor2)
..circle(color: customColor1)
..circle(color: customColor1)
..shadow(color: Colors.black)
..circle(color: customColor1),
);
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..circle(color: sliderTheme.disabledThumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor)));
expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
// Test default theme for disabled widget.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
material,
paints
..rrect(color: sliderTheme.disabledActiveTrackColor)
..rrect(color: sliderTheme.disabledInactiveTrackColor)
..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor),
);
expect(material, paints..shadow(color: Colors.black)..circle(color: sliderTheme.disabledThumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.activeTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.secondaryActiveTrackColor)));
// Test default theme for disabled discrete widget.
await tester.pumpWidget(buildApp(divisions: 3, enabled: false));
expect(
material,
paints
..circle(color: sliderTheme.disabledActiveTickMarkColor)
..circle(color: sliderTheme.disabledActiveTickMarkColor)
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
..shadow(color: Colors.black)
..circle(color: sliderTheme.disabledThumbColor),
);
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.activeTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.secondaryActiveTrackColor)));
expect(material, isNot(paints..circle(color: sliderTheme.activeTickMarkColor)));
expect(material, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor)));
// Test setting the activeColor, inactiveColor and secondaryActiveColor for disabled widget.
await tester.pumpWidget(buildApp(activeColor: customColor1, inactiveColor: customColor2, secondaryActiveColor: customColor3, enabled: false));
expect(
material,
paints
..rrect(color: sliderTheme.disabledActiveTrackColor)
..rrect(color: sliderTheme.disabledInactiveTrackColor)
..rrect(color: sliderTheme.disabledSecondaryActiveTrackColor),
);
expect(material, paints..circle(color: sliderTheme.disabledThumbColor));
expect(material, isNot(paints..circle(color: sliderTheme.thumbColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.activeTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: sliderTheme.secondaryActiveTrackColor)));
// Test that the default value indicator has the right colors.
await tester.pumpWidget(buildApp(divisions: 3));
Offset center = tester.getCenter(find.byType(Slider));
TestGesture gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(value, equals(2.0 / 3.0));
expect(
valueIndicatorBox,
paints
..path(color: sliderTheme.valueIndicatorColor)
..paragraph(),
);
await gesture.up();
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
// Testing the custom colors are used for the indicator.
await tester.pumpWidget(buildApp(
divisions: 3,
activeColor: customColor1,
inactiveColor: customColor2,
));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(value, equals(2.0 / 3.0));
expect(
valueIndicatorBox,
paints
..rrect(color: const Color(0xfffafafa))
..rrect(color: customColor1) // active track
..rrect(color: customColor2) // inactive track
..circle(color: customColor1.withOpacity(0.12)) // overlay
..circle(color: customColor2) // 1st tick mark
..circle(color: customColor2) // 2nd tick mark
..circle(color: customColor2) // 3rd tick mark
..circle(color: customColor1) // 4th tick mark
..shadow(color: Colors.black)
..circle(color: customColor1) // thumb
..path(color: sliderTheme.valueIndicatorColor), // indicator
);
await gesture.up();
} finally {
debugDisableShadows = true;
}
});
testWidgets('Slider parameters overrides theme properties', (WidgetTester tester) async {
debugDisableShadows = false;
const Color activeTrackColor = Color(0xffff0001);
const Color inactiveTrackColor = Color(0xffff0002);
const Color secondaryActiveTrackColor = Color(0xffff0003);
const Color thumbColor = Color(0xffff0004);
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
sliderTheme: const SliderThemeData(
activeTrackColor: Color(0xff000001),
inactiveTickMarkColor: Color(0xff000002),
secondaryActiveTrackColor: Color(0xff000003),
thumbColor: Color(0xff000004),
),
);
try {
const double value = 0.45;
Widget buildApp({ bool enabled = true }) {
return MaterialApp(
theme: theme,
home: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: Slider(
activeColor: activeTrackColor,
inactiveColor: inactiveTrackColor,
secondaryActiveColor: secondaryActiveTrackColor,
thumbColor: thumbColor,
value: value,
secondaryTrackValue: 0.75,
label: '$value',
onChanged: (double value) { },
),
),
),
),
);
}
await tester.pumpWidget(buildApp());
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Test Slider parameters.
expect(material, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(material, paints..circle(color: thumbColor));
} finally {
debugDisableShadows = true;
}
});
testWidgets('Slider uses ThemeData slider theme if present', (WidgetTester tester) async {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.red,
);
final SliderThemeData sliderTheme = theme.sliderTheme;
final SliderThemeData customTheme = sliderTheme.copyWith(
activeTrackColor: Colors.purple,
inactiveTrackColor: Colors.purple.withAlpha(0x3d),
secondaryActiveTrackColor: Colors.purple.withAlpha(0x8a),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, secondaryTrackValue: 0.75, enabled: false));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
material,
paints
..rrect(color: customTheme.disabledActiveTrackColor)
..rrect(color: customTheme.disabledInactiveTrackColor)
..rrect(color: customTheme.disabledSecondaryActiveTrackColor),
);
});
testWidgets('Slider overrides ThemeData theme if SliderTheme present', (WidgetTester tester) async {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.red,
);
final SliderThemeData sliderTheme = theme.sliderTheme;
final SliderThemeData customTheme = sliderTheme.copyWith(
activeTrackColor: Colors.purple,
inactiveTrackColor: Colors.purple.withAlpha(0x3d),
secondaryActiveTrackColor: Colors.purple.withAlpha(0x8a),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, secondaryTrackValue: 0.75, enabled: false));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
material,
paints
..rrect(color: customTheme.disabledActiveTrackColor)
..rrect(color: customTheme.disabledInactiveTrackColor)
..rrect(color: customTheme.disabledSecondaryActiveTrackColor),
);
});
testWidgets('SliderThemeData generates correct opacities for fromPrimaryColors', (WidgetTester tester) async {
const Color customColor1 = Color(0xcafefeed);
const Color customColor2 = Color(0xdeadbeef);
const Color customColor3 = Color(0xdecaface);
const Color customColor4 = Color(0xfeedcafe);
final SliderThemeData sliderTheme = SliderThemeData.fromPrimaryColors(
primaryColor: customColor1,
primaryColorDark: customColor2,
primaryColorLight: customColor3,
valueIndicatorTextStyle: ThemeData.fallback().textTheme.bodyLarge!.copyWith(color: customColor4),
);
expect(sliderTheme.activeTrackColor, equals(customColor1.withAlpha(0xff)));
expect(sliderTheme.inactiveTrackColor, equals(customColor1.withAlpha(0x3d)));
expect(sliderTheme.secondaryActiveTrackColor, equals(customColor1.withAlpha(0x8a)));
expect(sliderTheme.disabledActiveTrackColor, equals(customColor2.withAlpha(0x52)));
expect(sliderTheme.disabledInactiveTrackColor, equals(customColor2.withAlpha(0x1f)));
expect(sliderTheme.disabledSecondaryActiveTrackColor, equals(customColor2.withAlpha(0x1f)));
expect(sliderTheme.activeTickMarkColor, equals(customColor3.withAlpha(0x8a)));
expect(sliderTheme.inactiveTickMarkColor, equals(customColor1.withAlpha(0x8a)));
expect(sliderTheme.disabledActiveTickMarkColor, equals(customColor3.withAlpha(0x1f)));
expect(sliderTheme.disabledInactiveTickMarkColor, equals(customColor2.withAlpha(0x1f)));
expect(sliderTheme.thumbColor, equals(customColor1.withAlpha(0xff)));
expect(sliderTheme.disabledThumbColor, equals(customColor2.withAlpha(0x52)));
expect(sliderTheme.overlayColor, equals(customColor1.withAlpha(0x1f)));
expect(sliderTheme.valueIndicatorColor, equals(customColor1.withAlpha(0xff)));
expect(sliderTheme.valueIndicatorTextStyle!.color, equals(customColor4));
});
testWidgets('SliderThemeData generates correct shapes for fromPrimaryColors', (WidgetTester tester) async {
const Color customColor1 = Color(0xcafefeed);
const Color customColor2 = Color(0xdeadbeef);
const Color customColor3 = Color(0xdecaface);
const Color customColor4 = Color(0xfeedcafe);
final SliderThemeData sliderTheme = SliderThemeData.fromPrimaryColors(
primaryColor: customColor1,
primaryColorDark: customColor2,
primaryColorLight: customColor3,
valueIndicatorTextStyle: ThemeData.fallback().textTheme.bodyLarge!.copyWith(color: customColor4),
);
expect(sliderTheme.overlayShape, const RoundSliderOverlayShape());
expect(sliderTheme.tickMarkShape, const RoundSliderTickMarkShape());
expect(sliderTheme.thumbShape, const RoundSliderThumbShape());
expect(sliderTheme.trackShape, const RoundedRectSliderTrackShape());
expect(sliderTheme.valueIndicatorShape, const PaddleSliderValueIndicatorShape());
expect(sliderTheme.rangeTickMarkShape, const RoundRangeSliderTickMarkShape());
expect(sliderTheme.rangeThumbShape, const RoundRangeSliderThumbShape());
expect(sliderTheme.rangeTrackShape, const RoundedRectRangeSliderTrackShape());
expect(sliderTheme.rangeValueIndicatorShape, const PaddleRangeSliderValueIndicatorShape());
});
testWidgets('SliderThemeData lerps correctly', (WidgetTester tester) async {
final SliderThemeData sliderThemeBlack = SliderThemeData.fromPrimaryColors(
primaryColor: Colors.black,
primaryColorDark: Colors.black,
primaryColorLight: Colors.black,
valueIndicatorTextStyle: ThemeData.fallback().textTheme.bodyLarge!.copyWith(color: Colors.black),
).copyWith(trackHeight: 2.0);
final SliderThemeData sliderThemeWhite = SliderThemeData.fromPrimaryColors(
primaryColor: Colors.white,
primaryColorDark: Colors.white,
primaryColorLight: Colors.white,
valueIndicatorTextStyle: ThemeData.fallback().textTheme.bodyLarge!.copyWith(color: Colors.white),
).copyWith(trackHeight: 6.0);
final SliderThemeData lerp = SliderThemeData.lerp(sliderThemeBlack, sliderThemeWhite, 0.5);
const Color middleGrey = Color(0xff7f7f7f);
expect(lerp.trackHeight, equals(4.0));
expect(lerp.activeTrackColor, equals(middleGrey.withAlpha(0xff)));
expect(lerp.inactiveTrackColor, equals(middleGrey.withAlpha(0x3d)));
expect(lerp.secondaryActiveTrackColor, equals(middleGrey.withAlpha(0x8a)));
expect(lerp.disabledActiveTrackColor, equals(middleGrey.withAlpha(0x52)));
expect(lerp.disabledInactiveTrackColor, equals(middleGrey.withAlpha(0x1f)));
expect(lerp.disabledSecondaryActiveTrackColor, equals(middleGrey.withAlpha(0x1f)));
expect(lerp.activeTickMarkColor, equals(middleGrey.withAlpha(0x8a)));
expect(lerp.inactiveTickMarkColor, equals(middleGrey.withAlpha(0x8a)));
expect(lerp.disabledActiveTickMarkColor, equals(middleGrey.withAlpha(0x1f)));
expect(lerp.disabledInactiveTickMarkColor, equals(middleGrey.withAlpha(0x1f)));
expect(lerp.thumbColor, equals(middleGrey.withAlpha(0xff)));
expect(lerp.disabledThumbColor, equals(middleGrey.withAlpha(0x52)));
expect(lerp.overlayColor, equals(middleGrey.withAlpha(0x1f)));
expect(lerp.valueIndicatorColor, equals(middleGrey.withAlpha(0xff)));
expect(lerp.valueIndicatorTextStyle!.color, equals(middleGrey.withAlpha(0xff)));
});
testWidgets('Default slider track draws correctly', (WidgetTester tester) async {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
);
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, secondaryTrackValue: 0.5));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
const Radius radius = Radius.circular(2);
const Radius activatedRadius = Radius.circular(3);
// The enabled slider thumb has track segments that extend to and from
// the center of the thumb.
expect(
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 212.0, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.inactiveTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 400.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.secondaryActiveTrackColor),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, secondaryTrackValue: 0.5, enabled: false));
await tester.pumpAndSettle(); // wait for disable animation
// The disabled slider thumb is the same size as the enabled thumb.
expect(
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 212.0, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.disabledActiveTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledInactiveTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 298.0, 400.0, 302.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledSecondaryActiveTrackColor),
);
});
testWidgets('Default slider overlay draws correctly', (WidgetTester tester) async {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
);
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// With no touch, paints only the thumb.
expect(
material,
paints
..circle(
color: sliderTheme.thumbColor,
x: 212.0,
y: 300.0,
radius: 10.0,
),
);
final Offset center = tester.getCenter(find.byType(Slider));
final TestGesture gesture = await tester.startGesture(center);
// Wait for overlay animation to finish.
await tester.pumpAndSettle();
// After touch, paints thumb and overlay.
expect(
material,
paints
..circle(
color: sliderTheme.overlayColor,
x: 212.0,
y: 300.0,
radius: 24.0,
)
..circle(
color: sliderTheme.thumbColor,
x: 212.0,
y: 300.0,
radius: 10.0,
),
);
await gesture.up();
await tester.pumpAndSettle();
// After the gesture is up and complete, it again paints only the thumb.
expect(
material,
paints
..circle(
color: sliderTheme.thumbColor,
x: 212.0,
y: 300.0,
radius: 10.0,
),
);
});
testWidgets('Slider can use theme overlay with material states', (WidgetTester tester) async {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
);
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(
overlayColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.focused)) {
return Colors.brown[500]!;
}
return Colors.transparent;
}),
);
final FocusNode focusNode = FocusNode(debugLabel: 'Slider');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double value = 0.5;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
theme: ThemeData(sliderTheme: sliderTheme),
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Slider(
value: value,
onChanged: enabled ? (double newValue) {
setState(() {
value = newValue;
});
} : null,
autofocus: true,
focusNode: focusNode,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
// Check that the overlay shows when focused.
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Slider))),
paints..circle(color: Colors.brown[500]),
);
// Check that the overlay does not show when focused and disabled.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isFalse);
expect(
Material.of(tester.element(find.byType(Slider))),
isNot(paints..circle(color: Colors.brown[500])),
);
});
testWidgets('Default slider ticker and thumb shape draw correctly', (WidgetTester tester) async {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
);
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(thumbColor: Colors.red.shade500);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(material, paints..circle(color: sliderTheme.thumbColor, radius: 10.0));
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, enabled: false));
await tester.pumpAndSettle(); // wait for disable animation
expect(material, paints..circle(color: sliderTheme.disabledThumbColor, radius: 10.0));
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, divisions: 3));
await tester.pumpAndSettle(); // wait for enable animation
expect(
material,
paints
..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.activeTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor)
..circle(color: sliderTheme.inactiveTickMarkColor)
..circle(color: sliderTheme.thumbColor, radius: 10.0),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.45, divisions: 3, enabled: false));
await tester.pumpAndSettle(); // wait for disable animation
expect(
material,
paints
..circle(color: sliderTheme.disabledActiveTickMarkColor)
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
..circle(color: sliderTheme.disabledInactiveTickMarkColor)
..circle(color: sliderTheme.disabledThumbColor, radius: 10.0),
);
});
testWidgets('Default paddle slider value indicator shape draws correctly', (WidgetTester tester) async {
debugDisableShadows = false;
try {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
);
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(
thumbColor: Colors.red.shade500,
showValueIndicator: ShowValueIndicator.always,
valueIndicatorShape: const PaddleSliderValueIndicatorShape(),
);
Widget buildApp(String value, { double sliderValue = 0.5, double textScale = 1.0 }) {
return MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: MediaQueryData(textScaleFactor: textScale),
child: Material(
child: Row(
children: <Widget>[
Expanded(
child: SliderTheme(
data: sliderTheme,
child: Slider(
value: sliderValue,
label: value,
divisions: 3,
onChanged: (double d) { },
),
),
),
],
),
),
),
),
);
}
await tester.pumpWidget(buildApp('1'));
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
Offset center = tester.getCenter(find.byType(Slider));
TestGesture gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -40.0),
const Offset(15.9, -40.0),
const Offset(-15.9, -40.0),
],
excludes: <Offset>[const Offset(16.1, -40.0), const Offset(-16.1, -40.0)],
),
);
await gesture.up();
// Test that it expands with a larger label.
await tester.pumpWidget(buildApp('1000'));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -40.0),
const Offset(35.9, -40.0),
const Offset(-35.9, -40.0),
],
excludes: <Offset>[const Offset(36.1, -40.0), const Offset(-36.1, -40.0)],
),
);
await gesture.up();
// Test that it avoids the left edge of the screen.
await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -40.0),
const Offset(92.0, -40.0),
const Offset(-16.0, -40.0),
],
excludes: <Offset>[const Offset(98.1, -40.0), const Offset(-20.1, -40.0)],
),
);
await gesture.up();
// Test that it avoids the right edge of the screen.
await tester.pumpWidget(buildApp('1000000', sliderValue: 1.0));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -40.0),
const Offset(16.0, -40.0),
const Offset(-92.0, -40.0),
],
excludes: <Offset>[const Offset(20.1, -40.0), const Offset(-98.1, -40.0)],
),
);
await gesture.up();
// Test that the neck stretches when the text scale gets smaller.
await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0, textScale: 0.5));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -49.0),
const Offset(68.0, -49.0),
const Offset(-24.0, -49.0),
],
excludes: <Offset>[
const Offset(98.0, -32.0), // inside full size, outside small
const Offset(-40.0, -32.0), // inside full size, outside small
const Offset(90.1, -49.0),
const Offset(-40.1, -49.0),
],
),
);
await gesture.up();
// Test that the neck shrinks when the text scale gets larger.
await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0, textScale: 2.5));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -38.8),
const Offset(92.0, -38.8),
const Offset(8.0, -23.0), // Inside large, outside scale=1.0
const Offset(-2.0, -23.0), // Inside large, outside scale=1.0
],
excludes: <Offset>[
const Offset(98.5, -38.8),
const Offset(-16.1, -38.8),
],
),
);
await gesture.up();
} finally {
debugDisableShadows = true;
}
});
testWidgets('Default paddle slider value indicator shape draws correctly', (WidgetTester tester) async {
debugDisableShadows = false;
try {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
);
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(
thumbColor: Colors.red.shade500,
showValueIndicator: ShowValueIndicator.always,
valueIndicatorShape: const PaddleSliderValueIndicatorShape(),
);
Widget buildApp(String value, { double sliderValue = 0.5, double textScale = 1.0 }) {
return MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: MediaQueryData(textScaleFactor: textScale),
child: Material(
child: Row(
children: <Widget>[
Expanded(
child: SliderTheme(
data: sliderTheme,
child: Slider(
value: sliderValue,
label: value,
divisions: 3,
onChanged: (double d) { },
),
),
),
],
),
),
),
),
);
}
await tester.pumpWidget(buildApp('1'));
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
Offset center = tester.getCenter(find.byType(Slider));
TestGesture gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -40.0),
const Offset(15.9, -40.0),
const Offset(-15.9, -40.0),
],
excludes: <Offset>[const Offset(16.1, -40.0), const Offset(-16.1, -40.0)],
),
);
await gesture.up();
// Test that it expands with a larger label.
await tester.pumpWidget(buildApp('1000'));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -40.0),
const Offset(35.9, -40.0),
const Offset(-35.9, -40.0),
],
excludes: <Offset>[const Offset(36.1, -40.0), const Offset(-36.1, -40.0)],
),
);
await gesture.up();
// Test that it avoids the left edge of the screen.
await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -40.0),
const Offset(92.0, -40.0),
const Offset(-16.0, -40.0),
],
excludes: <Offset>[const Offset(98.1, -40.0), const Offset(-20.1, -40.0)],
),
);
await gesture.up();
// Test that it avoids the right edge of the screen.
await tester.pumpWidget(buildApp('1000000', sliderValue: 1.0));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -40.0),
const Offset(16.0, -40.0),
const Offset(-92.0, -40.0),
],
excludes: <Offset>[const Offset(20.1, -40.0), const Offset(-98.1, -40.0)],
),
);
await gesture.up();
// Test that the neck stretches when the text scale gets smaller.
await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0, textScale: 0.5));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -49.0),
const Offset(68.0, -49.0),
const Offset(-24.0, -49.0),
],
excludes: <Offset>[
const Offset(98.0, -32.0), // inside full size, outside small
const Offset(-40.0, -32.0), // inside full size, outside small
const Offset(90.1, -49.0),
const Offset(-40.1, -49.0),
],
),
);
await gesture.up();
// Test that the neck shrinks when the text scale gets larger.
await tester.pumpWidget(buildApp('1000000', sliderValue: 0.0, textScale: 2.5));
center = tester.getCenter(find.byType(Slider));
gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..path(
color: sliderTheme.valueIndicatorColor,
includes: <Offset>[
const Offset(0.0, -38.8),
const Offset(92.0, -38.8),
const Offset(8.0, -23.0), // Inside large, outside scale=1.0
const Offset(-2.0, -23.0), // Inside large, outside scale=1.0
],
excludes: <Offset>[
const Offset(98.5, -38.8),
const Offset(-16.1, -38.8),
],
),
);
await gesture.up();
} finally {
debugDisableShadows = true;
}
});
testWidgets('The slider track height can be overridden', (WidgetTester tester) async {
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(trackHeight: 16);
const Radius radius = Radius.circular(8);
const Radius activatedRadius = Radius.circular(9);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Top and bottom are centerY (300) + and - trackRadius (8).
expect(
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 291.0, 212.0, 309.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 292.0, 776.0, 308.0, topRight: radius, bottomRight: radius), color: sliderTheme.inactiveTrackColor),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
await tester.pumpAndSettle(); // wait for disable animation
// The disabled thumb is smaller so the active track has to paint longer to
// get to the edge.
expect(
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 291.0, 212.0, 309.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: sliderTheme.disabledActiveTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(212.0, 292.0, 776.0, 308.0, topRight: radius, bottomRight: radius), color: sliderTheme.disabledInactiveTrackColor),
);
});
testWidgets('The default slider thumb shape sizes can be overridden', (WidgetTester tester) async {
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 7,
disabledThumbRadius: 11,
),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
material,
paints..circle(x: 212, y: 300, radius: 7, color: sliderTheme.thumbColor),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
await tester.pumpAndSettle(); // wait for disable animation
expect(
material,
paints..circle(x: 212, y: 300, radius: 11, color: sliderTheme.disabledThumbColor),
);
});
testWidgets('The default slider thumb shape disabled size can be inferred from the enabled size', (WidgetTester tester) async {
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 9,
),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
material,
paints..circle(x: 212, y: 300, radius: 9, color: sliderTheme.thumbColor),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.25, enabled: false));
await tester.pumpAndSettle(); // wait for disable animation
expect(
material,
paints..circle(x: 212, y: 300, radius: 9, color: sliderTheme.disabledThumbColor),
);
});
testWidgets('The default slider tick mark shape size can be overridden', (WidgetTester tester) async {
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
tickMarkShape: const RoundSliderTickMarkShape(tickMarkRadius: 5),
activeTickMarkColor: const Color(0xfadedead),
inactiveTickMarkColor: const Color(0xfadebeef),
disabledActiveTickMarkColor: const Color(0xfadecafe),
disabledInactiveTickMarkColor: const Color(0xfadeface),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
material,
paints
..circle(x: 26, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
..circle(x: 400, y: 300, radius: 5, color: sliderTheme.activeTickMarkColor)
..circle(x: 774, y: 300, radius: 5, color: sliderTheme.inactiveTickMarkColor),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5, divisions: 2, enabled: false));
await tester.pumpAndSettle();
expect(
material,
paints
..circle(x: 26, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
..circle(x: 400, y: 300, radius: 5, color: sliderTheme.disabledActiveTickMarkColor)
..circle(x: 774, y: 300, radius: 5, color: sliderTheme.disabledInactiveTickMarkColor),
);
});
testWidgets('The default slider overlay shape size can be overridden', (WidgetTester tester) async {
const double uniqueOverlayRadius = 23;
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
overlayShape: const RoundSliderOverlayShape(
overlayRadius: uniqueOverlayRadius,
),
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5));
// Tap center and wait for animation.
final Offset center = tester.getCenter(find.byType(Slider));
await tester.startGesture(center);
await tester.pumpAndSettle();
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(
material,
paints..circle(
x: center.dx,
y: center.dy,
radius: uniqueOverlayRadius,
color: sliderTheme.overlayColor,
),
);
});
// Regression test for https://github.com/flutter/flutter/issues/74503
testWidgets('The slider track layout correctly when the overlay size is smaller than the thumb size', (WidgetTester tester) async {
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
overlayShape: SliderComponentShape.noOverlay,
);
await tester.pumpWidget(_buildApp(sliderTheme, value: 0.5));
final MaterialInkController material = Material.of(
tester.element(find.byType(Slider)),
);
// The track rectangle begins at 10 pixels from the left of the screen and ends 10 pixels from the right
// (790 pixels from the left). The main check here it that the track itself should be centered on
// the 800 pixel-wide screen.
expect(
material,
paints
// active track RRect. Starts 10 pixels from left of screen.
..rrect(rrect: RRect.fromLTRBAndCorners(
10.0,
297.0,
400.0,
303.0,
topLeft: const Radius.circular(3.0),
bottomLeft: const Radius.circular(3.0),
))
// inactive track RRect. Ends 10 pixels from right of screen.
..rrect(rrect: RRect.fromLTRBAndCorners(
400.0,
298.0,
790.0,
302.0,
topRight: const Radius.circular(2.0),
bottomRight: const Radius.circular(2.0),
))
// The thumb.
..circle(x: 400.0, y: 300.0, radius: 10.0),
);
});
// Regression test for https://github.com/flutter/flutter/issues/125467
testWidgets('The RangeSlider track layout correctly when the overlay size is smaller than the thumb size', (WidgetTester tester) async {
final SliderThemeData sliderTheme = ThemeData().sliderTheme.copyWith(
overlayShape: SliderComponentShape.noOverlay,
);
await tester.pumpWidget(_buildRangeApp(sliderTheme, values: const RangeValues(0.0, 1.0)));
final MaterialInkController material = Material.of(
tester.element(find.byType(RangeSlider)),
);
// The track rectangle begins at 10 pixels from the left of the screen and ends 10 pixels from the right
// (790 pixels from the left). The main check here it that the track itself should be centered on
// the 800 pixel-wide screen.
expect(
material,
paints
// active track RRect. Starts 10 pixels from left of screen.
..rrect(rrect: RRect.fromLTRBAndCorners(
10.0,
298.0,
10.0,
302.0,
topLeft: const Radius.circular(2.0),
bottomLeft: const Radius.circular(2.0),
))
// active track RRect Start 10 pixels from left screen.
..rect(rect:const Rect.fromLTRB(10.0, 297.0, 790.0, 303.0),)
// inactive track RRect. Ends 10 pixels from right of screen.
..rrect(rrect: RRect.fromLTRBAndCorners(
790.0,
298.0,
790.0,
302.0,
topRight: const Radius.circular(2.0),
bottomRight: const Radius.circular(2.0),
))
// The thumb Left.
..circle(x: 10.0, y: 300.0, radius: 10.0)
// The thumb Right.
..circle(x: 790.0, y: 300.0, radius: 10.0),
);
});
// Only the thumb, overlay, and tick mark have special shortcuts to provide
// no-op or empty shapes.
//
// The track can also be skipped by providing 0 height.
//
// The value indicator can be skipped by passing the appropriate
// [ShowValueIndicator].
testWidgets('The slider can skip all of its component painting', (WidgetTester tester) async {
// Pump a slider with all shapes skipped.
await tester.pumpWidget(_buildApp(
ThemeData().sliderTheme.copyWith(
trackHeight: 0,
overlayShape: SliderComponentShape.noOverlay,
thumbShape: SliderComponentShape.noThumb,
tickMarkShape: SliderTickMarkShape.noTickMark,
showValueIndicator: ShowValueIndicator.never,
),
value: 0.5,
divisions: 4,
));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(material, paintsExactlyCountTimes(#drawCircle, 0));
expect(material, paintsExactlyCountTimes(#drawPath, 0));
});
testWidgets('The slider can skip all component painting except the track', (WidgetTester tester) async {
// Pump a slider with just a track.
await tester.pumpWidget(_buildApp(
ThemeData().sliderTheme.copyWith(
overlayShape: SliderComponentShape.noOverlay,
thumbShape: SliderComponentShape.noThumb,
tickMarkShape: SliderTickMarkShape.noTickMark,
showValueIndicator: ShowValueIndicator.never,
),
value: 0.5,
divisions: 4,
));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Only 2 track segments.
expect(material, paintsExactlyCountTimes(#drawRRect, 2));
expect(material, paintsExactlyCountTimes(#drawCircle, 0));
expect(material, paintsExactlyCountTimes(#drawPath, 0));
});
testWidgets('The slider can skip all component painting except the tick marks', (WidgetTester tester) async {
// Pump a slider with just tick marks.
await tester.pumpWidget(_buildApp(
ThemeData().sliderTheme.copyWith(
trackHeight: 0,
overlayShape: SliderComponentShape.noOverlay,
thumbShape: SliderComponentShape.noThumb,
showValueIndicator: ShowValueIndicator.never,
// When the track is hidden to 0 height, a tick mark radius
// must be provided to get a non-zero radius.
tickMarkShape: const RoundSliderTickMarkShape(tickMarkRadius: 1),
),
value: 0.5,
divisions: 4,
));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Only 5 tick marks.
expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(material, paintsExactlyCountTimes(#drawCircle, 5));
expect(material, paintsExactlyCountTimes(#drawPath, 0));
});
testWidgets('The slider can skip all component painting except the thumb', (WidgetTester tester) async {
debugDisableShadows = false;
try {
// Pump a slider with just a thumb.
await tester.pumpWidget(_buildApp(
ThemeData().sliderTheme.copyWith(
trackHeight: 0,
overlayShape: SliderComponentShape.noOverlay,
tickMarkShape: SliderTickMarkShape.noTickMark,
showValueIndicator: ShowValueIndicator.never,
),
value: 0.5,
divisions: 4,
));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Only 1 thumb.
expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(material, paintsExactlyCountTimes(#drawCircle, 1));
expect(material, paintsExactlyCountTimes(#drawPath, 0));
} finally {
debugDisableShadows = true;
}
});
testWidgets('The slider can skip all component painting except the overlay', (WidgetTester tester) async {
// Pump a slider with just an overlay.
await tester.pumpWidget(_buildApp(
ThemeData().sliderTheme.copyWith(
trackHeight: 0,
thumbShape: SliderComponentShape.noThumb,
tickMarkShape: SliderTickMarkShape.noTickMark,
showValueIndicator: ShowValueIndicator.never,
),
value: 0.5,
divisions: 4,
));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
// Tap the center of the track and wait for animations to finish.
final Offset center = tester.getCenter(find.byType(Slider));
final TestGesture gesture = await tester.startGesture(center);
await tester.pumpAndSettle();
// Only 1 overlay.
expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(material, paintsExactlyCountTimes(#drawCircle, 1));
expect(material, paintsExactlyCountTimes(#drawPath, 0));
await gesture.up();
});
testWidgets('The slider can skip all component painting except the value indicator', (WidgetTester tester) async {
// Pump a slider with just a value indicator.
await tester.pumpWidget(_buildApp(
ThemeData().sliderTheme.copyWith(
trackHeight: 0,
overlayShape: SliderComponentShape.noOverlay,
thumbShape: SliderComponentShape.noThumb,
tickMarkShape: SliderTickMarkShape.noTickMark,
showValueIndicator: ShowValueIndicator.always,
),
value: 0.5,
divisions: 4,
));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
// Tap the center of the track and wait for animations to finish.
final Offset center = tester.getCenter(find.byType(Slider));
final TestGesture gesture = await tester.startGesture(center);
await tester.pumpAndSettle();
// Only 1 value indicator.
expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(material, paintsExactlyCountTimes(#drawCircle, 0));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 1));
await gesture.up();
});
testWidgets('PaddleSliderValueIndicatorShape skips all painting at zero scale', (WidgetTester tester) async {
// Pump a slider with just a value indicator.
await tester.pumpWidget(_buildApp(
ThemeData().sliderTheme.copyWith(
trackHeight: 0,
overlayShape: SliderComponentShape.noOverlay,
thumbShape: SliderComponentShape.noThumb,
tickMarkShape: SliderTickMarkShape.noTickMark,
showValueIndicator: ShowValueIndicator.always,
valueIndicatorShape: const PaddleSliderValueIndicatorShape(),
),
value: 0.5,
divisions: 4,
));
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
// Tap the center of the track to kick off the animation of the value indicator.
final Offset center = tester.getCenter(find.byType(Slider));
final TestGesture gesture = await tester.startGesture(center);
// Nothing to paint at scale 0.
await tester.pump();
expect(material, paintsExactlyCountTimes(#drawRect, 0));
expect(material, paintsExactlyCountTimes(#drawCircle, 0));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 0));
// Painting a path for the value indicator.
await tester.pump(const Duration(milliseconds: 16));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 1));
await gesture.up();
});
testWidgets('Default slider value indicator shape skips all painting at zero scale', (WidgetTester tester) async {
// Pump a slider with just a value indicator.
await tester.pumpWidget(_buildApp(
ThemeData().sliderTheme.copyWith(
trackHeight: 0,
overlayShape: SliderComponentShape.noOverlay,
thumbShape: SliderComponentShape.noThumb,
tickMarkShape: SliderTickMarkShape.noTickMark,
showValueIndicator: ShowValueIndicator.always,
),
value: 0.5,
divisions: 4,
));
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
// Tap the center of the track to kick off the animation of the value indicator.
final Offset center = tester.getCenter(find.byType(Slider));
final TestGesture gesture = await tester.startGesture(center);
// Nothing to paint at scale 0.
await tester.pump();
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 0));
// Painting a path for the value indicator.
await tester.pump(const Duration(milliseconds: 16));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 1));
await gesture.up();
});
testWidgets('Default paddle range slider value indicator shape draws correctly', (WidgetTester tester) async {
debugDisableShadows = false;
try {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
);
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(
thumbColor: Colors.red.shade500,
showValueIndicator: ShowValueIndicator.always,
rangeValueIndicatorShape: const PaddleRangeSliderValueIndicatorShape(),
);
await tester.pumpWidget(_buildRangeApp(sliderTheme));
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
final Offset center = tester.getCenter(find.byType(RangeSlider));
final TestGesture gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
// physical model
..rrect()
..rrect(rrect: RRect.fromLTRBAndCorners(
24.0, 298.0, 24.0, 302.0,
topLeft: const Radius.circular(2.0),
bottomLeft: const Radius.circular(2.0),
))
..rect(rect: const Rect.fromLTRB(24.0, 297.0, 24.0, 303.0))
..rrect(rrect: RRect.fromLTRBAndCorners(
24.0, 298.0, 776.0, 302.0,
topRight: const Radius.circular(2.0),
bottomRight: const Radius.circular(2.0),
))
..circle(x: 24.0, y: 300.0)
..shadow(elevation: 1.0)
..circle(x: 24.0, y: 300.0)
..shadow(elevation: 6.0)
..circle(x: 24.0, y: 300.0),
);
await gesture.up();
} finally {
debugDisableShadows = true;
}
});
testWidgets('Default paddle range slider value indicator shape draws correctly with debugDisableShadows', (WidgetTester tester) async {
debugDisableShadows = true;
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
primarySwatch: Colors.blue,
);
final SliderThemeData sliderTheme = theme.sliderTheme.copyWith(
thumbColor: Colors.red.shade500,
showValueIndicator: ShowValueIndicator.always,
rangeValueIndicatorShape: const PaddleRangeSliderValueIndicatorShape(),
);
await tester.pumpWidget(_buildRangeApp(sliderTheme));
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
final Offset center = tester.getCenter(find.byType(RangeSlider));
final TestGesture gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
// physical model
..rrect()
..rrect(rrect: RRect.fromLTRBAndCorners(
24.0, 298.0, 24.0, 302.0,
topLeft: const Radius.circular(2.0),
bottomLeft: const Radius.circular(2.0),
))
..rect(rect: const Rect.fromLTRB(24.0, 297.0, 24.0, 303.0))
..rrect(rrect: RRect.fromLTRBAndCorners(
24.0, 298.0, 776.0, 302.0,
topRight: const Radius.circular(2.0),
bottomRight: const Radius.circular(2.0),
))
..circle(x: 24.0, y: 300.0)
..path(strokeWidth: 1.0 * 2.0, color: Colors.black)
..circle(x: 24.0, y: 300.0)
..path(strokeWidth: 6.0 * 2.0, color: Colors.black)
..circle(x: 24.0, y: 300.0),
);
await gesture.up();
});
testWidgets('PaddleRangeSliderValueIndicatorShape skips all painting at zero scale', (WidgetTester tester) async {
debugDisableShadows = false;
try {
// Pump a slider with just a value indicator.
await tester.pumpWidget(_buildRangeApp(
ThemeData().sliderTheme.copyWith(
trackHeight: 0,
rangeValueIndicatorShape: const PaddleRangeSliderValueIndicatorShape(),
),
values: const RangeValues(0, 0.5),
divisions: 4,
));
// final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider));
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
// Tap the center of the track to kick off the animation of the value indicator.
final Offset center = tester.getCenter(find.byType(RangeSlider));
final TestGesture gesture = await tester.startGesture(center);
// No value indicator path to paint at scale 0.
await tester.pump();
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 0));
// Painting a path for each value indicator.
await tester.pump(const Duration(milliseconds: 16));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 2));
await gesture.up();
} finally {
debugDisableShadows = true;
}
});
testWidgets('Default range indicator shape skips all painting at zero scale', (WidgetTester tester) async {
debugDisableShadows = false;
try {
// Pump a slider with just a value indicator.
await tester.pumpWidget(_buildRangeApp(
ThemeData().sliderTheme.copyWith(
trackHeight: 0,
overlayShape: SliderComponentShape.noOverlay,
thumbShape: SliderComponentShape.noThumb,
tickMarkShape: SliderTickMarkShape.noTickMark,
showValueIndicator: ShowValueIndicator.always,
),
values: const RangeValues(0, 0.5),
divisions: 4,
));
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
// Tap the center of the track to kick off the animation of the value indicator.
final Offset center = tester.getCenter(find.byType(RangeSlider));
final TestGesture gesture = await tester.startGesture(center);
// No value indicator path to paint at scale 0.
await tester.pump();
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 0));
// Painting a path for each value indicator.
await tester.pump(const Duration(milliseconds: 16));
expect(valueIndicatorBox, paintsExactlyCountTimes(#drawPath, 2));
await gesture.up();
} finally {
debugDisableShadows = true;
}
});
testWidgets('activeTrackRadius is taken into account when painting the border of the active track', (WidgetTester tester) async {
await tester.pumpWidget(_buildApp(
ThemeData().sliderTheme.copyWith(
trackShape: const RoundedRectSliderTrackShapeWithCustomAdditionalActiveTrackHeight(
additionalActiveTrackHeight: 10.0
)
)
));
await tester.pumpAndSettle();
final Offset center = tester.getCenter(find.byType(Slider));
await tester.startGesture(center);
expect(
find.byType(Slider),
paints
..rrect(rrect: RRect.fromLTRBAndCorners(
24.0, 293.0, 24.0, 307.0,
topLeft: const Radius.circular(7.0),
bottomLeft: const Radius.circular(7.0),
))
..rrect(rrect: RRect.fromLTRBAndCorners(
24.0, 298.0, 776.0, 302.0,
topRight: const Radius.circular(2.0),
bottomRight: const Radius.circular(2.0),
)),
);
});
testWidgets('The mouse cursor is themeable', (WidgetTester tester) async {
await tester.pumpWidget(_buildApp(
ThemeData().sliderTheme.copyWith(
mouseCursor: const MaterialStatePropertyAll<MouseCursor>(SystemMouseCursors.text),
)
));
await tester.pumpAndSettle();
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(Slider)));
await tester.pumpAndSettle();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text);
});
testWidgets('SliderTheme.allowedInteraction is themeable', (WidgetTester tester) async {
double value = 0.0;
Widget buildApp({
bool isAllowedInteractionInThemeNull = false,
bool isAllowedInteractionInSliderNull = false,
}) {
return MaterialApp(
home: Scaffold(
body: Center(
child: SliderTheme(
data: ThemeData().sliderTheme.copyWith(
allowedInteraction: isAllowedInteractionInThemeNull
? null
: SliderInteraction.slideOnly,
),
child: StatefulBuilder(
builder: (_, void Function(void Function()) setState) {
return Slider(
value: value,
allowedInteraction: isAllowedInteractionInSliderNull
? null
: SliderInteraction.tapOnly,
onChanged: (double newValue) {
setState(() {
value = newValue;
});
},
);
}
),
),
),
),
);
}
final TestGesture gesture = await tester.createGesture();
// when theme and parameter are specified, parameter is used [tapOnly].
await tester.pumpWidget(buildApp());
// tap is allowed.
value = 0.0;
await gesture.down(tester.getCenter(find.byType(Slider)));
await tester.pump();
expect(value, equals(0.5)); // changes
await gesture.up();
// slide isn't allowed.
value = 0.0;
await gesture.down(tester.getCenter(find.byType(Slider)));
await tester.pump();
await gesture.moveBy(const Offset(50, 0));
expect(value, equals(0.0)); // no change
await gesture.up();
// when only parameter is specified, parameter is used [tapOnly].
await tester.pumpWidget(buildApp(isAllowedInteractionInThemeNull: true));
// tap is allowed.
value = 0.0;
await gesture.down(tester.getCenter(find.byType(Slider)));
await tester.pump();
expect(value, equals(0.5)); // changes
await gesture.up();
// slide isn't allowed.
value = 0.0;
await gesture.down(tester.getCenter(find.byType(Slider)));
await tester.pump();
await gesture.moveBy(const Offset(50, 0));
expect(value, equals(0.0)); // no change
await gesture.up();
// when theme is specified but parameter is null, theme is used [slideOnly].
await tester.pumpWidget(buildApp(isAllowedInteractionInSliderNull: true));
// tap isn't allowed.
value = 0.0;
await gesture.down(tester.getCenter(find.byType(Slider)));
await tester.pump();
expect(value, equals(0.0)); // no change
await gesture.up();
// slide isn't allowed.
value = 0.0;
await gesture.down(tester.getCenter(find.byType(Slider)));
await tester.pump();
await gesture.moveBy(const Offset(50, 0));
expect(value, greaterThan(0.0)); // changes
await gesture.up();
// when both theme and parameter are null, default is used [tapAndSlide].
await tester.pumpWidget(buildApp(
isAllowedInteractionInSliderNull: true,
isAllowedInteractionInThemeNull: true,
));
// tap is allowed.
value = 0.0;
await gesture.down(tester.getCenter(find.byType(Slider)));
await tester.pump();
expect(value, equals(0.5));
await gesture.up();
// slide is allowed.
value = 0.0;
await gesture.down(tester.getCenter(find.byType(Slider)));
await tester.pump();
await gesture.moveBy(const Offset(50, 0));
expect(value, greaterThan(0.0)); // changes
await gesture.up();
});
testWidgets('Default value indicator color', (WidgetTester tester) async {
debugDisableShadows = false;
try {
final ThemeData theme = ThemeData(
useMaterial3: true,
platform: TargetPlatform.android,
);
Widget buildApp(String value, { double sliderValue = 0.5, double textScale = 1.0 }) {
return MaterialApp(
theme: theme,
home: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: MediaQueryData(textScaleFactor: textScale),
child: Material(
child: Row(
children: <Widget>[
Expanded(
child: Slider(
value: sliderValue,
label: value,
divisions: 3,
onChanged: (double d) { },
),
),
],
),
),
),
),
);
}
await tester.pumpWidget(buildApp('1'));
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
final Offset center = tester.getCenter(find.byType(Slider));
await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..rrect(color: const Color(0xfffffbfe))
..rrect(color: const Color(0xff6750a4))
..rrect(color: const Color(0xffe7e0ec))
..path(color: Color(theme.colorScheme.primary.value))
..rrect(
color: Color(theme.colorScheme.primary.value),
)
);
} finally {
debugDisableShadows = true;
}
});
group('Material 2', () {
// Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
// is turned on by default, these tests can be removed.
testWidgets('Slider defaults', (WidgetTester tester) async {
debugDisableShadows = false;
final ThemeData theme = ThemeData();
const double trackHeight = 4.0;
final ColorScheme colorScheme = theme.colorScheme;
final Color activeTrackColor = Color(colorScheme.primary.value);
final Color inactiveTrackColor = colorScheme.primary.withOpacity(0.24);
final Color secondaryActiveTrackColor = colorScheme.primary.withOpacity(0.54);
final Color disabledActiveTrackColor = colorScheme.onSurface.withOpacity(0.32);
final Color disabledInactiveTrackColor = colorScheme.onSurface.withOpacity(0.12);
final Color disabledSecondaryActiveTrackColor = colorScheme.onSurface.withOpacity(0.12);
final Color shadowColor = colorScheme.shadow;
final Color thumbColor = Color(colorScheme.primary.value);
final Color disabledThumbColor = Color.alphaBlend(colorScheme.onSurface.withOpacity(.38), colorScheme.surface);
final Color activeTickMarkColor = colorScheme.onPrimary.withOpacity(0.54);
final Color inactiveTickMarkColor = colorScheme.primary.withOpacity(0.54);
final Color disabledActiveTickMarkColor = colorScheme.onPrimary.withOpacity(0.12);
final Color disabledInactiveTickMarkColor = colorScheme.onSurface.withOpacity(0.12);
final Color valueIndicatorColor = Color.alphaBlend(colorScheme.onSurface.withOpacity(0.60), colorScheme.surface.withOpacity(0.90));
try {
double value = 0.45;
Widget buildApp({
int? divisions,
bool enabled = true,
}) {
final ValueChanged<double>? onChanged = !enabled
? null
: (double d) {
value = d;
};
return MaterialApp(
home: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: Slider(
value: value,
secondaryTrackValue: 0.75,
label: '$value',
divisions: divisions,
onChanged: onChanged,
),
),
),
),
);
}
await tester.pumpWidget(buildApp());
final MaterialInkController material = Material.of(tester.element(find.byType(Slider)));
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
// Test default track height.
const Radius radius = Radius.circular(trackHeight / 2);
const Radius activatedRadius = Radius.circular((trackHeight + 2) / 2);
expect(
material,
paints
..rrect(rrect: RRect.fromLTRBAndCorners(24.0, 297.0, 362.4, 303.0, topLeft: activatedRadius, bottomLeft: activatedRadius), color: activeTrackColor)
..rrect(rrect: RRect.fromLTRBAndCorners(362.4, 298.0, 776.0, 302.0, topRight: radius, bottomRight: radius), color: inactiveTrackColor),
);
// Test default colors for enabled slider.
expect(material, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(material, paints..shadow(color: shadowColor));
expect(material, paints..circle(color: thumbColor));
expect(material, isNot(paints..circle(color: disabledThumbColor)));
expect(material, isNot(paints..rrect(color: disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledSecondaryActiveTrackColor)));
expect(material, isNot(paints..circle(color: activeTickMarkColor)));
expect(material, isNot(paints..circle(color: inactiveTickMarkColor)));
// Test defaults colors for discrete slider.
await tester.pumpWidget(buildApp(divisions: 3));
expect(material, paints..rrect(color: activeTrackColor)..rrect(color: inactiveTrackColor)..rrect(color: secondaryActiveTrackColor));
expect(
material,
paints
..circle(color: activeTickMarkColor)
..circle(color: activeTickMarkColor)
..circle(color: inactiveTickMarkColor)
..circle(color: inactiveTickMarkColor)
..shadow(color: Colors.black)
..circle(color: thumbColor),
);
expect(material, isNot(paints..circle(color: disabledThumbColor)));
expect(material, isNot(paints..rrect(color: disabledActiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledInactiveTrackColor)));
expect(material, isNot(paints..rrect(color: disabledSecondaryActiveTrackColor)));
// Test defaults colors for disabled slider.
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
material,
paints
..rrect(color: disabledActiveTrackColor)
..rrect(color: disabledInactiveTrackColor)
..rrect(color: disabledSecondaryActiveTrackColor),
);
expect(material, paints..shadow(color: Colors.black)..circle(color: disabledThumbColor));
expect(material, isNot(paints..circle(color: thumbColor)));
expect(material, isNot(paints..rrect(color: activeTrackColor)));
expect(material, isNot(paints..rrect(color: inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: secondaryActiveTrackColor)));
// Test defaults colors for disabled discrete slider.
await tester.pumpWidget(buildApp(divisions: 3, enabled: false));
expect(
material,
paints
..circle(color: disabledActiveTickMarkColor)
..circle(color: disabledActiveTickMarkColor)
..circle(color: disabledInactiveTickMarkColor)
..circle(color: disabledInactiveTickMarkColor)
..shadow(color: Colors.black)
..circle(color: disabledThumbColor),
);
expect(material, isNot(paints..circle(color: thumbColor)));
expect(material, isNot(paints..rrect(color: activeTrackColor)));
expect(material, isNot(paints..rrect(color: inactiveTrackColor)));
expect(material, isNot(paints..rrect(color: secondaryActiveTrackColor)));
expect(material, isNot(paints..circle(color: activeTickMarkColor)));
expect(material, isNot(paints..circle(color: inactiveTickMarkColor)));
// Test the default color for value indicator.
await tester.pumpWidget(buildApp(divisions: 3));
final Offset center = tester.getCenter(find.byType(Slider));
final TestGesture gesture = await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(value, equals(2.0 / 3.0));
expect(
valueIndicatorBox,
paints
..path(color: valueIndicatorColor)
..paragraph(),
);
await gesture.up();
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
} finally {
debugDisableShadows = true;
}
});
testWidgets('Default value indicator color', (WidgetTester tester) async {
debugDisableShadows = false;
try {
final ThemeData theme = ThemeData(
platform: TargetPlatform.android,
);
Widget buildApp(String value, { double sliderValue = 0.5, double textScale = 1.0 }) {
return MaterialApp(
theme: theme,
home: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: MediaQueryData(textScaleFactor: textScale),
child: Material(
child: Row(
children: <Widget>[
Expanded(
child: Slider(
value: sliderValue,
label: value,
divisions: 3,
onChanged: (double d) { },
),
),
],
),
),
),
),
);
}
await tester.pumpWidget(buildApp('1'));
final RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay));
final Offset center = tester.getCenter(find.byType(Slider));
await tester.startGesture(center);
// Wait for value indicator animation to finish.
await tester.pumpAndSettle();
expect(
valueIndicatorBox,
paints
..rrect(color: const Color(0xfffafafa))
..rrect(color: const Color(0xff2196f3))
..rrect(color: const Color(0x3d2196f3))
// Test that the value indicator text is painted with the correct color.
..path(color: const Color(0xf55f5f5f))
);
} finally {
debugDisableShadows = true;
}
});
});
}
class RoundedRectSliderTrackShapeWithCustomAdditionalActiveTrackHeight extends RoundedRectSliderTrackShape {
const RoundedRectSliderTrackShapeWithCustomAdditionalActiveTrackHeight({required this.additionalActiveTrackHeight});
final double additionalActiveTrackHeight;
@override
void paint(
PaintingContext context,
Offset offset, {
required RenderBox parentBox,
required SliderThemeData sliderTheme,
required Animation<double> enableAnimation,
required TextDirection textDirection,
required Offset thumbCenter,
Offset? secondaryOffset,
bool isDiscrete = false,
bool isEnabled = false,
double additionalActiveTrackHeight = 2.0,
}) {
super.paint(context, offset, parentBox: parentBox, sliderTheme: sliderTheme, enableAnimation: enableAnimation, textDirection: textDirection, thumbCenter: thumbCenter, secondaryOffset: secondaryOffset, additionalActiveTrackHeight: this.additionalActiveTrackHeight);
}
}
Widget _buildApp(
SliderThemeData sliderTheme, {
double value = 0.0,
double? secondaryTrackValue,
bool enabled = true,
int? divisions,
FocusNode? focusNode,
}) {
final ValueChanged<double>? onChanged = enabled ? (double d) => value = d : null;
return MaterialApp(
home: Scaffold(
body: Center(
child: SliderTheme(
data: sliderTheme,
child: Slider(
value: value,
secondaryTrackValue: secondaryTrackValue,
label: '$value',
onChanged: onChanged,
divisions: divisions,
focusNode: focusNode,
),
),
),
),
);
}
Widget _buildRangeApp(
SliderThemeData sliderTheme, {
RangeValues values = const RangeValues(0, 0),
bool enabled = true,
int? divisions,
}) {
final ValueChanged<RangeValues>? onChanged = enabled ? (RangeValues d) => values = d : null;
return MaterialApp(
home: Scaffold(
body: Center(
child: SliderTheme(
data: sliderTheme,
child: RangeSlider(
values: values,
labels: RangeLabels(values.start.toString(), values.end.toString()),
onChanged: onChanged,
divisions: divisions,
),
),
),
),
);
}