spell_check_suggestions_toolbar_test.dart 5.4 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
import 'package:flutter_test/flutter_test.dart';
9
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
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
// 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'),
    );
  }

51
  testWidgetsWithLeakTracking('positions toolbar below anchor when it fits above bottom view padding', (WidgetTester tester) async {
52 53 54 55 56 57 58 59 60 61 62 63 64
    // 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;
65
    expect(toolbarY, equals(_kAnchor));
66 67
  });

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

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

87
  testWidgetsWithLeakTracking('more than three suggestions throws an error', (WidgetTester tester) async {
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 130
    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')));
  });

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

  final List<String>? suggestions;

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

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