Unverified Commit 4e2e6979 authored by Chris Bracken's avatar Chris Bracken Committed by GitHub

Eliminate MethodCall hashCode and equals (#12277)

Since MethodCall equality checks are limited to test scenarios, this
patch replaces them with an equivalent test matcher. At present
MethodCalls are always used in scenarios where indentity-based
equality/hashing is appropriate.

This change avoids an assertion failure when MethodCall args are
Iterable (possible since args are of type dyanmic), and hashValue() from
dart:ui asserts that its input is not an Iterable.

The alternative of implementing support for deep equality in dart:ui was
rejected on the basis that if we're to encourage performant code,
expensive checks should be obviously-expensive to the author.
parent 354ab0ce
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui' show hashValues;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -46,48 +45,6 @@ class MethodCall { ...@@ -46,48 +45,6 @@ class MethodCall {
/// Must be a valid value for the [MethodCodec] used. /// Must be a valid value for the [MethodCodec] used.
final dynamic arguments; final dynamic arguments;
@override
bool operator == (dynamic other) {
if (identical(this, other))
return true;
if (runtimeType != other.runtimeType)
return false;
return method == other.method && _deepEquals(arguments, other.arguments);
}
@override
int get hashCode => hashValues(method, arguments);
bool _deepEquals(dynamic a, dynamic b) {
if (a == b)
return true;
if (a is List)
return b is List && _deepEqualsList(a, b);
if (a is Map)
return b is Map && _deepEqualsMap(a, b);
return false;
}
bool _deepEqualsList(List<dynamic> a, List<dynamic> b) {
if (a.length != b.length)
return false;
for (int i = 0; i < a.length; i++) {
if (!_deepEquals(a[i], b[i]))
return false;
}
return true;
}
bool _deepEqualsMap(Map<dynamic, dynamic> a, Map<dynamic, dynamic> b) {
if (a.length != b.length)
return false;
for (dynamic key in a.keys) {
if (!b.containsKey(key) || !_deepEquals(a[key], b[key]))
return false;
}
return true;
}
@override @override
String toString() => '$runtimeType($method, $arguments)'; String toString() => '$runtimeType($method, $arguments)';
} }
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'message_codecs_utils.dart';
void main() { void main() {
test('Haptic feedback control test', () async { test('Haptic feedback control test', () async {
final List<MethodCall> log = <MethodCall>[]; final List<MethodCall> log = <MethodCall>[];
...@@ -15,6 +17,7 @@ void main() { ...@@ -15,6 +17,7 @@ void main() {
await HapticFeedback.vibrate(); await HapticFeedback.vibrate();
expect(log, equals(<MethodCall>[const MethodCall('HapticFeedback.vibrate')])); expect(log, hasLength(1));
expect(log.single, isMethodCall('HapticFeedback.vibrate', arguments: null));
}); });
} }
// Copyright 2017 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/services.dart' show MethodCall;
import 'package:meta/meta.dart';
import 'package:test/test.dart';
class _IsMethodCall extends Matcher {
const _IsMethodCall(this.name, this.arguments);
final String name;
final dynamic arguments;
@override
bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
if (item is! MethodCall)
return false;
if (item.method != name)
return false;
return _deepEquals(item.arguments, arguments);
}
bool _deepEquals(dynamic a, dynamic b) {
if (a == b)
return true;
if (a is List)
return b is List && _deepEqualsList(a, b);
if (a is Map)
return b is Map && _deepEqualsMap(a, b);
return false;
}
bool _deepEqualsList(List<dynamic> a, List<dynamic> b) {
if (a.length != b.length)
return false;
for (int i = 0; i < a.length; i++) {
if (!_deepEquals(a[i], b[i]))
return false;
}
return true;
}
bool _deepEqualsMap(Map<dynamic, dynamic> a, Map<dynamic, dynamic> b) {
if (a.length != b.length)
return false;
for (dynamic key in a.keys) {
if (!b.containsKey(key) || !_deepEquals(a[key], b[key]))
return false;
}
return true;
}
@override
Description describe(Description description) {
return description
.add('has method name: ').addDescriptionOf(name)
.add(' with arguments: ').addDescriptionOf(arguments);
}
}
/// Returns a matcher that matches [MethodCall] instances with the specified
/// method name and arguments.
///
/// Arguments checking implements deep equality for [List] and [Map] types.
Matcher isMethodCall(String name, {@required dynamic arguments}) {
return new _IsMethodCall(name, arguments);
}
...@@ -7,6 +7,8 @@ import 'dart:typed_data'; ...@@ -7,6 +7,8 @@ import 'dart:typed_data';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'message_codecs_utils.dart';
void main() { void main() {
testWidgets('SystemChrome overlay style test', (WidgetTester tester) async { testWidgets('SystemChrome overlay style test', (WidgetTester tester) async {
// The first call is a cache miss and will queue a microtask // The first call is a cache miss and will queue a microtask
...@@ -33,10 +35,11 @@ void main() { ...@@ -33,10 +35,11 @@ void main() {
DeviceOrientation.portraitUp, DeviceOrientation.portraitUp,
]); ]);
expect(log, equals(<MethodCall>[new MethodCall( expect(log, hasLength(1));
expect(log.single, isMethodCall(
'SystemChrome.setPreferredOrientations', 'SystemChrome.setPreferredOrientations',
<String>['DeviceOrientation.portraitUp'], arguments: <String>['DeviceOrientation.portraitUp'],
)])); ));
}); });
test('setApplicationSwitcherDescription control test', () async { test('setApplicationSwitcherDescription control test', () async {
...@@ -50,10 +53,11 @@ void main() { ...@@ -50,10 +53,11 @@ void main() {
const ApplicationSwitcherDescription(label: 'Example label', primaryColor: 0xFF00FF00) const ApplicationSwitcherDescription(label: 'Example label', primaryColor: 0xFF00FF00)
); );
expect(log, equals(<MethodCall>[new MethodCall( expect(log, hasLength(1));
expect(log.single, isMethodCall(
'SystemChrome.setApplicationSwitcherDescription', 'SystemChrome.setApplicationSwitcherDescription',
<String, dynamic>{'label':'Example label','primaryColor':4278255360} arguments: <String, dynamic>{'label': 'Example label', 'primaryColor': 4278255360},
)])); ));
}); });
test('setApplicationSwitcherDescription missing plugin', () async { test('setApplicationSwitcherDescription missing plugin', () async {
...@@ -80,9 +84,10 @@ void main() { ...@@ -80,9 +84,10 @@ void main() {
await SystemChrome.setEnabledSystemUIOverlays(<SystemUiOverlay>[SystemUiOverlay.top]); await SystemChrome.setEnabledSystemUIOverlays(<SystemUiOverlay>[SystemUiOverlay.top]);
expect(log, equals(<MethodCall>[new MethodCall( expect(log, hasLength(1));
expect(log.single, isMethodCall(
'SystemChrome.setEnabledSystemUIOverlays', 'SystemChrome.setEnabledSystemUIOverlays',
<String>['SystemUiOverlay.top'], arguments: <String>['SystemUiOverlay.top'],
)])); ));
}); });
} }
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'message_codecs_utils.dart';
void main() { void main() {
test('System navigator control test', () async { test('System navigator control test', () async {
final List<MethodCall> log = <MethodCall>[]; final List<MethodCall> log = <MethodCall>[];
...@@ -15,6 +17,7 @@ void main() { ...@@ -15,6 +17,7 @@ void main() {
await SystemNavigator.pop(); await SystemNavigator.pop();
expect(log, equals(<MethodCall>[const MethodCall('SystemNavigator.pop')])); expect(log, hasLength(1));
expect(log.single, isMethodCall('SystemNavigator.pop', arguments: null));
}); });
} }
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'message_codecs_utils.dart';
void main() { void main() {
test('System sound control test', () async { test('System sound control test', () async {
final List<MethodCall> log = <MethodCall>[]; final List<MethodCall> log = <MethodCall>[];
...@@ -15,6 +17,7 @@ void main() { ...@@ -15,6 +17,7 @@ void main() {
await SystemSound.play(SystemSoundType.click); await SystemSound.play(SystemSoundType.click);
expect(log, equals(<MethodCall>[const MethodCall('SystemSound.play', 'SystemSoundType.click')])); expect(log, hasLength(1));
expect(log.single, isMethodCall('SystemSound.play', arguments: 'SystemSoundType.click'));
}); });
} }
...@@ -10,6 +10,7 @@ import 'package:flutter/material.dart'; ...@@ -10,6 +10,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import '../services/message_codecs_utils.dart';
import 'semantics_tester.dart'; import 'semantics_tester.dart';
void main() { void main() {
...@@ -243,17 +244,19 @@ void main() { ...@@ -243,17 +244,19 @@ void main() {
}); });
await tester.pump(); await tester.pump();
expect(log, <MethodCall>[ expect(log, hasLength(1));
const MethodCall('TextInput.setEditingState', const <String, dynamic>{ expect(log.single, isMethodCall(
'text': 'Wobble', 'TextInput.setEditingState',
'selectionBase': -1, arguments: const <String, dynamic>{
'selectionExtent': -1, 'text': 'Wobble',
'selectionAffinity': 'TextAffinity.downstream', 'selectionBase': -1,
'selectionIsDirectional': false, 'selectionExtent': -1,
'composingBase': -1, 'selectionAffinity': 'TextAffinity.downstream',
'composingExtent': -1, 'selectionIsDirectional': false,
}), 'composingBase': -1,
]); 'composingExtent': -1,
},
));
}); });
testWidgets('EditableText identifies as text field (w/ focus) in semantics', (WidgetTester tester) async { testWidgets('EditableText identifies as text field (w/ focus) in semantics', (WidgetTester tester) async {
......
...@@ -6,6 +6,8 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -6,6 +6,8 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import '../services/message_codecs_utils.dart';
void main() { void main() {
testWidgets('toString control test', (WidgetTester tester) async { testWidgets('toString control test', (WidgetTester tester) async {
final Widget widget = new Title( final Widget widget = new Title(
...@@ -58,9 +60,10 @@ void main() { ...@@ -58,9 +60,10 @@ void main() {
color: const Color(0xFF00FF00), color: const Color(0xFF00FF00),
)); ));
expect(log, equals(<MethodCall>[new MethodCall( expect(log, hasLength(1));
'SystemChrome.setApplicationSwitcherDescription', expect(log.single, isMethodCall(
<String, dynamic>{'label': '', 'primaryColor': 4278255360}, 'SystemChrome.setApplicationSwitcherDescription',
)])); arguments: <String, dynamic>{'label': '', 'primaryColor': 4278255360},
));
}); });
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment