parent
0392a86c89
commit
9d877478ad
7 changed files with 0 additions and 512 deletions
@ -1,3 +0,0 @@ |
|||||||
library flutter_quill_test; |
|
||||||
|
|
||||||
export 'src/test/widget_tester_extension.dart'; |
|
@ -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(); |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
@ -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); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
@ -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')); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
@ -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)); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
Loading…
Reference in new issue