spell_check_suggestions_toolbar_test.dart 5.28 KB
Newer Older
1 2 3 4
// 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.

5
import 'package:flutter/foundation.dart';
6
import 'package:flutter/material.dart';
7
import 'package:flutter/services.dart';
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
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) {
    final List<ContextMenuButtonItem> buttonItems = <ContextMenuButtonItem>[];

    for (final String suggestion in suggestions) {
      buttonItems.add(ContextMenuButtonItem(
        onPressed: () {},
        label: suggestion,
      ));
    }

    final ContextMenuButtonItem deleteButton =
      ContextMenuButtonItem(
        onPressed: () {},
        type: ContextMenuButtonType.delete,
        label: 'DELETE',
    );
    buttonItems.add(deleteButton);

    return buttonItems;
  }

  /// 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;
64
    expect(toolbarY, equals(_kAnchor));
65 66 67
  });

  testWidgets('re-positions toolbar higher below anchor when it does not fit above bottom view padding', (WidgetTester tester) async {
68 69
    // We expect the toolbar to be positioned _kTestToolbarOverlap pixels above the anchor.
    const double expectedToolbarY = _kAnchor - _kTestToolbarOverlap;
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84

    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));
  });
85

86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
  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')));
  });

130 131 132 133 134 135 136 137 138 139 140
  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 {
141 142 143 144 145 146
  _FakeEditableTextState({
    this.suggestions,
  });

  final List<String>? suggestions;

147 148 149 150 151
  @override
  TextEditingValue get currentTextEditingValue => TextEditingValue.empty;

  @override
  SuggestionSpan? findSuggestionSpanAtCursorIndex(int cursorIndex) {
152 153
    return SuggestionSpan(
      const TextRange(
154 155 156
        start: 0,
        end: 0,
      ),
157
      suggestions ?? <String>[],
158 159
    );
  }
160
}