blob: eafa7f8283120b7f418d435ac5632114034578c8 [file] [log] [blame]
// Copyright 2014 The Flutter 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/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
// Vertical position at which to anchor the toolbar for testing.
const double _kAnchor = 200;
// Amount for toolbar to overlap bottom padding for testing.
const double _kTestToolbarOverlap = 10;
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
/// Builds test button items for each of the suggestions provided.
List<ContextMenuButtonItem> buildSuggestionButtons(List<String> suggestions) {
return <ContextMenuButtonItem>[
for (final String suggestion in suggestions)
ContextMenuButtonItem(onPressed: () {}, label: suggestion),
ContextMenuButtonItem(
onPressed: () {},
type: ContextMenuButtonType.delete,
label: 'DELETE',
),
];
}
/// Finds the container of the [SpellCheckSuggestionsToolbar] so that
/// the position of the toolbar itself may be determined.
Finder findSpellCheckSuggestionsToolbar() {
return find.descendant(
of: find.byType(MaterialApp),
matching: find.byWidgetPredicate(
(Widget w) => '${w.runtimeType}' == '_SpellCheckSuggestionsToolbarContainer'),
);
}
testWidgets('positions toolbar below anchor when it fits above bottom view padding', (WidgetTester tester) async {
// We expect the toolbar to be positioned right below the anchor with padding accounted for.
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SpellCheckSuggestionsToolbar(
anchor: const Offset(0.0, _kAnchor),
buttonItems: buildSuggestionButtons(<String>['hello', 'yellow', 'yell']),
),
),
),
);
final double toolbarY = tester.getTopLeft(findSpellCheckSuggestionsToolbar()).dy;
expect(toolbarY, equals(_kAnchor));
});
testWidgets('re-positions toolbar higher below anchor when it does not fit above bottom view padding', (WidgetTester tester) async {
// We expect the toolbar to be positioned _kTestToolbarOverlap pixels above the anchor.
const double expectedToolbarY = _kAnchor - _kTestToolbarOverlap;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SpellCheckSuggestionsToolbar(
anchor: const Offset(0.0, _kAnchor - _kTestToolbarOverlap),
buttonItems: buildSuggestionButtons(<String>['hello', 'yellow', 'yell']),
),
),
),
);
final double toolbarY = tester.getTopLeft(findSpellCheckSuggestionsToolbar()).dy;
expect(toolbarY, equals(expectedToolbarY));
});
testWidgets('more than three suggestions throws an error', (WidgetTester tester) async {
Future<void> pumpToolbar(List<String> suggestions) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SpellCheckSuggestionsToolbar(
anchor: const Offset(0.0, _kAnchor - _kTestToolbarOverlap),
buttonItems: buildSuggestionButtons(suggestions),
),
),
),
);
}
await pumpToolbar(<String>['hello', 'yellow', 'yell']);
expect(() async {
await pumpToolbar(<String>['hello', 'yellow', 'yell', 'yeller']);
}, throwsAssertionError);
},
skip: kIsWeb, // [intended]
);
test('buildSuggestionButtons only considers the first three suggestions', () {
final _FakeEditableTextState editableTextState = _FakeEditableTextState(
suggestions: <String>[
'hello',
'yellow',
'yell',
'yeller',
],
);
final List<ContextMenuButtonItem>? buttonItems =
SpellCheckSuggestionsToolbar.buildButtonItems(editableTextState);
expect(buttonItems, isNotNull);
final Iterable<String?> labels = buttonItems!.map((ContextMenuButtonItem buttonItem) {
return buttonItem.label;
});
expect(labels, hasLength(4));
expect(labels, contains('hello'));
expect(labels, contains('yellow'));
expect(labels, contains('yell'));
expect(labels, contains(null)); // For the delete button.
expect(labels, isNot(contains('yeller')));
});
test('buildButtonItems builds only a delete button when no suggestions', () {
final _FakeEditableTextState editableTextState = _FakeEditableTextState();
final List<ContextMenuButtonItem>? buttonItems =
SpellCheckSuggestionsToolbar.buildButtonItems(editableTextState);
expect(buttonItems, hasLength(1));
expect(buttonItems!.first.type, ContextMenuButtonType.delete);
});
}
class _FakeEditableTextState extends EditableTextState {
_FakeEditableTextState({
this.suggestions,
});
final List<String>? suggestions;
@override
TextEditingValue get currentTextEditingValue => TextEditingValue.empty;
@override
SuggestionSpan? findSuggestionSpanAtCursorIndex(int cursorIndex) {
return SuggestionSpan(
const TextRange(
start: 0,
end: 0,
),
suggestions ?? <String>[],
);
}
}