Unverified Commit 5ea13b84 authored by Yegor's avatar Yegor Committed by GitHub

fix KeySet.hashCode; enable multiple web tests (#52861)

fix KeySet.hashCode; enable multiple web tests
parent 3998549d
...@@ -59,13 +59,13 @@ const int kWebShardCount = 8; ...@@ -59,13 +59,13 @@ const int kWebShardCount = 8;
const List<String> kWebTestFileBlacklist = <String>[ const List<String> kWebTestFileBlacklist = <String>[
// This test doesn't compile because it depends on code outside the flutter package. // This test doesn't compile because it depends on code outside the flutter package.
'test/examples/sector_layout_test.dart', 'test/examples/sector_layout_test.dart',
// This test relies on widget tracking capability in the VM.
'test/widgets/widget_inspector_test.dart',
'test/widgets/selectable_text_test.dart', 'test/widgets/selectable_text_test.dart',
'test/widgets/color_filter_test.dart', 'test/widgets/color_filter_test.dart',
'test/widgets/editable_text_cursor_test.dart', 'test/widgets/editable_text_cursor_test.dart',
'test/widgets/raw_keyboard_listener_test.dart',
'test/widgets/editable_text_test.dart', 'test/widgets/editable_text_test.dart',
'test/widgets/widget_inspector_test.dart',
'test/widgets/shortcuts_test.dart',
'test/material/animated_icons_private_test.dart', 'test/material/animated_icons_private_test.dart',
'test/material/text_form_field_test.dart', 'test/material/text_form_field_test.dart',
'test/material/data_table_test.dart', 'test/material/data_table_test.dart',
......
...@@ -79,9 +79,6 @@ class KeySet<T extends KeyboardKey> { ...@@ -79,9 +79,6 @@ class KeySet<T extends KeyboardKey> {
/// Returns an unmodifiable view of the [KeyboardKey]s in this [KeySet]. /// Returns an unmodifiable view of the [KeyboardKey]s in this [KeySet].
Set<T> get keys => UnmodifiableSetView<T>(_keys); Set<T> get keys => UnmodifiableSetView<T>(_keys);
// This needs to be a hash set to be sure that the hashCode accessor returns
// consistent results. LinkedHashSet (the default Set implementation) depends
// upon insertion order, and HashSet does not.
final HashSet<T> _keys; final HashSet<T> _keys;
@override @override
...@@ -93,9 +90,58 @@ class KeySet<T extends KeyboardKey> { ...@@ -93,9 +90,58 @@ class KeySet<T extends KeyboardKey> {
&& setEquals<T>(other._keys, _keys); && setEquals<T>(other._keys, _keys);
} }
// Arrays used to temporarily store hash codes for sorting.
static final List<int> _tempHashStore3 = <int>[0, 0, 0]; // used to sort exactly 3 keys
static final List<int> _tempHashStore4 = <int>[0, 0, 0, 0]; // used to sort exactly 4 keys
// Cached hash code value. Improves [hashCode] performance by 27%-900%,
// depending on key set size and read/write ratio.
int _hashCode;
@override @override
int get hashCode { int get hashCode {
return hashList(_keys); // Return cached hash code if available.
if (_hashCode != null) {
return _hashCode;
}
// Compute order-independent hash and cache it.
final int length = _keys.length;
final Iterator<T> iterator = _keys.iterator;
// There's always at least one key. Just extract it.
iterator.moveNext();
final int h1 = iterator.current.hashCode;
if (length == 1) {
// Don't do anything fancy if there's exactly one key.
return _hashCode = h1;
}
iterator.moveNext();
final int h2 = iterator.current.hashCode;
if (length == 2) {
// No need to sort if there's two keys, just compare them.
return _hashCode = h1 < h2
? hashValues(h1, h2)
: hashValues(h2, h1);
}
// Sort key hash codes and feed to hashList to ensure the aggregate
// hash code does not depend on the key order.
final List<int> sortedHashes = length == 3
? _tempHashStore3
: _tempHashStore4;
sortedHashes[0] = h1;
sortedHashes[1] = h2;
iterator.moveNext();
sortedHashes[2] = iterator.current.hashCode;
if (length == 4) {
iterator.moveNext();
sortedHashes[3] = iterator.current.hashCode;
}
sortedHashes.sort();
return _hashCode = hashList(sortedHashes);
} }
} }
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
@TestOn('!chrome') // needs substantial triage.
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
...@@ -2627,7 +2626,7 @@ void main() { ...@@ -2627,7 +2626,7 @@ void main() {
expect(tester.getTopLeft(find.text('text')).dy, 16.0); expect(tester.getTopLeft(find.text('text')).dy, 16.0);
expect(getBorderBottom(tester), kMinInteractiveDimension); // 40 bumped up to minimum. expect(getBorderBottom(tester), kMinInteractiveDimension); // 40 bumped up to minimum.
expect(getBorderWeight(tester), 1.0); expect(getBorderWeight(tester), 1.0);
}, skip: isBrowser); });
testWidgets('InputDecorator.collapsed', (WidgetTester tester) async { testWidgets('InputDecorator.collapsed', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -2667,7 +2666,7 @@ void main() { ...@@ -2667,7 +2666,7 @@ void main() {
expect(tester.getSize(find.text('hint')).height, 16.0); expect(tester.getSize(find.text('hint')).height, 16.0);
expect(tester.getTopLeft(find.text('hint')).dy, 16.0); expect(tester.getTopLeft(find.text('hint')).dy, 16.0);
expect(getBorderWeight(tester), 0.0); expect(getBorderWeight(tester), 0.0);
}, skip: isBrowser); });
testWidgets('InputDecorator with baseStyle', (WidgetTester tester) async { testWidgets('InputDecorator with baseStyle', (WidgetTester tester) async {
// Setting the baseStyle of the InputDecoration and the style of the input // Setting the baseStyle of the InputDecoration and the style of the input
...@@ -2708,7 +2707,7 @@ void main() { ...@@ -2708,7 +2707,7 @@ void main() {
expect(tester.getTopLeft(find.text('hint')).dy, 24.75); expect(tester.getTopLeft(find.text('hint')).dy, 24.75);
expect(tester.getTopLeft(find.text('label')).dy, 19.0); expect(tester.getTopLeft(find.text('label')).dy, 19.0);
expect(tester.getTopLeft(find.text('text')).dy, 24.75); expect(tester.getTopLeft(find.text('text')).dy, 24.75);
}, skip: isBrowser); });
testWidgets('InputDecorator with empty style overrides', (WidgetTester tester) async { testWidgets('InputDecorator with empty style overrides', (WidgetTester tester) async {
// Same as not specifying any style overrides // Same as not specifying any style overrides
...@@ -2750,7 +2749,7 @@ void main() { ...@@ -2750,7 +2749,7 @@ void main() {
expect(getBorderWeight(tester), 1.0); expect(getBorderWeight(tester), 1.0);
expect(tester.getTopLeft(find.text('helper')), const Offset(12.0, 64.0)); expect(tester.getTopLeft(find.text('helper')), const Offset(12.0, 64.0));
expect(tester.getTopRight(find.text('counter')), const Offset(788.0, 64.0)); expect(tester.getTopRight(find.text('counter')), const Offset(788.0, 64.0));
}, skip: isBrowser); });
testWidgets('InputDecoration outline shape with no border and no floating placeholder', (WidgetTester tester) async { testWidgets('InputDecoration outline shape with no border and no floating placeholder', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -2804,7 +2803,7 @@ void main() { ...@@ -2804,7 +2803,7 @@ void main() {
// The label should not be seen. // The label should not be seen.
expect(getOpacity(tester, 'label'), 0.0); expect(getOpacity(tester, 'label'), 0.0);
}, skip: isBrowser); });
test('InputDecorationTheme copyWith, ==, hashCode basics', () { test('InputDecorationTheme copyWith, ==, hashCode basics', () {
expect(const InputDecorationTheme(), const InputDecorationTheme().copyWith()); expect(const InputDecorationTheme(), const InputDecorationTheme().copyWith());
...@@ -2850,7 +2849,7 @@ void main() { ...@@ -2850,7 +2849,7 @@ void main() {
expect(tester.getBottomLeft(find.text('label')).dy, 36.0); expect(tester.getBottomLeft(find.text('label')).dy, 36.0);
expect(getBorderBottom(tester), 56.0); expect(getBorderBottom(tester), 56.0);
expect(getBorderWeight(tester), 1.0); expect(getBorderWeight(tester), 1.0);
}, skip: isBrowser); });
testWidgets('InputDecorationTheme outline border, dense layout', (WidgetTester tester) async { testWidgets('InputDecorationTheme outline border, dense layout', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
...@@ -3670,7 +3669,7 @@ void main() { ...@@ -3670,7 +3669,7 @@ void main() {
) )
..restore(), ..restore(),
); );
}); }, skip: isBrowser);
testWidgets('OutlineInputBorder radius carries over when lerping', (WidgetTester tester) async { testWidgets('OutlineInputBorder radius carries over when lerping', (WidgetTester tester) async {
// This is a regression test for https://github.com/flutter/flutter/issues/23982 // This is a regression test for https://github.com/flutter/flutter/issues/23982
...@@ -3880,6 +3879,6 @@ void main() { ...@@ -3880,6 +3879,6 @@ void main() {
expect(tester.getTopLeft(find.text('hint')).dy, 28.75); expect(tester.getTopLeft(find.text('hint')).dy, 28.75);
// Ideographic (incorrect) value is 50.299999713897705 // Ideographic (incorrect) value is 50.299999713897705
expect(tester.getBottomLeft(find.text('hint')).dy, 47.75); expect(tester.getBottomLeft(find.text('hint')).dy, isBrowser ? 45.75 : 47.75);
}); });
} }
...@@ -55,6 +55,6 @@ void main() { ...@@ -55,6 +55,6 @@ void main() {
await tester.pump(); await tester.pump();
expect(tester.widget<Title>(find.byType(Title)).title, 'en_US'); expect(tester.widget<Title>(find.byType(Title)).title, 'en_US');
expect(tester.widget<Title>(find.byType(Title)).color, kTitleColor); expect(tester.widget<Title>(find.byType(Title)).color, kTitleColor);
}, skip: isBrowser); });
} }
...@@ -212,7 +212,7 @@ void main() { ...@@ -212,7 +212,7 @@ void main() {
await tester.pumpWidget(_StatefulListView((int i) => i % 3 == 0)); await tester.pumpWidget(_StatefulListView((int i) => i % 3 == 0));
await checkAndScroll('0:true'); await checkAndScroll('0:true');
}, skip: isBrowser); });
testWidgets('ListView can build out of underflow', (WidgetTester tester) async { testWidgets('ListView can build out of underflow', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
......
...@@ -191,5 +191,5 @@ void main() { ...@@ -191,5 +191,5 @@ void main() {
// empty opacity layer is sent. // empty opacity layer is sent.
final OffsetLayer offsetLayer = element.renderObject.debugLayer as OffsetLayer; final OffsetLayer offsetLayer = element.renderObject.debugLayer as OffsetLayer;
await offsetLayer.toImage(const Rect.fromLTRB(0.0, 0.0, 1.0, 1.0)); await offsetLayer.toImage(const Rect.fromLTRB(0.0, 0.0, 1.0, 1.0));
}, skip: isBrowser); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/52856
} }
...@@ -277,7 +277,7 @@ void main() { ...@@ -277,7 +277,7 @@ void main() {
await _testStackChildren(tester, children, expectedErrorCount: 0); await _testStackChildren(tester, children, expectedErrorCount: 0);
expect(find.byType(Material), findsNWidgets(2)); expect(find.byType(Material), findsNWidgets(2));
}, skip: isBrowser); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/52855
// Tests: // Tests:
// //
...@@ -484,7 +484,7 @@ void main() { ...@@ -484,7 +484,7 @@ void main() {
await _testStackChildren(tester, children, expectedErrorCount: 0); await _testStackChildren(tester, children, expectedErrorCount: 0);
expect(find.byType(Material), findsNWidgets(2)); expect(find.byType(Material), findsNWidgets(2));
}, skip: isBrowser); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/52855
// Tests: // Tests:
// //
......
...@@ -119,7 +119,7 @@ void main() { ...@@ -119,7 +119,7 @@ void main() {
await tester.tap(find.byType(Scrollable)); await tester.tap(find.byType(Scrollable));
await tester.pump(const Duration(milliseconds: 50)); await tester.pump(const Duration(milliseconds: 50));
expect(log, equals(<String>['tap 21', 'tap 35'])); expect(log, equals(<String>['tap 21', 'tap 35']));
}, skip: isBrowser); });
testWidgets('fling and wait and tap', (WidgetTester tester) async { testWidgets('fling and wait and tap', (WidgetTester tester) async {
final List<String> log = <String>[]; final List<String> log = <String>[];
...@@ -148,5 +148,5 @@ void main() { ...@@ -148,5 +148,5 @@ void main() {
await tester.tap(find.byType(Scrollable)); await tester.tap(find.byType(Scrollable));
await tester.pump(const Duration(milliseconds: 50)); await tester.pump(const Duration(milliseconds: 50));
expect(log, equals(<String>['tap 21', 'tap 48'])); expect(log, equals(<String>['tap 21', 'tap 48']));
}, skip: isBrowser); });
} }
...@@ -253,7 +253,7 @@ void main() { ...@@ -253,7 +253,7 @@ void main() {
)); ));
semantics.dispose(); semantics.dispose();
}, skip: isBrowser); });
testWidgets('correct scrollProgress for unbound', (WidgetTester tester) async { testWidgets('correct scrollProgress for unbound', (WidgetTester tester) async {
semantics = SemanticsTester(tester); semantics = SemanticsTester(tester);
......
...@@ -63,7 +63,7 @@ void main() { ...@@ -63,7 +63,7 @@ void main() {
)); ));
semantics.dispose(); semantics.dispose();
}, skip: isBrowser); });
testWidgets('SemanticsNode is not removed if out of bounds and merged into something within bounds', (WidgetTester tester) async { testWidgets('SemanticsNode is not removed if out of bounds and merged into something within bounds', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
...@@ -124,5 +124,5 @@ void main() { ...@@ -124,5 +124,5 @@ void main() {
)); ));
semantics.dispose(); semantics.dispose();
}, skip: isBrowser); });
} }
...@@ -540,7 +540,7 @@ void main() { ...@@ -540,7 +540,7 @@ void main() {
expect(semantics, hasSemantics(expectedSemantics, ignoreId: true)); expect(semantics, hasSemantics(expectedSemantics, ignoreId: true));
semantics.dispose(); semantics.dispose();
}, skip: isBrowser); });
testWidgets('Actions can be replaced without triggering semantics update', (WidgetTester tester) async { testWidgets('Actions can be replaced without triggering semantics update', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
......
...@@ -59,7 +59,7 @@ Future<void> main() async { ...@@ -59,7 +59,7 @@ Future<void> main() async {
..rect(color: Colors.black) ..rect(color: Colors.black)
..rect(color: Colors.white), ..rect(color: Colors.white),
); );
}, skip: isBrowser); });
test('ShapeDecoration with BorderDirectional', () { test('ShapeDecoration with BorderDirectional', () {
const ShapeDecoration decoration = ShapeDecoration( const ShapeDecoration decoration = ShapeDecoration(
......
...@@ -132,8 +132,8 @@ void main() { ...@@ -132,8 +132,8 @@ void main() {
final Map<LogicalKeySet, String> map = <LogicalKeySet, String>{set1: 'one'}; final Map<LogicalKeySet, String> map = <LogicalKeySet, String>{set1: 'one'};
expect(set2 == set3, isTrue); expect(set2 == set3, isTrue);
expect(set2 == set4, isTrue); expect(set2 == set4, isTrue);
expect(set2.hashCode == set3.hashCode, isTrue); expect(set2.hashCode, set3.hashCode);
expect(set2.hashCode == set4.hashCode, isTrue); expect(set2.hashCode, set4.hashCode);
expect(map.containsKey(set1), isTrue); expect(map.containsKey(set1), isTrue);
expect(map.containsKey(LogicalKeySet(LogicalKeyboardKey.keyA)), isTrue); expect(map.containsKey(LogicalKeySet(LogicalKeyboardKey.keyA)), isTrue);
expect( expect(
...@@ -146,6 +146,40 @@ void main() { ...@@ -146,6 +146,40 @@ void main() {
})), })),
); );
}); });
test('LogicalKeySet.hashCode is stable', () {
final LogicalKeySet set1 = LogicalKeySet(LogicalKeyboardKey.keyA);
expect(set1.hashCode, set1.hashCode);
final LogicalKeySet set2 = LogicalKeySet(LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyB);
expect(set2.hashCode, set2.hashCode);
final LogicalKeySet set3 = LogicalKeySet(LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyB, LogicalKeyboardKey.keyC);
expect(set3.hashCode, set3.hashCode);
final LogicalKeySet set4 = LogicalKeySet(LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyB, LogicalKeyboardKey.keyC, LogicalKeyboardKey.keyD);
expect(set4.hashCode, set4.hashCode);
});
test('LogicalKeySet.hashCode is order-independent', () {
expect(
LogicalKeySet(LogicalKeyboardKey.keyA).hashCode,
LogicalKeySet(LogicalKeyboardKey.keyA).hashCode,
);
expect(
LogicalKeySet(LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyB).hashCode,
LogicalKeySet(LogicalKeyboardKey.keyB, LogicalKeyboardKey.keyA).hashCode,
);
expect(
LogicalKeySet(LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyB, LogicalKeyboardKey.keyC).hashCode,
LogicalKeySet(LogicalKeyboardKey.keyC, LogicalKeyboardKey.keyB, LogicalKeyboardKey.keyA).hashCode,
);
expect(
LogicalKeySet(LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyB, LogicalKeyboardKey.keyC, LogicalKeyboardKey.keyD).hashCode,
LogicalKeySet(LogicalKeyboardKey.keyD, LogicalKeyboardKey.keyC, LogicalKeyboardKey.keyB, LogicalKeyboardKey.keyA).hashCode,
);
});
test('LogicalKeySet diagnostics work.', () { test('LogicalKeySet diagnostics work.', () {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
......
...@@ -34,7 +34,7 @@ void main() { ...@@ -34,7 +34,7 @@ void main() {
))); )));
semantics.dispose(); semantics.dispose();
}, skip: isBrowser); });
testWidgets('Simple tree is simple - material', (WidgetTester tester) async { testWidgets('Simple tree is simple - material', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
...@@ -78,5 +78,5 @@ void main() { ...@@ -78,5 +78,5 @@ void main() {
))); )));
semantics.dispose(); semantics.dispose();
}, skip: isBrowser); });
} }
...@@ -486,5 +486,5 @@ void main() { ...@@ -486,5 +486,5 @@ void main() {
log.clear(); log.clear();
semantics.dispose(); semantics.dispose();
}, skip: isBrowser); });
} }
...@@ -298,7 +298,7 @@ void main() { ...@@ -298,7 +298,7 @@ void main() {
// initial position of E was 200 + 56 + cSize.height + cSize.height + 500 // initial position of E was 200 + 56 + cSize.height + cSize.height + 500
// we've scrolled that up by 600.0, meaning it's at that minus 600 now: // we've scrolled that up by 600.0, meaning it's at that minus 600 now:
expect(tester.getTopLeft(find.text('E')), Offset(0.0, 200.0 + 56.0 + cSize.height * 2.0 + 500.0 - 600.0)); expect(tester.getTopLeft(find.text('E')), Offset(0.0, 200.0 + 56.0 + cSize.height * 2.0 + 500.0 - 600.0));
}, skip: isBrowser); });
testWidgets('Does not crash when there is less than minExtent remainingPaintExtent', (WidgetTester tester) async { testWidgets('Does not crash when there is less than minExtent remainingPaintExtent', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/21887. // Regression test for https://github.com/flutter/flutter/issues/21887.
......
...@@ -214,5 +214,5 @@ void main() { ...@@ -214,5 +214,5 @@ void main() {
// coordinate space of the screen, the scroll view actually moved far more // coordinate space of the screen, the scroll view actually moved far more
// pixels in its local coordinate system due to the perspective transform. // pixels in its local coordinate system due to the perspective transform.
expect(controller.offset, greaterThan(100)); expect(controller.offset, greaterThan(100));
}, skip: isBrowser); });
} }
...@@ -429,5 +429,5 @@ void main() { ...@@ -429,5 +429,5 @@ void main() {
log.clear(); log.clear();
semantics.dispose(); semantics.dispose();
}, skip: isBrowser); });
} }
...@@ -827,7 +827,7 @@ void main() { ...@@ -827,7 +827,7 @@ void main() {
expect(tester.renderObject<RenderBox>(find.text('X')).size, const Size(100.0, 100.0)); expect(tester.renderObject<RenderBox>(find.text('X')).size, const Size(100.0, 100.0));
expect(tester.renderObject<RenderBox>(find.byType(Baseline)).size, expect(tester.renderObject<RenderBox>(find.byType(Baseline)).size,
within<Size>(from: const Size(100.0, 200.0), distance: 0.001)); within<Size>(from: const Size(100.0, 200.0), distance: 0.001));
}, skip: isBrowser); });
testWidgets('Spacing with slight overflow', (WidgetTester tester) async { testWidgets('Spacing with slight overflow', (WidgetTester tester) async {
await tester.pumpWidget(Wrap( await tester.pumpWidget(Wrap(
......
...@@ -351,9 +351,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase ...@@ -351,9 +351,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
return TestAsyncUtils.guard<void>(() async { return TestAsyncUtils.guard<void>(() async {
assert(inTest); assert(inTest);
final Locale locale = Locale(languageCode, countryCode == '' ? null : countryCode); final Locale locale = Locale(languageCode, countryCode == '' ? null : countryCode);
if (isBrowser) {
return;
}
dispatchLocalesChanged(<Locale>[locale]); dispatchLocalesChanged(<Locale>[locale]);
}); });
} }
......
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