blob: 217d0ebcc926658dbe85b12c60adb267c7be21e4 [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:collection/collection.dart';
// Android MotionEvent actions for which a pointer index is encoded in the
// unmasked action code.
const List<int> kPointerActions = <int>[
0, // DOWN
1, // UP
5, // POINTER_DOWN
6, // POINTER_UP
];
const double kDoubleErrorMargin = 1e-4;
String diffMotionEvents(
Map<String, dynamic> originalEvent,
Map<String, dynamic> synthesizedEvent,
) {
final StringBuffer diff = StringBuffer();
diffMaps(originalEvent, synthesizedEvent, diff, excludeKeys: const <String>[
'pointerProperties', // Compared separately.
'pointerCoords', // Compared separately.
'source', // Unused by Flutter.
'deviceId', // Android documentation says that's an arbitrary number that shouldn't be depended on.
'action', // Compared separately.
'motionEventId', // TODO(kaushikiska): add support for motion event diffing, https://github.com/flutter/flutter/issues/61022.
]);
diffActions(diff, originalEvent, synthesizedEvent);
diffPointerProperties(diff, originalEvent, synthesizedEvent);
diffPointerCoordsList(diff, originalEvent, synthesizedEvent);
return diff.toString();
}
void diffActions(StringBuffer diffBuffer, Map<String, dynamic> originalEvent,
Map<String, dynamic> synthesizedEvent) {
final int synthesizedActionMasked =
getActionMasked(synthesizedEvent['action'] as int);
final int originalActionMasked = getActionMasked(originalEvent['action'] as int);
final String synthesizedActionName =
getActionName(synthesizedActionMasked, synthesizedEvent['action'] as int);
final String originalActionName =
getActionName(originalActionMasked, originalEvent['action'] as int);
if (synthesizedActionMasked != originalActionMasked) {
diffBuffer.write(
'action (expected: $originalActionName actual: $synthesizedActionName) ');
}
if (kPointerActions.contains(originalActionMasked) &&
originalActionMasked == synthesizedActionMasked) {
final int originalPointer = getPointerIdx(originalEvent['action'] as int);
final int synthesizedPointer = getPointerIdx(synthesizedEvent['action'] as int);
if (originalPointer != synthesizedPointer) {
diffBuffer.write(
'pointerIdx (expected: $originalPointer actual: $synthesizedPointer action: $originalActionName ');
}
}
}
void diffPointerProperties(StringBuffer diffBuffer,
Map<String, dynamic> originalEvent, Map<String, dynamic> synthesizedEvent) {
final List<Map<dynamic, dynamic>> expectedList =
(originalEvent['pointerProperties'] as List<dynamic>).cast<Map<dynamic, dynamic>>();
final List<Map<dynamic, dynamic>> actualList =
(synthesizedEvent['pointerProperties'] as List<dynamic>).cast<Map<dynamic, dynamic>>();
if (expectedList.length != actualList.length) {
diffBuffer.write(
'pointerProperties (actual length: ${actualList.length}, expected length: ${expectedList.length} ');
return;
}
for (int i = 0; i < expectedList.length; i++) {
final Map<String, dynamic> expected =
expectedList[i].cast<String, dynamic>();
final Map<String, dynamic> actual = actualList[i].cast<String, dynamic>();
diffMaps(expected, actual, diffBuffer,
messagePrefix: '[pointerProperty $i] ');
}
}
void diffPointerCoordsList(StringBuffer diffBuffer,
Map<String, dynamic> originalEvent, Map<String, dynamic> synthesizedEvent) {
final List<Map<dynamic, dynamic>> expectedList =
(originalEvent['pointerCoords'] as List<dynamic>).cast<Map<dynamic, dynamic>>();
final List<Map<dynamic, dynamic>> actualList =
(synthesizedEvent['pointerCoords'] as List<dynamic>).cast<Map<dynamic, dynamic>>();
if (expectedList.length != actualList.length) {
diffBuffer.write(
'pointerCoords (actual length: ${actualList.length}, expected length: ${expectedList.length} ');
return;
}
for (int i = 0; i < expectedList.length; i++) {
final Map<String, dynamic> expected =
expectedList[i].cast<String, dynamic>();
final Map<String, dynamic> actual = actualList[i].cast<String, dynamic>();
diffPointerCoords(expected, actual, i, diffBuffer);
}
}
void diffPointerCoords(Map<String, dynamic> expected,
Map<String, dynamic> actual, int pointerIdx, StringBuffer diffBuffer) {
diffMaps(expected, actual, diffBuffer, messagePrefix: '[pointerCoord $pointerIdx] ');
}
void diffMaps(
Map<String, dynamic> expected,
Map<String, dynamic> actual,
StringBuffer diffBuffer, {
List<String> excludeKeys = const <String>[],
String messagePrefix = '',
}) {
const IterableEquality<String> eq = IterableEquality<String>();
if (!eq.equals(expected.keys, actual.keys)) {
diffBuffer.write(
'${messagePrefix}keys (expected: ${expected.keys} actual: ${actual.keys} ');
return;
}
for (final String key in expected.keys) {
if (excludeKeys.contains(key)) {
continue;
}
if (doublesApproximatelyMatch(expected[key], actual[key])) {
continue;
}
if (expected[key] != actual[key]) {
diffBuffer.write(
'$messagePrefix$key (expected: ${expected[key]} actual: ${actual[key]}) ');
}
}
}
int getActionMasked(int action) => action & 0xff;
int getPointerIdx(int action) => (action >> 8) & 0xff;
String getActionName(int actionMasked, int action) {
const List<String> actionNames = <String>[
'DOWN',
'UP',
'MOVE',
'CANCEL',
'OUTSIDE',
'POINTER_DOWN',
'POINTER_UP',
'HOVER_MOVE',
'SCROLL',
'HOVER_ENTER',
'HOVER_EXIT',
'BUTTON_PRESS',
'BUTTON_RELEASE',
];
if (actionMasked < actionNames.length) {
return '${actionNames[actionMasked]}($action)';
} else {
return 'ACTION_$actionMasked';
}
}
bool doublesApproximatelyMatch(dynamic a, dynamic b) =>
a is double && b is double && (a - b).abs() < kDoubleErrorMargin;