diff --git a/.gitignore b/.gitignore index 6b87759f..08c3c879 100644 --- a/.gitignore +++ b/.gitignore @@ -29,7 +29,6 @@ .pub-cache/ .pub/ build/ -coverage/ # Android related **/android/**/gradle-wrapper.jar diff --git a/README.md b/README.md index 9fe48b01..d31e9a58 100644 --- a/README.md +++ b/README.md @@ -391,22 +391,6 @@ tables, and mentions. Conversion can be performed in vanilla Dart (i.e., server- It is a complete Dart part of the popular and mature [quill-delta-to-html](https://www.npmjs.com/package/quill-delta-to-html) Typescript/Javascript package. -## Testing - -To aid in testing applications using the editor an extension to the flutter `WidgetTester` is provided which includes methods to simplify interacting with the editor in test cases. - -Import the test utilities in your test file: - -```dart -import 'package:flutter_quill/flutter_quill_test.dart'; -``` - -and then enter text using `quillEnterText`: - -```dart -await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); -``` - ## Sponsors <a href="https://bulletjournal.us/home/index.html"> diff --git a/lib/flutter_quill_test.dart b/lib/flutter_quill_test.dart deleted file mode 100644 index 988e4e82..00000000 --- a/lib/flutter_quill_test.dart +++ /dev/null @@ -1,3 +0,0 @@ -library flutter_quill_test; - -export 'src/test/widget_tester_extension.dart'; diff --git a/lib/src/test/widget_tester_extension.dart b/lib/src/test/widget_tester_extension.dart deleted file mode 100644 index 21bb75ab..00000000 --- a/lib/src/test/widget_tester_extension.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import '../widgets/editor.dart'; -import '../widgets/raw_editor.dart'; - -/// Extends -extension QuillEnterText on WidgetTester { - /// Give the QuillEditor widget specified by [finder] the focus. - Future<void> quillGiveFocus(Finder finder) { - return TestAsyncUtils.guard(() async { - final editor = state<QuillEditorState>( - find.descendant( - of: finder, - matching: - find.byType(QuillEditor, skipOffstage: finder.skipOffstage), - matchRoot: true), - ); - editor.widget.focusNode.requestFocus(); - await pump(); - expect(editor.widget.focusNode.hasFocus, isTrue); - }); - } - - /// Give the QuillEditor widget specified by [finder] the focus and update its - /// editing value with [text], as if it had been provided by the onscreen - /// keyboard. - /// - /// The widget specified by [finder] must be a [QuillEditor] or have a - /// [QuillEditor] descendant. For example `find.byType(QuillEditor)`. - Future<void> quillEnterText(Finder finder, String text) async { - return TestAsyncUtils.guard(() async { - await quillGiveFocus(finder); - await quillUpdateEditingValue(finder, text); - await idle(); - }); - } - - /// Update the text editing value of the QuillEditor widget specified by - /// [finder] with [text], as if it had been provided by the onscreen keyboard. - /// - /// The widget specified by [finder] must already have focus and be a - /// [QuillEditor] or have a [QuillEditor] descendant. For example - /// `find.byType(QuillEditor)`. - Future<void> quillUpdateEditingValue(Finder finder, String text) async { - return TestAsyncUtils.guard(() async { - final editor = state<RawEditorState>( - find.descendant( - of: finder, - matching: find.byType(RawEditor, skipOffstage: finder.skipOffstage), - matchRoot: true), - ); - testTextInput.updateEditingValue(TextEditingValue( - text: text, - selection: TextSelection.collapsed( - offset: editor.textEditingValue.text.length))); - await idle(); - }); - } -} diff --git a/test/bug_fix_test.dart b/test/bug_fix_test.dart deleted file mode 100644 index ecbcad2b..00000000 --- a/test/bug_fix_test.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_quill/flutter_quill.dart'; -import 'package:flutter_quill/flutter_quill_test.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('Bug fix', () { - group('1189 - The provided text position is not in the current node', () { - late QuillController controller; - late QuillEditor editor; - - setUp(() { - controller = QuillController.basic(); - editor = QuillEditor.basic(controller: controller, readOnly: false); - }); - - tearDown(() { - controller.dispose(); - }); - - testWidgets('Refocus editor after controller clears document', - (tester) async { - await tester.pumpWidget(MaterialApp(home: Column(children: [editor]))); - await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); - - editor.focusNode.unfocus(); - await tester.pump(); - controller.clear(); - editor.focusNode.requestFocus(); - await tester.pump(); - expect(tester.takeException(), isNull); - }); - - testWidgets('Refocus editor after removing block attribute', - (tester) async { - await tester.pumpWidget(MaterialApp(home: Column(children: [editor]))); - await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); - - controller.formatSelection(Attribute.ul); - editor.focusNode.unfocus(); - await tester.pump(); - controller.formatSelection(const ListAttribute(null)); - editor.focusNode.requestFocus(); - await tester.pump(); - expect(tester.takeException(), isNull); - }); - - testWidgets('Tap checkbox in unfocused editor', (tester) async { - await tester.pumpWidget(MaterialApp(home: Column(children: [editor]))); - await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); - - controller.formatSelection(Attribute.unchecked); - editor.focusNode.unfocus(); - await tester.pump(); - await tester.tap(find.byType(CheckboxPoint)); - expect(tester.takeException(), isNull); - }); - }); - }); -} diff --git a/test/widgets/controller_test.dart b/test/widgets/controller_test.dart deleted file mode 100644 index 047dfcae..00000000 --- a/test/widgets/controller_test.dart +++ /dev/null @@ -1,290 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_quill/flutter_quill.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - const testDocumentContents = 'data'; - late QuillController controller; - - setUp(() { - controller = QuillController.basic() - ..compose(Delta()..insert(testDocumentContents), - const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL); - }); - - group('controller', () { - test('set document', () { - const replacementContents = 'replacement\n'; - final newDocument = - Document.fromDelta(Delta()..insert(replacementContents)); - var listenerCalled = false; - controller - ..addListener(() { - listenerCalled = true; - }) - ..document = newDocument; - expect(listenerCalled, isTrue); - expect(controller.document.toPlainText(), replacementContents); - }); - - test('getSelectionStyle', () { - controller - ..formatText(0, 5, Attribute.h1) - ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 4), - ChangeSource.LOCAL); - - expect(controller.getSelectionStyle().values, [Attribute.h1]); - }); - - test('indentSelection with single line document', () { - var listenerCalled = false; - // With selection range - controller - ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 4), - ChangeSource.LOCAL) - ..addListener(() { - listenerCalled = true; - }) - ..indentSelection(true); - expect(listenerCalled, isTrue); - expect(controller.getSelectionStyle().values, [Attribute.indentL1]); - controller.indentSelection(true); - expect(controller.getSelectionStyle().values, [Attribute.indentL2]); - controller.indentSelection(false); - expect(controller.getSelectionStyle().values, [Attribute.indentL1]); - controller.indentSelection(false); - expect(controller.getSelectionStyle().values, []); - - // With collapsed selection - controller - ..updateSelection( - const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) - ..indentSelection(true); - expect(controller.getSelectionStyle().values, [Attribute.indentL1]); - controller - ..updateSelection( - const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) - ..indentSelection(true); - expect(controller.getSelectionStyle().values, [Attribute.indentL2]); - controller.indentSelection(false); - expect(controller.getSelectionStyle().values, [Attribute.indentL1]); - controller.indentSelection(false); - expect(controller.getSelectionStyle().values, []); - }); - - test('indentSelection with multiline document', () { - controller - ..compose(Delta()..insert('line1\nline2\nline3\n'), - const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) - // Indent first line - ..updateSelection( - const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL) - ..indentSelection(true); - expect(controller.getSelectionStyle().values, [Attribute.indentL1]); - - // Indent first two lines - controller - ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 11), - ChangeSource.LOCAL) - ..indentSelection(true); - - // Should have both L1 and L2 indent attributes in selection. - expect(controller.getAllSelectionStyles(), - contains(Style().put(Attribute.indentL1).put(Attribute.indentL2))); - - // Remaining lines should have no attributes. - controller.updateSelection( - TextSelection( - baseOffset: 12, - extentOffset: controller.document.toPlainText().length - 1), - ChangeSource.LOCAL); - expect(controller.getAllSelectionStyles(), everyElement(Style())); - }); - - test('getAllIndividualSelectionStyles', () { - controller.formatText(0, 2, Attribute.bold); - final result = controller.getAllIndividualSelectionStyles(); - expect(result.length, 1); - expect(result[0].offset, 0); - expect(result[0].value, Style().put(Attribute.bold)); - }); - - test('getPlainText', () { - controller.updateSelection( - const TextSelection(baseOffset: 0, extentOffset: 4), - ChangeSource.LOCAL); - - expect(controller.getPlainText(), testDocumentContents); - }); - - test('getAllSelectionStyles', () { - controller.formatText(0, 2, Attribute.bold); - expect(controller.getAllSelectionStyles(), - contains(Style().put(Attribute.bold))); - }); - - test('undo', () { - var listenerCalled = false; - controller.updateSelection( - const TextSelection.collapsed(offset: 4), ChangeSource.LOCAL); - - expect(controller.document.toDelta(), Delta()..insert('data\n')); - controller - ..addListener(() { - listenerCalled = true; - }) - ..undo(); - expect(listenerCalled, isTrue); - expect(controller.document.toDelta(), Delta()..insert('\n')); - }); - - test('redo', () { - var listenerCalled = false; - controller.updateSelection( - const TextSelection.collapsed(offset: 4), ChangeSource.LOCAL); - - expect(controller.document.toDelta(), Delta()..insert('data\n')); - controller.undo(); - expect(controller.document.toDelta(), Delta()..insert('\n')); - controller - ..addListener(() { - listenerCalled = true; - }) - ..redo(); - expect(listenerCalled, isTrue); - expect(controller.document.toDelta(), Delta()..insert('data\n')); - }); - test('clear', () { - var listenerCalled = false; - controller - ..addListener(() { - listenerCalled = true; - }) - ..clear(); - - expect(listenerCalled, isTrue); - expect(controller.document.toDelta(), Delta()..insert('\n')); - }); - - test('replaceText', () { - var listenerCalled = false; - controller - ..addListener(() { - listenerCalled = true; - }) - ..replaceText(1, 2, '11', const TextSelection.collapsed(offset: 0)); - - expect(listenerCalled, isTrue); - expect(controller.document.toDelta(), Delta()..insert('d11a\n')); - }); - - test('formatTextStyle', () { - var listenerCalled = false; - final style = Style().put(Attribute.bold).put(Attribute.italic); - controller - ..addListener(() { - listenerCalled = true; - }) - ..formatTextStyle(0, 2, style); - expect(listenerCalled, isTrue); - expect(controller.document.collectAllStyles(0, 2), contains(style)); - expect(controller.document.collectAllStyles(2, 4), everyElement(Style())); - }); - - test('formatText', () { - var listenerCalled = false; - controller - ..addListener(() { - listenerCalled = true; - }) - ..formatText(0, 2, Attribute.bold); - expect(listenerCalled, isTrue); - expect(controller.document.collectAllStyles(0, 2), - contains(Style().put(Attribute.bold))); - expect(controller.document.collectAllStyles(2, 4), everyElement(Style())); - }); - - test('formatSelection', () { - var listenerCalled = false; - controller - ..updateSelection(const TextSelection(baseOffset: 0, extentOffset: 2), - ChangeSource.LOCAL) - ..addListener(() { - listenerCalled = true; - }) - ..formatSelection(Attribute.bold); - expect(listenerCalled, isTrue); - expect(controller.document.collectAllStyles(0, 2), - contains(Style().put(Attribute.bold))); - expect(controller.document.collectAllStyles(2, 4), everyElement(Style())); - }); - - test('moveCursorToStart', () { - var listenerCalled = false; - controller - ..updateSelection( - const TextSelection.collapsed(offset: 4), ChangeSource.LOCAL) - ..addListener(() { - listenerCalled = true; - }); - expect(controller.selection, const TextSelection.collapsed(offset: 4)); - - controller.moveCursorToStart(); - expect(listenerCalled, isTrue); - expect(controller.selection, const TextSelection.collapsed(offset: 0)); - }); - - test('moveCursorToPosition', () { - var listenerCalled = false; - controller.addListener(() { - listenerCalled = true; - }); - expect(controller.selection, const TextSelection.collapsed(offset: 0)); - - controller.moveCursorToPosition(2); - expect(listenerCalled, isTrue); - expect(controller.selection, const TextSelection.collapsed(offset: 2)); - }); - - test('moveCursorToEnd', () { - var listenerCalled = false; - controller.addListener(() { - listenerCalled = true; - }); - expect(controller.selection, const TextSelection.collapsed(offset: 0)); - - controller.moveCursorToEnd(); - expect(listenerCalled, isTrue); - expect(controller.selection, - TextSelection.collapsed(offset: controller.document.length - 1)); - }); - - test('updateSelection', () { - var listenerCalled = false; - const selection = TextSelection.collapsed(offset: 0); - controller - ..addListener(() { - listenerCalled = true; - }) - ..updateSelection(selection, ChangeSource.LOCAL); - - expect(listenerCalled, isTrue); - expect(controller.selection, selection); - }); - - test('compose', () { - var listenerCalled = false; - final originalContents = controller.document.toPlainText(); - controller - ..addListener(() { - listenerCalled = true; - }) - ..compose(Delta()..insert('test '), - const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL); - - expect(listenerCalled, isTrue); - expect(controller.document.toDelta(), - Delta()..insert('test $originalContents')); - }); - }); -} diff --git a/test/widgets/editor_test.dart b/test/widgets/editor_test.dart deleted file mode 100644 index 3fd425fc..00000000 --- a/test/widgets/editor_test.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'dart:convert' show jsonDecode; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_quill/flutter_quill.dart'; -import 'package:flutter_quill/flutter_quill_test.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - late QuillController controller; - - setUp(() { - controller = QuillController.basic(); - }); - - tearDown(() { - controller.dispose(); - }); - - group('QuillEditor', () { - testWidgets('Keyboard entered text is stored in document', (tester) async { - await tester.pumpWidget( - MaterialApp( - home: QuillEditor.basic(controller: controller, readOnly: false), - ), - ); - await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); - - expect(controller.document.toPlainText(), 'test\n'); - }); - - testWidgets('insertContent is handled correctly', (tester) async { - String? latestUri; - await tester.pumpWidget( - MaterialApp( - home: QuillEditor( - controller: controller, - focusNode: FocusNode(), - scrollController: ScrollController(), - scrollable: true, - padding: const EdgeInsets.all(0), - autoFocus: true, - readOnly: false, - expands: true, - contentInsertionConfiguration: ContentInsertionConfiguration( - onContentInserted: (content) { - latestUri = content.uri; - }, - allowedMimeTypes: const <String>['image/gif'], - ), - ), - ), - ); - await tester.tap(find.byType(QuillEditor)); - await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); - await tester.idle(); - - const uri = - 'content://com.google.android.inputmethod.latin.fileprovider/test.gif'; - final messageBytes = - const JSONMessageCodec().encodeMessage(<String, dynamic>{ - 'args': <dynamic>[ - -1, - 'TextInputAction.commitContent', - jsonDecode( - '{"mimeType": "image/gif", "data": [0,1,0,1,0,1,0,0,0], "uri": "$uri"}'), - ], - 'method': 'TextInputClient.performAction', - }); - - Object? error; - try { - await tester.binding.defaultBinaryMessenger - .handlePlatformMessage('flutter/textinput', messageBytes, (_) {}); - } catch (e) { - error = e; - } - expect(error, isNull); - expect(latestUri, equals(uri)); - }); - }); -}