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 @@
// found in the LICENSE file.
import 'dart:typed_data';
import 'dart:ui' show hashValues;
import 'package:flutter/foundation.dart';
......@@ -46,48 +45,6 @@ class MethodCall {
/// Must be a valid value for the [MethodCodec] used.
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
String toString() => '$runtimeType($method, $arguments)';
}
......
......@@ -5,6 +5,8 @@
import 'package:flutter/services.dart';
import 'package:test/test.dart';
import 'message_codecs_utils.dart';
void main() {
test('Haptic feedback control test', () async {
final List<MethodCall> log = <MethodCall>[];
......@@ -15,6 +17,7 @@ void main() {
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';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'message_codecs_utils.dart';
void main() {
testWidgets('SystemChrome overlay style test', (WidgetTester tester) async {
// The first call is a cache miss and will queue a microtask
......@@ -33,10 +35,11 @@ void main() {
DeviceOrientation.portraitUp,
]);
expect(log, equals(<MethodCall>[new MethodCall(
expect(log, hasLength(1));
expect(log.single, isMethodCall(
'SystemChrome.setPreferredOrientations',
<String>['DeviceOrientation.portraitUp'],
)]));
arguments: <String>['DeviceOrientation.portraitUp'],
));
});
test('setApplicationSwitcherDescription control test', () async {
......@@ -50,10 +53,11 @@ void main() {
const ApplicationSwitcherDescription(label: 'Example label', primaryColor: 0xFF00FF00)
);
expect(log, equals(<MethodCall>[new MethodCall(
expect(log, hasLength(1));
expect(log.single, isMethodCall(
'SystemChrome.setApplicationSwitcherDescription',
<String, dynamic>{'label':'Example label','primaryColor':4278255360}
)]));
arguments: <String, dynamic>{'label': 'Example label', 'primaryColor': 4278255360},
));
});
test('setApplicationSwitcherDescription missing plugin', () async {
......@@ -80,9 +84,10 @@ void main() {
await SystemChrome.setEnabledSystemUIOverlays(<SystemUiOverlay>[SystemUiOverlay.top]);
expect(log, equals(<MethodCall>[new MethodCall(
expect(log, hasLength(1));
expect(log.single, isMethodCall(
'SystemChrome.setEnabledSystemUIOverlays',
<String>['SystemUiOverlay.top'],
)]));
arguments: <String>['SystemUiOverlay.top'],
));
});
}
......@@ -5,6 +5,8 @@
import 'package:flutter/services.dart';
import 'package:test/test.dart';
import 'message_codecs_utils.dart';
void main() {
test('System navigator control test', () async {
final List<MethodCall> log = <MethodCall>[];
......@@ -15,6 +17,7 @@ void main() {
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 @@
import 'package:flutter/services.dart';
import 'package:test/test.dart';
import 'message_codecs_utils.dart';
void main() {
test('System sound control test', () async {
final List<MethodCall> log = <MethodCall>[];
......@@ -15,6 +17,7 @@ void main() {
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';
import 'package:flutter/widgets.dart';
import 'package:flutter/services.dart';
import '../services/message_codecs_utils.dart';
import 'semantics_tester.dart';
void main() {
......@@ -243,17 +244,19 @@ void main() {
});
await tester.pump();
expect(log, <MethodCall>[
const MethodCall('TextInput.setEditingState', const <String, dynamic>{
'text': 'Wobble',
'selectionBase': -1,
'selectionExtent': -1,
'selectionAffinity': 'TextAffinity.downstream',
'selectionIsDirectional': false,
'composingBase': -1,
'composingExtent': -1,
}),
]);
expect(log, hasLength(1));
expect(log.single, isMethodCall(
'TextInput.setEditingState',
arguments: const <String, dynamic>{
'text': 'Wobble',
'selectionBase': -1,
'selectionExtent': -1,
'selectionAffinity': 'TextAffinity.downstream',
'selectionIsDirectional': false,
'composingBase': -1,
'composingExtent': -1,
},
));
});
testWidgets('EditableText identifies as text field (w/ focus) in semantics', (WidgetTester tester) async {
......
......@@ -6,6 +6,8 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/services.dart';
import '../services/message_codecs_utils.dart';
void main() {
testWidgets('toString control test', (WidgetTester tester) async {
final Widget widget = new Title(
......@@ -58,9 +60,10 @@ void main() {
color: const Color(0xFF00FF00),
));
expect(log, equals(<MethodCall>[new MethodCall(
'SystemChrome.setApplicationSwitcherDescription',
<String, dynamic>{'label': '', 'primaryColor': 4278255360},
)]));
expect(log, hasLength(1));
expect(log.single, isMethodCall(
'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