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.
This commit is contained in:
parent
354ab0ce7b
commit
4e2e69797a
@ -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));
|
||||
});
|
||||
}
|
||||
|
68
packages/flutter/test/services/message_codecs_utils.dart
Normal file
68
packages/flutter/test/services/message_codecs_utils.dart
Normal file
@ -0,0 +1,68 @@
|
||||
// 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},
|
||||
));
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user