// 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 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.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() { testWidgets('Range Slider can move when tapped (continuous LTR)', (WidgetTester tester) async { RangeValues values = const RangeValues(0.3, 0.7); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // No thumbs get select when tapping between the thumbs outside the touch // boundaries expect(values, equals(const RangeValues(0.3, 0.7))); // taps at 0.5 await tester.tap(find.byType(RangeSlider)); await tester.pump(); expect(values, equals(const RangeValues(0.3, 0.7))); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); // The start thumb is selected when tapping the left inactive track. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.1; await tester.tapAt(leftTarget); expect(values.start, closeTo(0.1, 0.01)); expect(values.end, equals(0.7)); // The end thumb is selected when tapping the right inactive track. await tester.pump(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.9; await tester.tapAt(rightTarget); expect(values.start, closeTo(0.1, 0.01)); expect(values.end, closeTo(0.9, 0.01)); }); testWidgets('Range Slider can move when tapped (continuous RTL)', (WidgetTester tester) async { RangeValues values = const RangeValues(0.3, 0.7); await tester.pumpWidget( Directionality( textDirection: TextDirection.rtl, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // No thumbs get select when tapping between the thumbs outside the touch // boundaries expect(values, equals(const RangeValues(0.3, 0.7))); // taps at 0.5 await tester.tap(find.byType(RangeSlider)); await tester.pump(); expect(values, equals(const RangeValues(0.3, 0.7))); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); // The end thumb is selected when tapping the left inactive track. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.1; await tester.tapAt(leftTarget); expect(values.start, 0.3); expect(values.end, closeTo(0.9, 0.01)); // The start thumb is selected when tapping the right inactive track. await tester.pump(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.9; await tester.tapAt(rightTarget); expect(values.start, closeTo(0.1, 0.01)); expect(values.end, closeTo(0.9, 0.01)); }); testWidgets('Range Slider can move when tapped (discrete LTR)', (WidgetTester tester) async { RangeValues values = const RangeValues(30, 70); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, min: 0.0, max: 100.0, divisions: 10, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // No thumbs get select when tapping between the thumbs outside the touch // boundaries expect(values, equals(const RangeValues(30, 70))); // taps at 0.5 await tester.tap(find.byType(RangeSlider)); await tester.pumpAndSettle(); expect(values, equals(const RangeValues(30, 70))); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); // The start thumb is selected when tapping the left inactive track. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.1; await tester.tapAt(leftTarget); await tester.pumpAndSettle(); expect(values.start.round(), equals(10)); expect(values.end.round(), equals(70)); // The end thumb is selected when tapping the right inactive track. await tester.pump(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.9; await tester.tapAt(rightTarget); await tester.pumpAndSettle(); expect(values.start.round(), equals(10)); expect(values.end.round(), equals(90)); }); testWidgets('Range Slider can move when tapped (discrete RTL)', (WidgetTester tester) async { RangeValues values = const RangeValues(30, 70); await tester.pumpWidget( Directionality( textDirection: TextDirection.rtl, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, min: 0, max: 100, divisions: 10, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // No thumbs get select when tapping between the thumbs outside the touch // boundaries expect(values, equals(const RangeValues(30, 70))); // taps at 0.5 await tester.tap(find.byType(RangeSlider)); await tester.pumpAndSettle(); expect(values, equals(const RangeValues(30, 70))); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); // The start thumb is selected when tapping the left inactive track. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.1; await tester.tapAt(leftTarget); await tester.pumpAndSettle(); expect(values.start.round(), equals(30)); expect(values.end.round(), equals(90)); // The end thumb is selected when tapping the right inactive track. await tester.pump(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.9; await tester.tapAt(rightTarget); await tester.pumpAndSettle(); expect(values.start.round(), equals(10)); expect(values.end.round(), equals(90)); }); testWidgets('Range Slider thumbs can be dragged to the min and max (continous LTR)', (WidgetTester tester) async { RangeValues values = const RangeValues(0.3, 0.7); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); // Drag the start thumb to the min. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, topLeft + (bottomRight - topLeft) * -0.4); expect(values.start, equals(0)); // Drag the end thumb to the max. await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, topLeft + (bottomRight - topLeft) * 0.4); expect(values.end, equals(1)); }); testWidgets('Range Slider thumbs can be dragged to the min and max (continous RTL)', (WidgetTester tester) async { RangeValues values = const RangeValues(0.3, 0.7); await tester.pumpWidget( Directionality( textDirection: TextDirection.rtl, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); // Drag the end thumb to the max. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, topLeft + (bottomRight - topLeft) * -0.4); expect(values.end, equals(1)); // Drag the start thumb to the min. await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, topLeft + (bottomRight - topLeft) * 0.4); expect(values.start, equals(0)); }); testWidgets('Range Slider thumbs can be dragged to the min and max (discrete LTR)', (WidgetTester tester) async { RangeValues values = const RangeValues(30, 70); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, min: 0, max: 100, divisions: 10, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); // Drag the start thumb to the min. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, topLeft + (bottomRight - topLeft) * -0.4); expect(values.start, equals(0)); // Drag the end thumb to the max. await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, topLeft + (bottomRight - topLeft) * 0.4); expect(values.end, equals(100)); }); testWidgets('Range Slider thumbs can be dragged to the min and max (discrete RTL)', (WidgetTester tester) async { RangeValues values = const RangeValues(30, 70); await tester.pumpWidget( Directionality( textDirection: TextDirection.rtl, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, min: 0, max: 100, divisions: 10, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); // Drag the end thumb to the max. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, topLeft + (bottomRight - topLeft) * -0.4); expect(values.end, equals(100)); // Drag the start thumb to the min. await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, topLeft + (bottomRight - topLeft) * 0.4); expect(values.start, equals(0)); }); testWidgets('Range Slider thumbs can be dragged together and the start thumb can be dragged apart (continous LTR)', (WidgetTester tester) async { RangeValues values = const RangeValues(0.3, 0.7); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); final Offset middle = topLeft + bottomRight / 2; // Drag the start thumb towards the center. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, middle - leftTarget); expect(values.start, closeTo(0.5, 0.05)); // Drag the end thumb towards the center. await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, middle - rightTarget); expect(values.end, closeTo(0.5, 0.05)); // Drag the start thumb apart. await tester.pumpAndSettle(); await tester.dragFrom(middle, -(bottomRight - topLeft) * 0.3); expect(values.start, closeTo(0.2, 0.05)); }); testWidgets('Range Slider thumbs can be dragged together and the start thumb can be dragged apart (continous RTL)', (WidgetTester tester) async { RangeValues values = const RangeValues(0.3, 0.7); await tester.pumpWidget( Directionality( textDirection: TextDirection.rtl, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); final Offset middle = topLeft + bottomRight / 2; // Drag the end thumb towards the center. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, middle - leftTarget); expect(values.end, closeTo(0.5, 0.05)); // Drag the start thumb towards the center. await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, middle - rightTarget); expect(values.start, closeTo(0.5, 0.05)); // Drag the start thumb apart. await tester.pumpAndSettle(); await tester.dragFrom(middle, (bottomRight - topLeft) * 0.3); expect(values.start, closeTo(0.2, 0.05)); }); testWidgets('Range Slider thumbs can be dragged together and the start thumb can be dragged apart (discrete LTR)', (WidgetTester tester) async { RangeValues values = const RangeValues(30, 70); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, min: 0, max: 100, divisions: 10, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); final Offset middle = topLeft + bottomRight / 2; // Drag the start thumb towards the center. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, middle - leftTarget); expect(values.start, closeTo(50, 0.01)); // Drag the end thumb towards the center. await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, middle - rightTarget); expect(values.end, closeTo(50, 0.01)); // Drag the start thumb apart. await tester.pumpAndSettle(); await tester.dragFrom(middle, -(bottomRight - topLeft) * 0.3); expect(values.start, closeTo(20, 0.01)); }); testWidgets('Range Slider thumbs can be dragged together and the start thumb can be dragged apart (discrete RTL)', (WidgetTester tester) async { RangeValues values = const RangeValues(30, 70); await tester.pumpWidget( Directionality( textDirection: TextDirection.rtl, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, min: 0, max: 100, divisions: 10, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); final Offset middle = topLeft + bottomRight / 2; // Drag the end thumb towards the center. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, middle - leftTarget); expect(values.end, closeTo(50, 0.01)); // Drag the start thumb towards the center. await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, middle - rightTarget); expect(values.start, closeTo(50, 0.01)); // Drag the start thumb apart. await tester.pumpAndSettle(); await tester.dragFrom(middle, (bottomRight - topLeft) * 0.3); expect(values.start, closeTo(20, 0.01)); }); testWidgets('Range Slider thumbs can be dragged together and the end thumb can be dragged apart (continous LTR)', (WidgetTester tester) async { RangeValues values = const RangeValues(0.3, 0.7); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); final Offset middle = topLeft + bottomRight / 2; // Drag the start thumb towards the center. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, middle - leftTarget); expect(values.start, closeTo(0.5, 0.05)); // Drag the end thumb towards the center. await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, middle - rightTarget); expect(values.end, closeTo(0.5, 0.05)); // Drag the end thumb apart. await tester.pumpAndSettle(); await tester.dragFrom(middle, (bottomRight - topLeft) * 0.3); expect(values.end, closeTo(0.8, 0.05)); }); testWidgets('Range Slider thumbs can be dragged together and the end thumb can be dragged apart (continous RTL)', (WidgetTester tester) async { RangeValues values = const RangeValues(0.3, 0.7); await tester.pumpWidget( Directionality( textDirection: TextDirection.rtl, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); final Offset middle = topLeft + bottomRight / 2; // Drag the end thumb towards the center. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, middle - leftTarget); expect(values.end, closeTo(0.5, 0.05)); // Drag the start thumb towards the center. await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, middle - rightTarget); expect(values.start, closeTo(0.5, 0.05)); // Drag the end thumb apart. await tester.pumpAndSettle(); await tester.dragFrom(middle, -(bottomRight - topLeft) * 0.3); expect(values.end, closeTo(0.8, 0.05)); }); testWidgets('Range Slider thumbs can be dragged together and the end thumb can be dragged apart (discrete LTR)', (WidgetTester tester) async { RangeValues values = const RangeValues(30, 70); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, min: 0, max: 100, divisions: 10, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); final Offset middle = topLeft + bottomRight / 2; // Drag the start thumb towards the center. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, middle - leftTarget); expect(values.start, closeTo(50, 0.01)); // Drag the end thumb towards the center. await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, middle - rightTarget); expect(values.end, closeTo(50, 0.01)); // Drag the end thumb apart. await tester.pumpAndSettle(); await tester.dragFrom(middle, (bottomRight - topLeft) * 0.3); expect(values.end, closeTo(80, 0.01)); }); testWidgets('Range Slider thumbs can be dragged together and the end thumb can be dragged apart (discrete RTL)', (WidgetTester tester) async { RangeValues values = const RangeValues(30, 70); await tester.pumpWidget( Directionality( textDirection: TextDirection.rtl, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, min: 0, max: 100, divisions: 10, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ); }, ), ), ); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); final Offset middle = topLeft + bottomRight / 2; // Drag the end thumb towards the center. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, middle - leftTarget); expect(values.end, closeTo(50, 0.01)); // Drag the start thumb towards the center. await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, middle - rightTarget); expect(values.start, closeTo(50, 0.01)); // Drag the end thumb apart. await tester.pumpAndSettle(); await tester.dragFrom(middle, -(bottomRight - topLeft) * 0.3); expect(values.end, closeTo(80, 0.01)); }); testWidgets('Range Slider onChangeEnd and onChangeStart are called on an interaction initiated by tap', (WidgetTester tester) async { RangeValues values = const RangeValues(30, 70); RangeValues startValues; RangeValues endValues; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, min: 0, max: 100, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, onChangeStart: (RangeValues newValues) { startValues = newValues; }, onChangeEnd: (RangeValues newValues) { endValues = newValues; }, ), ), ), ); }, ), ), ); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); // Drag the start thumb towards the center. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; expect(startValues, null); expect(endValues, null); await tester.dragFrom(leftTarget, (bottomRight - topLeft) * 0.2); expect(startValues.start, closeTo(30, 1)); expect(startValues.end, closeTo(70, 1)); expect(values.start, closeTo(50, 1)); expect(values.end, closeTo(70, 1)); expect(endValues.start, closeTo(50, 1)); expect(endValues.end, closeTo(70, 1)); }); testWidgets('Range Slider onChangeEnd and onChangeStart are called on an interaction initiated by drag', (WidgetTester tester) async { RangeValues values = const RangeValues(30, 70); RangeValues startValues; RangeValues endValues; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: RangeSlider( values: values, min: 0, max: 100, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, onChangeStart: (RangeValues newValues) { startValues = newValues; }, onChangeEnd: (RangeValues newValues) { endValues = newValues; }, ), ), ), ); }, ), ), ); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); // Drag the thumbs together. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, (bottomRight - topLeft) * 0.2); await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, (bottomRight - topLeft) * -0.2); await tester.pumpAndSettle(); expect(values.start, closeTo(50, 1)); expect(values.end, closeTo(51, 1)); // Drag the end thumb to the right. final Offset middleTarget = topLeft + (bottomRight - topLeft) * 0.5; await tester.dragFrom(middleTarget, (bottomRight - topLeft) * 0.4); await tester.pumpAndSettle(); expect(startValues.start, closeTo(50, 1)); expect(startValues.end, closeTo(51, 1)); expect(endValues.start, closeTo(50, 1)); expect(endValues.end, closeTo(90, 1)); }); ThemeData _buildTheme() { return 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), ), ); } Widget _buildThemedApp({ ThemeData theme, Color activeColor, Color inactiveColor, int divisions, bool enabled = true, }) { RangeValues values = const RangeValues(0.5, 0.75); final ValueChanged<RangeValues> onChanged = !enabled ? null : (RangeValues newValues) { values = newValues; }; return Directionality( textDirection: TextDirection.ltr, child: MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: Theme( data: theme, child: RangeSlider( values: values, labels: RangeLabels(values.start.toStringAsFixed(2), values.end.toStringAsFixed(2)), divisions: divisions, activeColor: activeColor, inactiveColor: inactiveColor, onChanged: onChanged, ), ), ), ), ), ); } testWidgets('Range Slider uses the right theme colors for the right shapes for a default enabled slider', (WidgetTester tester) async { final ThemeData theme = _buildTheme(); final SliderThemeData sliderTheme = theme.sliderTheme; await tester.pumpWidget(_buildThemedApp(theme: theme)); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider)); // Check default theme for enabled widget. expect(sliderBox, paints ..rect(color: sliderTheme.inactiveTrackColor) ..rect(color: sliderTheme.activeTrackColor) ..rect(color: sliderTheme.inactiveTrackColor)); expect(sliderBox, paints ..circle(color: sliderTheme.thumbColor) ..circle(color: sliderTheme.thumbColor)); expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor))); expect(sliderBox, isNot(paints..circle(color: sliderTheme.activeTickMarkColor))); expect(sliderBox, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor))); }); testWidgets('Range Slider uses the right theme colors for the right shapes when setting the active color', (WidgetTester tester) async { const Color activeColor = Color(0xcafefeed); final ThemeData theme = _buildTheme(); final SliderThemeData sliderTheme = theme.sliderTheme; await tester.pumpWidget(_buildThemedApp(theme: theme, activeColor: activeColor)); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider)); expect( sliderBox, paints ..rect(color: sliderTheme.inactiveTrackColor) ..rect(color: activeColor) ..rect(color: sliderTheme.inactiveTrackColor)); expect( sliderBox, paints ..circle(color: activeColor) ..circle(color: activeColor)); expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor))); }); testWidgets('Range Slider uses the right theme colors for the right shapes when setting the inactive color', (WidgetTester tester) async { const Color inactiveColor = Color(0xdeadbeef); final ThemeData theme = _buildTheme(); final SliderThemeData sliderTheme = theme.sliderTheme; await tester.pumpWidget(_buildThemedApp(theme: theme, inactiveColor: inactiveColor)); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider)); expect( sliderBox, paints ..rect(color: inactiveColor) ..rect(color: sliderTheme.activeTrackColor) ..rect(color: inactiveColor)); expect( sliderBox, paints ..circle(color: sliderTheme.thumbColor) ..circle(color: sliderTheme.thumbColor)); expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor))); }); testWidgets('Range Slider uses the right theme colors for the right shapes with active and inactive colors', (WidgetTester tester) async { const Color activeColor = Color(0xcafefeed); const Color inactiveColor = Color(0xdeadbeef); final ThemeData theme = _buildTheme(); final SliderThemeData sliderTheme = theme.sliderTheme; await tester.pumpWidget(_buildThemedApp( theme: theme, activeColor: activeColor, inactiveColor: inactiveColor, )); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider)); expect( sliderBox, paints ..rect(color: inactiveColor) ..rect(color: activeColor) ..rect(color: inactiveColor)); expect( sliderBox, paints ..circle(color: activeColor) ..circle(color: activeColor)); expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor))); }); testWidgets('Range Slider uses the right theme colors for the right shapes for a discrete slider', (WidgetTester tester) async { final ThemeData theme = _buildTheme(); final SliderThemeData sliderTheme = theme.sliderTheme; await tester.pumpWidget(_buildThemedApp(theme: theme, divisions: 3)); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider)); expect( sliderBox, paints ..rect(color: sliderTheme.inactiveTrackColor) ..rect(color: sliderTheme.activeTrackColor) ..rect(color: sliderTheme.inactiveTrackColor)); expect( sliderBox, paints ..circle(color: sliderTheme.inactiveTickMarkColor) ..circle(color: sliderTheme.inactiveTickMarkColor) ..circle(color: sliderTheme.activeTickMarkColor) ..circle(color: sliderTheme.inactiveTickMarkColor) ..circle(color: sliderTheme.thumbColor) ..circle(color: sliderTheme.thumbColor)); expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor))); }); testWidgets('Range Slider uses the right theme colors for the right shapes for a discrete slider with active and inactive colors', (WidgetTester tester) async { const Color activeColor = Color(0xcafefeed); const Color inactiveColor = Color(0xdeadbeef); final ThemeData theme = _buildTheme(); final SliderThemeData sliderTheme = theme.sliderTheme; await tester.pumpWidget(_buildThemedApp( theme: theme, activeColor: activeColor, inactiveColor: inactiveColor, divisions: 3, )); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider)); expect( sliderBox, paints ..rect(color: inactiveColor) ..rect(color: activeColor) ..rect(color: inactiveColor)); expect( sliderBox, paints ..circle(color: activeColor) ..circle(color: activeColor) ..circle(color: inactiveColor) ..circle(color: activeColor) ..circle(color: activeColor) ..circle(color: activeColor)); expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(sliderBox, isNot(paints..circle(color: sliderTheme.disabledThumbColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledActiveTrackColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.disabledInactiveTrackColor))); expect(sliderBox, isNot(paints..circle(color: sliderTheme.activeTickMarkColor))); expect(sliderBox, isNot(paints..circle(color: sliderTheme.inactiveTickMarkColor))); }); testWidgets('Range Slider uses the right theme colors for the right shapes for a default disabled slider', (WidgetTester tester) async { final ThemeData theme = _buildTheme(); final SliderThemeData sliderTheme = theme.sliderTheme; await tester.pumpWidget(_buildThemedApp(theme: theme, enabled: false)); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider)); expect( sliderBox, paints ..rect(color: sliderTheme.disabledInactiveTrackColor) ..rect(color: sliderTheme.disabledActiveTrackColor) ..rect(color: sliderTheme.disabledInactiveTrackColor)); expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.activeTrackColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.inactiveTrackColor))); }); testWidgets('Range Slider uses the right theme colors for the right shapes for a disabled slider with active and inactive colors', (WidgetTester tester) async { const Color activeColor = Color(0xcafefeed); const Color inactiveColor = Color(0xdeadbeef); final ThemeData theme = _buildTheme(); final SliderThemeData sliderTheme = theme.sliderTheme; await tester.pumpWidget(_buildThemedApp( theme: theme, activeColor: activeColor, inactiveColor: inactiveColor, enabled: false, )); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider)); expect( sliderBox, paints ..rect(color: sliderTheme.disabledInactiveTrackColor) ..rect(color: sliderTheme.disabledActiveTrackColor) ..rect(color: sliderTheme.disabledInactiveTrackColor)); expect(sliderBox, isNot(paints..circle(color: sliderTheme.thumbColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.activeTrackColor))); expect(sliderBox, isNot(paints..rect(color: sliderTheme.inactiveTrackColor))); }); testWidgets('Range Slider uses the right theme colors for the right shapes when the value indicators are showing', (WidgetTester tester) async { const Color customColor1 = Color(0xcafefeed); const Color customColor2 = Color(0xdeadbeef); final ThemeData theme = _buildTheme(); final SliderThemeData sliderTheme = theme.sliderTheme; RangeValues values = const RangeValues(0.5, 0.75); Widget buildApp({ Color activeColor, Color inactiveColor, int divisions, bool enabled = true, }) { final ValueChanged<RangeValues> onChanged = !enabled ? null : (RangeValues newValues) { values = newValues; }; return Directionality( textDirection: TextDirection.ltr, child: MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: Theme( data: theme, child: RangeSlider( values: values, labels: RangeLabels(values.start.toStringAsFixed(2), values.end.toStringAsFixed(2)), divisions: divisions, activeColor: activeColor, inactiveColor: inactiveColor, onChanged: onChanged, ), ), ), ), ), ); } await tester.pumpWidget(buildApp(divisions: 3)); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider)); final Offset topRight = tester.getTopRight(find.byType(RangeSlider)).translate(-24, 0); TestGesture gesture = await tester.startGesture(topRight); // Wait for value indicator animation to finish. await tester.pumpAndSettle(); expect(values.end, equals(1)); expect( sliderBox, paints ..path(color: sliderTheme.valueIndicatorColor) ..path(color: sliderTheme.valueIndicatorColor), ); 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, )); gesture = await tester.startGesture(topRight); // Wait for value indicator animation to finish. await tester.pumpAndSettle(); expect(values.end, equals(1)); expect( sliderBox, paints ..path(color: customColor1) ..path(color: customColor1), ); await gesture.up(); }); testWidgets('Range Slider top thumb gets stroked when overlapping', (WidgetTester tester) async { RangeValues values = const RangeValues(0.3, 0.7); final ThemeData theme = ThemeData( platform: TargetPlatform.android, primarySwatch: Colors.blue, sliderTheme: const SliderThemeData( thumbColor: Color(0xff000001), overlappingShapeStrokeColor: Color(0xff000002), ), ); final SliderThemeData sliderTheme = theme.sliderTheme; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: Theme( data: theme, child: RangeSlider( values: values, onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ), ); }, ), ), ); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider)); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); final Offset middle = topLeft + bottomRight / 2; // Drag the thumbs towards the center. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, middle - leftTarget); await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, middle - rightTarget); expect(values.start, closeTo(0.5, 0.03)); expect(values.end, closeTo(0.5, 0.03)); await tester.pumpAndSettle(); expect( sliderBox, paints ..circle(color: sliderTheme.thumbColor) ..circle(color: sliderTheme.overlappingShapeStrokeColor) ..circle(color: sliderTheme.thumbColor), ); }); testWidgets('Range Slider top value indicator gets stroked when overlapping', (WidgetTester tester) async { RangeValues values = const RangeValues(0.3, 0.7); final ThemeData theme = ThemeData( platform: TargetPlatform.android, primarySwatch: Colors.blue, sliderTheme: const SliderThemeData( valueIndicatorColor: Color(0xff000001), overlappingShapeStrokeColor: Color(0xff000002), showValueIndicator: ShowValueIndicator.always, ), ); final SliderThemeData sliderTheme = theme.sliderTheme; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window), child: Material( child: Center( child: Theme( data: theme, child: RangeSlider( values: values, labels: RangeLabels(values.start.toStringAsFixed(2), values.end.toStringAsFixed(2)), onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ), ); }, ), ), ); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider)); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); final Offset middle = topLeft + bottomRight / 2; // Drag the thumbs towards the center. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, middle - leftTarget); await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, middle - rightTarget); await tester.pumpAndSettle(); expect(values.start, closeTo(0.5, 0.03)); expect(values.end, closeTo(0.5, 0.03)); final TestGesture gesture = await tester.startGesture(middle); await tester.pumpAndSettle(); expect( sliderBox, paints ..path(color: sliderTheme.valueIndicatorColor) ..path(color: sliderTheme.overlappingShapeStrokeColor) ..path(color: sliderTheme.valueIndicatorColor), ); await gesture.up(); }); testWidgets('Range Slider top value indicator gets stroked when overlapping with large text scale', (WidgetTester tester) async { RangeValues values = const RangeValues(0.3, 0.7); final ThemeData theme = ThemeData( platform: TargetPlatform.android, primarySwatch: Colors.blue, sliderTheme: const SliderThemeData( valueIndicatorColor: Color(0xff000001), overlappingShapeStrokeColor: Color(0xff000002), showValueIndicator: ShowValueIndicator.always, ), ); final SliderThemeData sliderTheme = theme.sliderTheme; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return MediaQuery( data: MediaQueryData.fromWindow(window).copyWith(textScaleFactor: 2.0), child: Material( child: Center( child: Theme( data: theme, child: RangeSlider( values: values, labels: RangeLabels(values.start.toStringAsFixed(2), values.end.toStringAsFixed(2)), onChanged: (RangeValues newValues) { setState(() { values = newValues; }); }, ), ), ), ), ); }, ), ), ); final RenderBox sliderBox = tester.firstRenderObject<RenderBox>(find.byType(RangeSlider)); // Get the bounds of the track by finding the slider edges and translating // inwards by the overlay radius. final Offset topLeft = tester.getTopLeft(find.byType(RangeSlider)).translate(24, 0); final Offset bottomRight = tester.getBottomRight(find.byType(RangeSlider)).translate(-24, 0); final Offset middle = topLeft + bottomRight / 2; // Drag the thumbs towards the center. final Offset leftTarget = topLeft + (bottomRight - topLeft) * 0.3; await tester.dragFrom(leftTarget, middle - leftTarget); await tester.pumpAndSettle(); final Offset rightTarget = topLeft + (bottomRight - topLeft) * 0.7; await tester.dragFrom(rightTarget, middle - rightTarget); await tester.pumpAndSettle(); expect(values.start, closeTo(0.5, 0.03)); expect(values.end, closeTo(0.5, 0.03)); final TestGesture gesture = await tester.startGesture(middle); await tester.pumpAndSettle(); expect( sliderBox, paints ..path(color: sliderTheme.valueIndicatorColor) ..path(color: sliderTheme.overlappingShapeStrokeColor) ..path(color: sliderTheme.valueIndicatorColor), ); await gesture.up(); }); testWidgets('Range Slider implements debugFillProperties', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); RangeSlider( activeColor: Colors.blue, divisions: 4, inactiveColor: Colors.grey, labels: const RangeLabels('lowerValue', 'upperValue'), max: 100.0, min: 0.0, onChanged: null, onChangeEnd: null, onChangeStart: null, semanticFormatterCallback: null, values: const RangeValues(25.0, 75.0), ).debugFillProperties(builder); final List<String> description = builder.properties .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) .map((DiagnosticsNode node) => node.toString()).toList(); expect(description, <String>[ 'valueStart: 25.0', 'valueEnd: 75.0', 'disabled', 'min: 0.0', 'max: 100.0', 'divisions: 4', 'labelStart: "lowerValue"', 'labelEnd: "upperValue"', 'activeColor: MaterialColor(primary value: Color(0xff2196f3))', 'inactiveColor: MaterialColor(primary value: Color(0xff9e9e9e))' ]); }); }