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;
const List<String> kWebTestFileBlacklist = <String>[
// This test doesn't compile because it depends on code outside the flutter package.
'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/color_filter_test.dart',
'test/widgets/editable_text_cursor_test.dart',
'test/widgets/raw_keyboard_listener_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/text_form_field_test.dart',
'test/material/data_table_test.dart',
......
......@@ -79,9 +79,6 @@ class KeySet<T extends KeyboardKey> {
/// Returns an unmodifiable view of the [KeyboardKey]s in this [KeySet].
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;
@override
......@@ -93,9 +90,58 @@ class KeySet<T extends KeyboardKey> {
&& 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
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 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@TestOn('!chrome') // needs substantial triage.
import 'dart:async';
import 'package:flutter/material.dart';
......@@ -2627,7 +2626,7 @@ void main() {
expect(tester.getTopLeft(find.text('text')).dy, 16.0);
expect(getBorderBottom(tester), kMinInteractiveDimension); // 40 bumped up to minimum.
expect(getBorderWeight(tester), 1.0);
}, skip: isBrowser);
});
testWidgets('InputDecorator.collapsed', (WidgetTester tester) async {
await tester.pumpWidget(
......@@ -2667,7 +2666,7 @@ void main() {
expect(tester.getSize(find.text('hint')).height, 16.0);
expect(tester.getTopLeft(find.text('hint')).dy, 16.0);
expect(getBorderWeight(tester), 0.0);
}, skip: isBrowser);
});
testWidgets('InputDecorator with baseStyle', (WidgetTester tester) async {
// Setting the baseStyle of the InputDecoration and the style of the input
......@@ -2708,7 +2707,7 @@ void main() {
expect(tester.getTopLeft(find.text('hint')).dy, 24.75);
expect(tester.getTopLeft(find.text('label')).dy, 19.0);
expect(tester.getTopLeft(find.text('text')).dy, 24.75);
}, skip: isBrowser);
});
testWidgets('InputDecorator with empty style overrides', (WidgetTester tester) async {
// Same as not specifying any style overrides
......@@ -2750,7 +2749,7 @@ void main() {
expect(getBorderWeight(tester), 1.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));
}, skip: isBrowser);
});
testWidgets('InputDecoration outline shape with no border and no floating placeholder', (WidgetTester tester) async {
await tester.pumpWidget(
......@@ -2804,7 +2803,7 @@ void main() {
// The label should not be seen.
expect(getOpacity(tester, 'label'), 0.0);
}, skip: isBrowser);
});
test('InputDecorationTheme copyWith, ==, hashCode basics', () {
expect(const InputDecorationTheme(), const InputDecorationTheme().copyWith());
......@@ -2850,7 +2849,7 @@ void main() {
expect(tester.getBottomLeft(find.text('label')).dy, 36.0);
expect(getBorderBottom(tester), 56.0);
expect(getBorderWeight(tester), 1.0);
}, skip: isBrowser);
});
testWidgets('InputDecorationTheme outline border, dense layout', (WidgetTester tester) async {
await tester.pumpWidget(
......@@ -3670,7 +3669,7 @@ void main() {
)
..restore(),
);
});
}, skip: isBrowser);
testWidgets('OutlineInputBorder radius carries over when lerping', (WidgetTester tester) async {
// This is a regression test for https://github.com/flutter/flutter/issues/23982
......@@ -3880,6 +3879,6 @@ void main() {
expect(tester.getTopLeft(find.text('hint')).dy, 28.75);
// 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() {
await tester.pump();
expect(tester.widget<Title>(find.byType(Title)).title, 'en_US');
expect(tester.widget<Title>(find.byType(Title)).color, kTitleColor);
}, skip: isBrowser);
});
}
......@@ -212,7 +212,7 @@ void main() {
await tester.pumpWidget(_StatefulListView((int i) => i % 3 == 0));
await checkAndScroll('0:true');
}, skip: isBrowser);
});
testWidgets('ListView can build out of underflow', (WidgetTester tester) async {
await tester.pumpWidget(
......
......@@ -191,5 +191,5 @@ void main() {
// empty opacity layer is sent.
final OffsetLayer offsetLayer = element.renderObject.debugLayer as OffsetLayer;
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() {
await _testStackChildren(tester, children, expectedErrorCount: 0);
expect(find.byType(Material), findsNWidgets(2));
}, skip: isBrowser);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/52855
// Tests:
//
......@@ -484,7 +484,7 @@ void main() {
await _testStackChildren(tester, children, expectedErrorCount: 0);
expect(find.byType(Material), findsNWidgets(2));
}, skip: isBrowser);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/52855
// Tests:
//
......
......@@ -119,7 +119,7 @@ void main() {
await tester.tap(find.byType(Scrollable));
await tester.pump(const Duration(milliseconds: 50));
expect(log, equals(<String>['tap 21', 'tap 35']));
}, skip: isBrowser);
});
testWidgets('fling and wait and tap', (WidgetTester tester) async {
final List<String> log = <String>[];
......@@ -148,5 +148,5 @@ void main() {
await tester.tap(find.byType(Scrollable));
await tester.pump(const Duration(milliseconds: 50));
expect(log, equals(<String>['tap 21', 'tap 48']));
}, skip: isBrowser);
});
}
......@@ -253,7 +253,7 @@ void main() {
));
semantics.dispose();
}, skip: isBrowser);
});
testWidgets('correct scrollProgress for unbound', (WidgetTester tester) async {
semantics = SemanticsTester(tester);
......
......@@ -63,7 +63,7 @@ void main() {
));
semantics.dispose();
}, skip: isBrowser);
});
testWidgets('SemanticsNode is not removed if out of bounds and merged into something within bounds', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
......@@ -124,5 +124,5 @@ void main() {
));
semantics.dispose();
}, skip: isBrowser);
});
}
......@@ -540,7 +540,7 @@ void main() {
expect(semantics, hasSemantics(expectedSemantics, ignoreId: true));
semantics.dispose();
}, skip: isBrowser);
});
testWidgets('Actions can be replaced without triggering semantics update', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
......
......@@ -59,7 +59,7 @@ Future<void> main() async {
..rect(color: Colors.black)
..rect(color: Colors.white),
);
}, skip: isBrowser);
});
test('ShapeDecoration with BorderDirectional', () {
const ShapeDecoration decoration = ShapeDecoration(
......
......@@ -132,8 +132,8 @@ void main() {
final Map<LogicalKeySet, String> map = <LogicalKeySet, String>{set1: 'one'};
expect(set2 == set3, isTrue);
expect(set2 == set4, isTrue);
expect(set2.hashCode == set3.hashCode, isTrue);
expect(set2.hashCode == set4.hashCode, isTrue);
expect(set2.hashCode, set3.hashCode);
expect(set2.hashCode, set4.hashCode);
expect(map.containsKey(set1), isTrue);
expect(map.containsKey(LogicalKeySet(LogicalKeyboardKey.keyA)), isTrue);
expect(
......@@ -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.', () {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
......
......@@ -34,7 +34,7 @@ void main() {
)));
semantics.dispose();
}, skip: isBrowser);
});
testWidgets('Simple tree is simple - material', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
......@@ -78,5 +78,5 @@ void main() {
)));
semantics.dispose();
}, skip: isBrowser);
});
}
......@@ -486,5 +486,5 @@ void main() {
log.clear();
semantics.dispose();
}, skip: isBrowser);
});
}
......@@ -298,7 +298,7 @@ void main() {
// 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:
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 {
// Regression test for https://github.com/flutter/flutter/issues/21887.
......
......@@ -214,5 +214,5 @@ void main() {
// coordinate space of the screen, the scroll view actually moved far more
// pixels in its local coordinate system due to the perspective transform.
expect(controller.offset, greaterThan(100));
}, skip: isBrowser);
});
}
......@@ -429,5 +429,5 @@ void main() {
log.clear();
semantics.dispose();
}, skip: isBrowser);
});
}
......@@ -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.byType(Baseline)).size,
within<Size>(from: const Size(100.0, 200.0), distance: 0.001));
}, skip: isBrowser);
});
testWidgets('Spacing with slight overflow', (WidgetTester tester) async {
await tester.pumpWidget(Wrap(
......
......@@ -351,9 +351,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
return TestAsyncUtils.guard<void>(() async {
assert(inTest);
final Locale locale = Locale(languageCode, countryCode == '' ? null : countryCode);
if (isBrowser) {
return;
}
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