// Copyright 2016 The Chromium 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/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_test/flutter_test.dart'; import '../rendering/mock_canvas.dart'; import '../widgets/semantics_tester.dart'; void main() { Future<void> _dragSlider(WidgetTester tester, Key sliderKey) { final Offset topLeft = tester.getTopLeft(find.byKey(sliderKey)); const double unit = CupertinoThumbPainter.radius; const double delta = 3.0 * unit; return tester.dragFrom(topLeft + const Offset(unit, unit), const Offset(delta, 0.0)); } testWidgets('Slider does not move when tapped (LTR)', (WidgetTester tester) async { final Key sliderKey = UniqueKey(); double value = 0.0; await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return Material( child: Center( child: CupertinoSlider( key: sliderKey, value: value, onChanged: (double newValue) { setState(() { value = newValue; }); }, ), ), ); }, ), )); expect(value, equals(0.0)); await tester.tap(find.byKey(sliderKey)); expect(value, equals(0.0)); await tester.pump(); // No animation should start. // Check the transientCallbackCount before tearing down the widget to ensure // that no animation is running. expect(SchedulerBinding.instance.transientCallbackCount, equals(0)); }); testWidgets('Slider does not move when tapped (RTL)', (WidgetTester tester) async { final Key sliderKey = UniqueKey(); double value = 0.0; await tester.pumpWidget(Directionality( textDirection: TextDirection.rtl, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return Material( child: Center( child: CupertinoSlider( key: sliderKey, value: value, onChanged: (double newValue) { setState(() { value = newValue; }); }, ), ), ); }, ), )); expect(value, equals(0.0)); await tester.tap(find.byKey(sliderKey)); expect(value, equals(0.0)); await tester.pump(); // No animation should start. // Check the transientCallbackCount before tearing down the widget to ensure // that no animation is running. expect(SchedulerBinding.instance.transientCallbackCount, equals(0)); }); testWidgets('Slider calls onChangeStart once when interaction begins', (WidgetTester tester) async { final Key sliderKey = UniqueKey(); double value = 0.0; int numberOfTimesOnChangeStartIsCalled = 0; await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return Material( child: Center( child: CupertinoSlider( key: sliderKey, value: value, onChanged: (double newValue) { setState(() { value = newValue; }); }, onChangeStart: (double value) { numberOfTimesOnChangeStartIsCalled++; }, ), ), ); }, ), )); await _dragSlider(tester, sliderKey); expect(numberOfTimesOnChangeStartIsCalled, equals(1)); await tester.pump(); // No animation should start. // Check the transientCallbackCount before tearing down the widget to ensure // that no animation is running. expect(SchedulerBinding.instance.transientCallbackCount, equals(0)); }); testWidgets('Slider calls onChangeEnd once after interaction has ended', (WidgetTester tester) async { final Key sliderKey = UniqueKey(); double value = 0.0; int numberOfTimesOnChangeEndIsCalled = 0; await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return Material( child: Center( child: CupertinoSlider( key: sliderKey, value: value, onChanged: (double newValue) { setState(() { value = newValue; }); }, onChangeEnd: (double value) { numberOfTimesOnChangeEndIsCalled++; }, ), ), ); }, ), )); await _dragSlider(tester, sliderKey); expect(numberOfTimesOnChangeEndIsCalled, equals(1)); await tester.pump(); // No animation should start. // Check the transientCallbackCount before tearing down the widget to ensure // that no animation is running. expect(SchedulerBinding.instance.transientCallbackCount, equals(0)); }); testWidgets('Slider moves when dragged (LTR)', (WidgetTester tester) async { final Key sliderKey = UniqueKey(); double value = 0.0; double startValue; double endValue; await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return Material( child: Center( child: CupertinoSlider( key: sliderKey, value: value, onChanged: (double newValue) { setState(() { value = newValue; }); }, onChangeStart: (double value) { startValue = value; }, onChangeEnd: (double value) { endValue = value; }, ), ), ); }, ), )); expect(value, equals(0.0)); final Offset topLeft = tester.getTopLeft(find.byKey(sliderKey)); const double unit = CupertinoThumbPainter.radius; const double delta = 3.0 * unit; await tester.dragFrom(topLeft + const Offset(unit, unit), const Offset(delta, 0.0)); final Size size = tester.getSize(find.byKey(sliderKey)); final double finalValue = delta / (size.width - 2.0 * (8.0 + CupertinoThumbPainter.radius)); expect(startValue, equals(0.0)); expect(value, equals(finalValue)); expect(endValue, equals(finalValue)); await tester.pump(); // No animation should start. // Check the transientCallbackCount before tearing down the widget to ensure // that no animation is running. expect(SchedulerBinding.instance.transientCallbackCount, equals(0)); }); testWidgets('Slider moves when dragged (RTL)', (WidgetTester tester) async { final Key sliderKey = UniqueKey(); double value = 0.0; double startValue; double endValue; await tester.pumpWidget(Directionality( textDirection: TextDirection.rtl, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return Material( child: Center( child: CupertinoSlider( key: sliderKey, value: value, onChanged: (double newValue) { setState(() { value = newValue; }); }, onChangeStart: (double value) { setState(() { startValue = value; }); }, onChangeEnd: (double value) { setState(() { endValue = value; }); }, ), ), ); }, ), )); expect(value, equals(0.0)); final Offset bottomRight = tester.getBottomRight(find.byKey(sliderKey)); const double unit = CupertinoThumbPainter.radius; const double delta = 3.0 * unit; await tester.dragFrom(bottomRight - const Offset(unit, unit), const Offset(-delta, 0.0)); final Size size = tester.getSize(find.byKey(sliderKey)); final double finalValue = delta / (size.width - 2.0 * (8.0 + CupertinoThumbPainter.radius)); expect(startValue, equals(0.0)); expect(value, equals(finalValue)); expect(endValue, equals(finalValue)); await tester.pump(); // No animation should start. // Check the transientCallbackCount before tearing down the widget to ensure // that no animation is running. expect(SchedulerBinding.instance.transientCallbackCount, equals(0)); }); testWidgets('Slider Semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: CupertinoSlider( value: 0.5, onChanged: (double v) { }, ), )); expect(semantics, hasSemantics( TestSemantics.root( children: <TestSemantics>[ TestSemantics.rootChild( id: 1, value: '50%', increasedValue: '60%', decreasedValue: '40%', textDirection: TextDirection.ltr, actions: SemanticsAction.decrease.index | SemanticsAction.increase.index, ), ] ), ignoreRect: true, ignoreTransform: true, )); // Disable slider await tester.pumpWidget(const Directionality( textDirection: TextDirection.ltr, child: CupertinoSlider( value: 0.5, onChanged: null, ), )); expect(semantics, hasSemantics( TestSemantics.root(), ignoreRect: true, ignoreTransform: true, )); semantics.dispose(); }); testWidgets('Slider Semantics can be updated', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); double value = 0.5; await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: CupertinoSlider( value: value, onChanged: (double v) { }, ), )); expect(tester.getSemantics(find.byType(CupertinoSlider)), matchesSemantics( hasIncreaseAction: true, hasDecreaseAction: true, value: '50%', increasedValue: '60%', decreasedValue: '40%', textDirection: TextDirection.ltr, )); value = 0.6; await tester.pumpWidget(Directionality( textDirection: TextDirection.ltr, child: CupertinoSlider( value: value, onChanged: (double v) { }, ), )); expect(tester.getSemantics(find.byType(CupertinoSlider)), matchesSemantics( hasIncreaseAction: true, hasDecreaseAction: true, value: '60%', increasedValue: '70%', decreasedValue: '50%', textDirection: TextDirection.ltr, )); handle.dispose(); }); testWidgets('Slider respects themes', (WidgetTester tester) async { await tester.pumpWidget( CupertinoApp( home: Center( child: CupertinoSlider( onChanged: (double value) { }, value: 0.5, ), ), ), ); expect( find.byType(CupertinoSlider), // First line it paints is blue. paints..rrect(color: CupertinoColors.activeBlue), ); await tester.pumpWidget( CupertinoApp( theme: const CupertinoThemeData(brightness: Brightness.dark), home: Center( child: CupertinoSlider( onChanged: (double value) { }, value: 0.5, ), ), ), ); expect( find.byType(CupertinoSlider), paints..rrect(color: CupertinoColors.activeOrange), ); }); testWidgets('Themes can be overridden', (WidgetTester tester) async { await tester.pumpWidget( CupertinoApp( theme: const CupertinoThemeData(brightness: Brightness.dark), home: Center( child: CupertinoSlider( activeColor: CupertinoColors.activeGreen, onChanged: (double value) { }, value: 0.5, ), ), ), ); expect( find.byType(CupertinoSlider), paints..rrect(color: CupertinoColors.activeGreen), ); }); }