forked from firka/flutter
fix KeySet.hashCode; enable multiple web tests (#52861)
fix KeySet.hashCode; enable multiple web tests
This commit is contained in:
@@ -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]);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user