blob: 448d0c75f97b4fa2365faf3a59c3bbd48582eac7 [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/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart';
// This test is very fragile and bypasses some zone-related checks.
// It is written this way to verify some invariants that would otherwise
// be difficult to check.
// Do not use this test as a guide for writing good Flutter code.
class TestBinding extends WidgetsFlutterBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
}
@override
bool debugCheckZone(String entryPoint) { return true; }
static TestBinding get instance => BindingBase.checkInstance(_instance);
static TestBinding? _instance;
static TestBinding ensureInitialized() {
if (TestBinding._instance == null) {
TestBinding();
}
return TestBinding.instance;
}
}
class CountButton extends StatefulWidget {
const CountButton({super.key});
@override
State<CountButton> createState() => _CountButtonState();
}
class _CountButtonState extends State<CountButton> {
int counter = 0;
@override
Widget build(BuildContext context) {
return ElevatedButton(
child: Text('Counter $counter'),
onPressed: () {
setState(() {
counter += 1;
});
},
);
}
}
class AnimateSample extends StatefulWidget {
const AnimateSample({super.key});
@override
State<AnimateSample> createState() => _AnimateSampleState();
}
class _AnimateSampleState extends State<AnimateSample>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
)..forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, _) => Text('Value: ${_controller.value}'),
);
}
}
void main() {
TestBinding.ensureInitialized();
test('Test pump on LiveWidgetController', () async {
runApp(const MaterialApp(home: Center(child: CountButton())));
await SchedulerBinding.instance.endOfFrame;
final WidgetController controller =
LiveWidgetController(WidgetsBinding.instance);
await controller.tap(find.text('Counter 0'));
expect(find.text('Counter 0'), findsOneWidget);
expect(find.text('Counter 1'), findsNothing);
await controller.pump();
expect(find.text('Counter 0'), findsNothing);
expect(find.text('Counter 1'), findsOneWidget);
});
test('Test pumpAndSettle on LiveWidgetController', () async {
runApp(const MaterialApp(home: Center(child: AnimateSample())));
await SchedulerBinding.instance.endOfFrame;
final WidgetController controller =
LiveWidgetController(WidgetsBinding.instance);
expect(find.text('Value: 1.0'), findsNothing);
await controller.pumpAndSettle();
expect(find.text('Value: 1.0'), findsOneWidget);
});
test('Input event array on LiveWidgetController', () async {
final List<String> logs = <String>[];
runApp(
MaterialApp(
home: Listener(
onPointerDown: (PointerDownEvent event) => logs.add('down ${event.buttons}'),
onPointerMove: (PointerMoveEvent event) => logs.add('move ${event.buttons}'),
onPointerUp: (PointerUpEvent event) => logs.add('up ${event.buttons}'),
child: const Text('test'),
),
),
);
await SchedulerBinding.instance.endOfFrame;
final WidgetController controller =
LiveWidgetController(WidgetsBinding.instance);
final Offset location = controller.getCenter(find.text('test'));
final List<PointerEventRecord> records = <PointerEventRecord>[
PointerEventRecord(Duration.zero, <PointerEvent>[
// Typically PointerAddedEvent is not used in testers, but for records
// captured on a device it is usually what starts a gesture.
PointerAddedEvent(
position: location,
),
PointerDownEvent(
position: location,
buttons: kSecondaryMouseButton,
pointer: 1,
),
]),
...<PointerEventRecord>[
for (Duration t = const Duration(milliseconds: 5);
t < const Duration(milliseconds: 80);
t += const Duration(milliseconds: 16))
PointerEventRecord(t, <PointerEvent>[
PointerMoveEvent(
timeStamp: t - const Duration(milliseconds: 1),
position: location,
buttons: kSecondaryMouseButton,
pointer: 1,
),
]),
],
PointerEventRecord(const Duration(milliseconds: 80), <PointerEvent>[
PointerUpEvent(
timeStamp: const Duration(milliseconds: 79),
position: location,
buttons: kSecondaryMouseButton,
pointer: 1,
),
]),
];
final List<Duration> timeDiffs =
await controller.handlePointerEventRecord(records);
expect(timeDiffs.length, records.length);
for (final Duration diff in timeDiffs) {
// Allow some freedom of time delay in real world.
// TODO(pdblasi-google): The expected wiggle room should be -1, but occasional
// results were reaching -6. This assert has been adjusted to reduce flakiness,
// but the root cause is still unknown. (https://github.com/flutter/flutter/issues/109638)
assert(diff.inMilliseconds > -7, 'timeDiffs were: $timeDiffs (offending time was ${diff.inMilliseconds}ms)');
}
const String b = '$kSecondaryMouseButton';
expect(logs.first, 'down $b');
for (int i = 1; i < logs.length - 1; i++) {
expect(logs[i], 'move $b');
}
expect(logs.last, 'up $b');
});
}