mirror of
https://github.com/flutter/flutter
synced 2024-10-13 03:32:55 +00:00
Add geometry getters to Flutter Driver (#32302)
This commit is contained in:
parent
8b114482a4
commit
ff1dbcdeb6
77
packages/flutter_driver/lib/src/common/geometry.dart
Normal file
77
packages/flutter_driver/lib/src/common/geometry.dart
Normal file
|
@ -0,0 +1,77 @@
|
|||
// Copyright 2019 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_driver/src/common/enum_util.dart';
|
||||
|
||||
import 'find.dart';
|
||||
import 'message.dart';
|
||||
|
||||
/// Offset types that can be requested by [GetOffset].
|
||||
enum OffsetType {
|
||||
/// The top left point.
|
||||
topLeft,
|
||||
|
||||
/// The top right point.
|
||||
topRight,
|
||||
|
||||
/// The bottom left point.
|
||||
bottomLeft,
|
||||
|
||||
/// The bottom right point.
|
||||
bottomRight,
|
||||
|
||||
/// The center point.
|
||||
center,
|
||||
}
|
||||
|
||||
EnumIndex<OffsetType> _offsetTypeIndex = EnumIndex<OffsetType>(OffsetType.values);
|
||||
|
||||
/// A Flutter Driver command that return the [offsetType] from the RenderObject
|
||||
/// identified by [finder].
|
||||
class GetOffset extends CommandWithTarget {
|
||||
/// The `finder` looks for an element to get its rect.
|
||||
GetOffset(SerializableFinder finder, this.offsetType, { Duration timeout }) : super(finder, timeout: timeout);
|
||||
|
||||
/// Deserializes this command from the value generated by [serialize].
|
||||
GetOffset.deserialize(Map<String, dynamic> json)
|
||||
: offsetType = _offsetTypeIndex.lookupBySimpleName(json['offsetType']),
|
||||
super.deserialize(json);
|
||||
|
||||
@override
|
||||
Map<String, String> serialize() => super.serialize()..addAll(<String, String>{
|
||||
'offsetType': _offsetTypeIndex.toSimpleName(offsetType),
|
||||
});
|
||||
|
||||
/// The type of the requested offset.
|
||||
final OffsetType offsetType;
|
||||
|
||||
@override
|
||||
final String kind = 'get_offset';
|
||||
}
|
||||
|
||||
/// The result of the [GetRect] command.
|
||||
class GetOffsetResult extends Result {
|
||||
/// Creates a result with the offset defined by [dx] and [dy].
|
||||
GetOffsetResult({ this.dx = 0.0, this.dy = 0.0});
|
||||
|
||||
/// The x component of the offset.
|
||||
final double dx;
|
||||
|
||||
/// The y component of the offset.
|
||||
final double dy;
|
||||
|
||||
/// Deserializes the result from JSON.
|
||||
static GetOffsetResult fromJson(Map<String, dynamic> json) {
|
||||
return GetOffsetResult(
|
||||
dx: json['dx'],
|
||||
dy: json['dy'],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => <String, double>{
|
||||
'dx': dx,
|
||||
'dy': dy,
|
||||
};
|
||||
}
|
|
@ -19,6 +19,7 @@ import '../common/error.dart';
|
|||
import '../common/find.dart';
|
||||
import '../common/frame_sync.dart';
|
||||
import '../common/fuchsia_compat.dart';
|
||||
import '../common/geometry.dart';
|
||||
import '../common/gesture.dart';
|
||||
import '../common/health.dart';
|
||||
import '../common/message.dart';
|
||||
|
@ -466,6 +467,37 @@ class FlutterDriver {
|
|||
await _sendCommand(WaitUntilNoTransientCallbacks(timeout: timeout));
|
||||
}
|
||||
|
||||
Future<DriverOffset> _getOffset(SerializableFinder finder, OffsetType type, { Duration timeout }) async {
|
||||
final GetOffset command = GetOffset(finder, type, timeout: timeout);
|
||||
final GetOffsetResult result = GetOffsetResult.fromJson(await _sendCommand(command));
|
||||
return DriverOffset(result.dx, result.dy);
|
||||
}
|
||||
|
||||
/// Returns the point at the top left of the widget identified by `finder`.
|
||||
Future<DriverOffset> getTopLeft(SerializableFinder finder, { Duration timeout }) async {
|
||||
return _getOffset(finder, OffsetType.topLeft, timeout: timeout);
|
||||
}
|
||||
|
||||
/// Returns the point at the top right of the widget identified by `finder`.
|
||||
Future<DriverOffset> getTopRight(SerializableFinder finder, { Duration timeout }) async {
|
||||
return _getOffset(finder, OffsetType.topRight, timeout: timeout);
|
||||
}
|
||||
|
||||
/// Returns the point at the bottom left of the widget identified by `finder`.
|
||||
Future<DriverOffset> getBottomLeft(SerializableFinder finder, { Duration timeout }) async {
|
||||
return _getOffset(finder, OffsetType.bottomLeft, timeout: timeout);
|
||||
}
|
||||
|
||||
/// Returns the point at the bottom right of the widget identified by `finder`.
|
||||
Future<DriverOffset> getBottomRight(SerializableFinder finder, { Duration timeout }) async {
|
||||
return _getOffset(finder, OffsetType.bottomRight, timeout: timeout);
|
||||
}
|
||||
|
||||
/// Returns the point at the center of the widget identified by `finder`.
|
||||
Future<DriverOffset> getCenter(SerializableFinder finder, { Duration timeout }) async {
|
||||
return _getOffset(finder, OffsetType.center, timeout: timeout);
|
||||
}
|
||||
|
||||
/// Tell the driver to perform a scrolling action.
|
||||
///
|
||||
/// A scrolling action begins with a "pointer down" event, which commonly maps
|
||||
|
@ -986,3 +1018,29 @@ class CommonFinders {
|
|||
/// Finds the back button on a Material or Cupertino page's scaffold.
|
||||
SerializableFinder pageBack() => PageBack();
|
||||
}
|
||||
|
||||
/// An immutable 2D floating-point offset used by Flutter Driver.
|
||||
class DriverOffset {
|
||||
/// Creates an offset.
|
||||
const DriverOffset(this.dx, this.dy);
|
||||
|
||||
/// The x component of the offset.
|
||||
final double dx;
|
||||
|
||||
/// The y component of the offset.
|
||||
final double dy;
|
||||
|
||||
@override
|
||||
String toString() => '$runtimeType($dx, $dy)';
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (other is! DriverOffset)
|
||||
return false;
|
||||
final DriverOffset typedOther = other;
|
||||
return dx == typedOther.dx && dy == typedOther.dy;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => dx.hashCode + dy.hashCode;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import 'package:flutter_test/flutter_test.dart';
|
|||
import '../common/error.dart';
|
||||
import '../common/find.dart';
|
||||
import '../common/frame_sync.dart';
|
||||
import '../common/geometry.dart';
|
||||
import '../common/gesture.dart';
|
||||
import '../common/health.dart';
|
||||
import '../common/message.dart';
|
||||
|
@ -112,6 +113,7 @@ class FlutterDriverExtension {
|
|||
'waitForAbsent': _waitForAbsent,
|
||||
'waitUntilNoTransientCallbacks': _waitUntilNoTransientCallbacks,
|
||||
'get_semantics_id': _getSemanticsId,
|
||||
'get_offset': _getOffset,
|
||||
});
|
||||
|
||||
_commandDeserializers.addAll(<String, CommandDeserializerCallback>{
|
||||
|
@ -130,6 +132,7 @@ class FlutterDriverExtension {
|
|||
'waitForAbsent': (Map<String, String> params) => WaitForAbsent.deserialize(params),
|
||||
'waitUntilNoTransientCallbacks': (Map<String, String> params) => WaitUntilNoTransientCallbacks.deserialize(params),
|
||||
'get_semantics_id': (Map<String, String> params) => GetSemanticsId.deserialize(params),
|
||||
'get_offset': (Map<String, String> params) => GetOffset.deserialize(params),
|
||||
});
|
||||
|
||||
_finders.addAll(<String, FinderConstructor>{
|
||||
|
@ -358,6 +361,33 @@ class FlutterDriverExtension {
|
|||
return GetSemanticsIdResult(node.id);
|
||||
}
|
||||
|
||||
Future<GetOffsetResult> _getOffset(Command command) async {
|
||||
final GetOffset getOffsetCommand = command;
|
||||
final Finder finder = await _waitForElement(_createFinder(getOffsetCommand.finder));
|
||||
final Element element = finder.evaluate().single;
|
||||
final RenderBox box = element.renderObject;
|
||||
Offset localPoint;
|
||||
switch (getOffsetCommand.offsetType) {
|
||||
case OffsetType.topLeft:
|
||||
localPoint = Offset.zero;
|
||||
break;
|
||||
case OffsetType.topRight:
|
||||
localPoint = box.size.topRight(Offset.zero);
|
||||
break;
|
||||
case OffsetType.bottomLeft:
|
||||
localPoint = box.size.bottomLeft(Offset.zero);
|
||||
break;
|
||||
case OffsetType.bottomRight:
|
||||
localPoint = box.size.bottomRight(Offset.zero);
|
||||
break;
|
||||
case OffsetType.center:
|
||||
localPoint = box.size.center(Offset.zero);
|
||||
break;
|
||||
}
|
||||
final Offset globalPoint = box.localToGlobal(localPoint);
|
||||
return GetOffsetResult(dx: globalPoint.dx, dy: globalPoint.dy);
|
||||
}
|
||||
|
||||
Future<ScrollResult> _scroll(Command command) async {
|
||||
final Scroll scrollCommand = command;
|
||||
final Finder target = await _waitForElement(_createFinder(scrollCommand.finder));
|
||||
|
|
|
@ -261,6 +261,111 @@ void main() {
|
|||
});
|
||||
});
|
||||
|
||||
group('getOffset', () {
|
||||
test('requires a target reference', () async {
|
||||
expect(driver.getCenter(null), throwsA(isInstanceOf<DriverError>()));
|
||||
expect(driver.getTopLeft(null), throwsA(isInstanceOf<DriverError>()));
|
||||
expect(driver.getTopRight(null), throwsA(isInstanceOf<DriverError>()));
|
||||
expect(driver.getBottomLeft(null), throwsA(isInstanceOf<DriverError>()));
|
||||
expect(driver.getBottomRight(null), throwsA(isInstanceOf<DriverError>()));
|
||||
});
|
||||
|
||||
test('sends the getCenter command', () async {
|
||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||
expect(i.positionalArguments[1], <String, dynamic>{
|
||||
'command': 'get_offset',
|
||||
'offsetType': 'center',
|
||||
'timeout': _kSerializedTestTimeout,
|
||||
'finderType': 'ByValueKey',
|
||||
'keyValueString': '123',
|
||||
'keyValueType': 'int',
|
||||
});
|
||||
return makeMockResponse(<String, double>{
|
||||
'dx': 11,
|
||||
'dy': 12,
|
||||
});
|
||||
});
|
||||
final DriverOffset result = await driver.getCenter(find.byValueKey(123), timeout: _kTestTimeout);
|
||||
expect(result, const DriverOffset(11, 12));
|
||||
});
|
||||
|
||||
test('sends the getTopLeft command', () async {
|
||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||
expect(i.positionalArguments[1], <String, dynamic>{
|
||||
'command': 'get_offset',
|
||||
'offsetType': 'topLeft',
|
||||
'timeout': _kSerializedTestTimeout,
|
||||
'finderType': 'ByValueKey',
|
||||
'keyValueString': '123',
|
||||
'keyValueType': 'int',
|
||||
});
|
||||
return makeMockResponse(<String, double>{
|
||||
'dx': 11,
|
||||
'dy': 12,
|
||||
});
|
||||
});
|
||||
final DriverOffset result = await driver.getTopLeft(find.byValueKey(123), timeout: _kTestTimeout);
|
||||
expect(result, const DriverOffset(11, 12));
|
||||
});
|
||||
|
||||
test('sends the getTopRight command', () async {
|
||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||
expect(i.positionalArguments[1], <String, dynamic>{
|
||||
'command': 'get_offset',
|
||||
'offsetType': 'topRight',
|
||||
'timeout': _kSerializedTestTimeout,
|
||||
'finderType': 'ByValueKey',
|
||||
'keyValueString': '123',
|
||||
'keyValueType': 'int',
|
||||
});
|
||||
return makeMockResponse(<String, double>{
|
||||
'dx': 11,
|
||||
'dy': 12,
|
||||
});
|
||||
});
|
||||
final DriverOffset result = await driver.getTopRight(find.byValueKey(123), timeout: _kTestTimeout);
|
||||
expect(result, const DriverOffset(11, 12));
|
||||
});
|
||||
|
||||
test('sends the getBottomLeft command', () async {
|
||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||
expect(i.positionalArguments[1], <String, dynamic>{
|
||||
'command': 'get_offset',
|
||||
'offsetType': 'bottomLeft',
|
||||
'timeout': _kSerializedTestTimeout,
|
||||
'finderType': 'ByValueKey',
|
||||
'keyValueString': '123',
|
||||
'keyValueType': 'int',
|
||||
});
|
||||
return makeMockResponse(<String, double>{
|
||||
'dx': 11,
|
||||
'dy': 12,
|
||||
});
|
||||
});
|
||||
final DriverOffset result = await driver.getBottomLeft(find.byValueKey(123), timeout: _kTestTimeout);
|
||||
expect(result, const DriverOffset(11, 12));
|
||||
});
|
||||
|
||||
test('sends the getBottomRight command', () async {
|
||||
when(mockIsolate.invokeExtension(any, any)).thenAnswer((Invocation i) {
|
||||
expect(i.positionalArguments[1], <String, dynamic>{
|
||||
'command': 'get_offset',
|
||||
'offsetType': 'bottomRight',
|
||||
'timeout': _kSerializedTestTimeout,
|
||||
'finderType': 'ByValueKey',
|
||||
'keyValueString': '123',
|
||||
'keyValueType': 'int',
|
||||
});
|
||||
return makeMockResponse(<String, double>{
|
||||
'dx': 11,
|
||||
'dy': 12,
|
||||
});
|
||||
});
|
||||
final DriverOffset result = await driver.getBottomRight(find.byValueKey(123), timeout: _kTestTimeout);
|
||||
expect(result, const DriverOffset(11, 12));
|
||||
});
|
||||
});
|
||||
|
||||
group('clearTimeline', () {
|
||||
test('clears timeline', () async {
|
||||
bool clearWasCalled = false;
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:flutter/rendering.dart';
|
|||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_driver/src/common/find.dart';
|
||||
import 'package:flutter_driver/src/common/geometry.dart';
|
||||
import 'package:flutter_driver/src/common/request_data.dart';
|
||||
import 'package:flutter_driver/src/extension/extension.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
@ -120,4 +121,34 @@ void main() {
|
|||
semantics.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('getOffset', (WidgetTester tester) async {
|
||||
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true);
|
||||
|
||||
Future<Offset> getOffset(OffsetType offset) async {
|
||||
final Map<String, Object> arguments = GetOffset(ByValueKey(1), offset).serialize();
|
||||
final GetOffsetResult result = GetOffsetResult.fromJson((await extension.call(arguments))['response']);
|
||||
return Offset(result.dx, result.dy);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Transform.translate(
|
||||
offset: const Offset(40, 30),
|
||||
child: Container(
|
||||
key: const ValueKey<int>(1),
|
||||
width: 100,
|
||||
height: 120,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(await getOffset(OffsetType.topLeft), const Offset(40, 30));
|
||||
expect(await getOffset(OffsetType.topRight), const Offset(40 + 100.0, 30));
|
||||
expect(await getOffset(OffsetType.bottomLeft), const Offset(40, 30 + 120.0));
|
||||
expect(await getOffset(OffsetType.bottomRight), const Offset(40 + 100.0, 30 + 120.0));
|
||||
expect(await getOffset(OffsetType.center), const Offset(40 + (100 / 2), 30 + (120 / 2)));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue