jslavitz cea4aa9b7b
Teach drag start behaviors to DragGestureRecognizer (#26246)
* the onStart callback will report the location of the pointer where it wins the gesture arena by default instead of the pointer down location. Fixes all tests related to changing this default value.
2019-01-09 10:53:47 -08:00

628 lines
19 KiB
Dart

// 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/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/gestures.dart';
import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart';
void main() {
testWidgets('Switch can toggle on tap', (WidgetTester tester) async {
final Key switchKey = UniqueKey();
bool value = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Switch(
dragStartBehavior: DragStartBehavior.down,
key: switchKey,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
);
expect(value, isFalse);
await tester.tap(find.byKey(switchKey));
expect(value, isTrue);
});
testWidgets('Switch size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
await tester.pumpWidget(
Theme(
data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
child: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: Switch(
dragStartBehavior: DragStartBehavior.down,
value: true,
onChanged: (bool newValue) {},
),
),
),
),
),
);
expect(tester.getSize(find.byType(Switch)), const Size(59.0, 48.0));
await tester.pumpWidget(
Theme(
data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
child: Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Center(
child: Switch(
dragStartBehavior: DragStartBehavior.down,
value: true,
onChanged: (bool newValue) {},
),
),
),
),
),
);
expect(tester.getSize(find.byType(Switch)), const Size(59.0, 40.0));
});
testWidgets('Switch can drag (LTR)', (WidgetTester tester) async {
bool value = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Switch(
dragStartBehavior: DragStartBehavior.down,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
);
expect(value, isFalse);
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
expect(value, isFalse);
await tester.drag(find.byType(Switch), const Offset(30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(Switch), const Offset(30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
expect(value, isFalse);
});
testWidgets('Switch can drag with dragStartBehavior', (WidgetTester tester) async {
bool value = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Switch(
dragStartBehavior: DragStartBehavior.down,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
}
);
}
),
),
);
},
),
),
);
expect(value, isFalse);
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
expect(value, isFalse);
await tester.drag(find.byType(Switch), const Offset(30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(Switch), const Offset(30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
expect(value, isFalse);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Switch(
dragStartBehavior: DragStartBehavior.start,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
}
),
),
);
},
),
),
);
await tester.pumpAndSettle();
final Rect switchRect = tester.getRect(find.byType(Switch));
TestGesture gesture = await tester.startGesture(switchRect.center);
// We have to execute the drag in two frames because the first update will
// just set the start position.
await gesture.moveBy(const Offset(20.0, 0.0));
await gesture.moveBy(const Offset(20.0, 0.0));
expect(value, isTrue);
await gesture.up();
await tester.pump();
gesture = await tester.startGesture(switchRect.center);
await gesture.moveBy(const Offset(20.0, 0.0));
await gesture.moveBy(const Offset(20.0, 0.0));
expect(value, isTrue);
await gesture.up();
await tester.pump();
gesture = await tester.startGesture(switchRect.center);
await gesture.moveBy(const Offset(-20.0, 0.0));
await gesture.moveBy(const Offset(-20.0, 0.0));
expect(value, isFalse);
});
testWidgets('Switch can drag (RTL)', (WidgetTester tester) async {
bool value = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Switch(
dragStartBehavior: DragStartBehavior.down,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
);
await tester.drag(find.byType(Switch), const Offset(30.0, 0.0));
expect(value, isFalse);
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
expect(value, isTrue);
await tester.pump();
await tester.drag(find.byType(Switch), const Offset(30.0, 0.0));
expect(value, isFalse);
});
testWidgets('Switch has default colors when enabled', (WidgetTester tester) async {
bool value = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Switch(
dragStartBehavior: DragStartBehavior.down,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: const Color(0x52000000), // Black with 32% opacity
rrect: RRect.fromLTRBR(
383.5, 293.0, 416.5, 307.0, const Radius.circular(7.0)))
..circle(color: const Color(0x33000000))
..circle(color: const Color(0x24000000))
..circle(color: const Color(0x1f000000))
..circle(color: Colors.grey.shade50),
reason: 'Inactive enabled switch should match these colors',
);
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
await tester.pump();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: Colors.blue[600].withAlpha(0x80),
rrect: RRect.fromLTRBR(
383.5, 293.0, 416.5, 307.0, const Radius.circular(7.0)))
..circle(color: const Color(0x33000000))
..circle(color: const Color(0x24000000))
..circle(color: const Color(0x1f000000))
..circle(color: Colors.blue[600]),
reason: 'Active enabled switch should match these colors',
);
});
testWidgets('Switch has default colors when disabled', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return const Material(
child: Center(
child: Switch(
value: false,
onChanged: null,
),
),
);
},
),
),
);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: Colors.black12,
rrect: RRect.fromLTRBR(
383.5, 293.0, 416.5, 307.0, const Radius.circular(7.0)))
..circle(color: const Color(0x33000000))
..circle(color: const Color(0x24000000))
..circle(color: const Color(0x1f000000))
..circle(color: Colors.grey.shade400),
reason: 'Inactive disabled switch should match these colors',
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return const Material(
child: Center(
child: Switch(
value: true,
onChanged: null,
),
),
);
},
),
),
);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: Colors.black12,
rrect: RRect.fromLTRBR(
383.5, 293.0, 416.5, 307.0, const Radius.circular(7.0)))
..circle(color: const Color(0x33000000))
..circle(color: const Color(0x24000000))
..circle(color: const Color(0x1f000000))
..circle(color: Colors.grey.shade400),
reason: 'Active disabled switch should match these colors',
);
});
testWidgets('Switch can be set color', (WidgetTester tester) async {
bool value = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.rtl,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Switch(
dragStartBehavior: DragStartBehavior.down,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
activeColor: Colors.red[500],
activeTrackColor: Colors.green[500],
inactiveThumbColor: Colors.yellow[500],
inactiveTrackColor: Colors.blue[500],
),
),
);
},
),
),
);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: Colors.blue[500],
rrect: RRect.fromLTRBR(
383.5, 293.0, 416.5, 307.0, const Radius.circular(7.0)))
..circle(color: const Color(0x33000000))
..circle(color: const Color(0x24000000))
..circle(color: const Color(0x1f000000))
..circle(color: Colors.yellow[500])
);
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
await tester.pump();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: Colors.green[500],
rrect: RRect.fromLTRBR(
383.5, 293.0, 416.5, 307.0, const Radius.circular(7.0)))
..circle(color: const Color(0x33000000))
..circle(color: const Color(0x24000000))
..circle(color: const Color(0x1f000000))
..circle(color: Colors.red[500])
);
});
testWidgets('Drag ends after animation completes', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/17773
bool value = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Switch(
dragStartBehavior: DragStartBehavior.down,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
);
expect(value, isFalse);
final Rect switchRect = tester.getRect(find.byType(Switch));
final TestGesture gesture = await tester.startGesture(switchRect.centerLeft);
await tester.pump();
await gesture.moveBy(Offset(switchRect.width, 0.0));
await tester.pump();
await gesture.up();
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
expect(value, isTrue);
expect(tester.hasRunningAnimations, false);
});
testWidgets('switch has semantic events', (WidgetTester tester) async {
dynamic semanticEvent;
bool value = false;
SystemChannels.accessibility.setMockMessageHandler((dynamic message) async {
semanticEvent = message;
});
final SemanticsTester semanticsTester = SemanticsTester(tester);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Switch(
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
);
await tester.tap(find.byType(Switch));
final RenderObject object = tester.firstRenderObject(find.byType(Switch));
expect(value, true);
expect(semanticEvent, <String, dynamic>{
'type': 'tap',
'nodeId': object.debugSemantics.id,
'data': <String, dynamic>{},
});
expect(object.debugSemantics.getSemanticsData().hasAction(SemanticsAction.tap), true);
semanticsTester.dispose();
SystemChannels.accessibility.setMockMessageHandler(null);
});
testWidgets('switch sends semantic events from parent if fully merged', (WidgetTester tester) async {
dynamic semanticEvent;
bool value = false;
SystemChannels.accessibility.setMockMessageHandler((dynamic message) async {
semanticEvent = message;
});
final SemanticsTester semanticsTester = SemanticsTester(tester);
await tester.pumpWidget(
MaterialApp(
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
void onChanged(bool newValue) {
setState(() {
value = newValue;
});
}
return Material(
child: MergeSemantics(
child: ListTile(
leading: const Text('test'),
onTap: () {
onChanged(!value);
},
trailing: Switch(
value: value,
onChanged: onChanged,
),
),
),
);
},
),
),
);
await tester.tap(find.byType(MergeSemantics));
final RenderObject object = tester.firstRenderObject(find.byType(MergeSemantics));
expect(value, true);
expect(semanticEvent, <String, dynamic>{
'type': 'tap',
'nodeId': object.debugSemantics.id,
'data': <String, dynamic>{},
});
expect(object.debugSemantics.getSemanticsData().hasAction(SemanticsAction.tap), true);
semanticsTester.dispose();
SystemChannels.accessibility.setMockMessageHandler(null);
});
testWidgets('Switch.adaptive', (WidgetTester tester) async {
bool value = false;
Widget buildFrame(TargetPlatform platform) {
return MaterialApp(
theme: ThemeData(platform: platform),
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Switch.adaptive(
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
);
}
await tester.pumpWidget(buildFrame(TargetPlatform.iOS));
expect(find.byType(CupertinoSwitch), findsOneWidget);
expect(value, isFalse);
await tester.tap(find.byType(Switch));
expect(value, isTrue);
await tester.pumpWidget(buildFrame(TargetPlatform.android));
await tester.pumpAndSettle(); // Finish the theme change animation.
expect(find.byType(CupertinoSwitch), findsNothing);
expect(value, isTrue);
await tester.tap(find.byType(Switch));
expect(value, isFalse);
});
}