refactor: migrate tests (#438)
* refactor: separate remove line command * test: delete sentence * chore: export delete left line command * refactor: move from internal_key_handler to new * refactor: move from command to new
This commit is contained in:
parent
2924c167e2
commit
ad55171c5b
25 changed files with 627 additions and 649 deletions
|
|
@ -14,35 +14,6 @@ final CommandShortcutEvent backspaceCommand = CommandShortcutEvent(
|
|||
handler: _backspaceCommandHandler,
|
||||
);
|
||||
|
||||
final CommandShortcutEvent deleteLeftSentenceCommand = CommandShortcutEvent(
|
||||
key: 'delete the left word',
|
||||
command: 'ctrl+alt+backspace',
|
||||
macOSCommand: 'cmd+backspace',
|
||||
handler: _deleteLeftSentenceCommandHandler,
|
||||
);
|
||||
|
||||
CommandShortcutEventHandler _deleteLeftSentenceCommandHandler = (editorState) {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null || !selection.isCollapsed) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
final node = editorState.getNodeAtPath(selection.end.path);
|
||||
final delta = node?.delta;
|
||||
if (node == null || delta == null) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
final transaction = editorState.transaction;
|
||||
transaction.deleteText(
|
||||
node,
|
||||
0,
|
||||
selection.endIndex,
|
||||
);
|
||||
editorState.apply(transaction);
|
||||
return KeyEventResult.handled;
|
||||
};
|
||||
|
||||
CommandShortcutEventHandler _backspaceCommandHandler = (editorState) {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export 'page_down_command.dart';
|
|||
export 'page_up_command.dart';
|
||||
export 'paste_command.dart';
|
||||
export 'remove_word_command.dart';
|
||||
export 'remove_line_command.dart';
|
||||
export 'select_all_command.dart';
|
||||
export 'show_link_menu_command.dart';
|
||||
export 'toggle_colors_command.dart';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Remove Line to the Left.
|
||||
///
|
||||
/// - support
|
||||
/// - desktop
|
||||
/// - web
|
||||
/// - mobile
|
||||
///
|
||||
|
||||
final CommandShortcutEvent deleteLeftSentenceCommand = CommandShortcutEvent(
|
||||
key: 'delete the left line',
|
||||
command: 'ctrl+alt+backspace',
|
||||
macOSCommand: 'cmd+backspace',
|
||||
handler: _deleteLeftSentenceCommandHandler,
|
||||
);
|
||||
|
||||
CommandShortcutEventHandler _deleteLeftSentenceCommandHandler = (editorState) {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null || !selection.isCollapsed) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
final node = editorState.getNodeAtPath(selection.end.path);
|
||||
final delta = node?.delta;
|
||||
if (node == null || delta == null) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
final transaction = editorState.transaction;
|
||||
transaction.deleteText(
|
||||
node,
|
||||
0,
|
||||
selection.endIndex,
|
||||
);
|
||||
editorState.apply(transaction);
|
||||
return KeyEventResult.handled;
|
||||
};
|
||||
|
|
@ -1,270 +0,0 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../infra/clipboard_test.dart';
|
||||
import '../../new/infra/testable_editor.dart';
|
||||
|
||||
void main() async {
|
||||
late MockClipboard mockClipboard;
|
||||
|
||||
setUp(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
mockClipboard = const MockClipboard(html: null, text: null);
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, (message) async {
|
||||
switch (message.method) {
|
||||
case "Clipboard.getData":
|
||||
return mockClipboard.getData;
|
||||
case "Clipboard.setData":
|
||||
final args = message.arguments as Map<String, dynamic>;
|
||||
mockClipboard = mockClipboard.copyWith(
|
||||
text: args['text'],
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
});
|
||||
|
||||
group('paste_command_test.dart', () {
|
||||
testWidgets(
|
||||
'Copy link',
|
||||
(tester) async {
|
||||
final editor = tester.editor..addParagraph(initialText: '');
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.collapsed(Position(path: [0], offset: 0)),
|
||||
);
|
||||
|
||||
const link = 'https://appflowy.io/';
|
||||
AppFlowyClipboard.mockSetData(
|
||||
const AppFlowyClipboardData(text: link),
|
||||
);
|
||||
|
||||
pasteCommand.execute(editor.editorState);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final delta = editor.nodeAtPath([0])!.delta!;
|
||||
expect(delta.toPlainText(), link);
|
||||
expect(
|
||||
delta.everyAttributes(
|
||||
(element) => element[AppFlowyRichTextKeys.href] == link,
|
||||
),
|
||||
true,
|
||||
);
|
||||
|
||||
AppFlowyClipboard.mockSetData(null);
|
||||
await editor.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Presses Command + A in small document and copy text and paste text',
|
||||
(tester) async {
|
||||
await _testHandleCopyPaste(tester, Document.fromJson(paragraphData));
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Presses Command + A in small document and copy text and paste text multiple times',
|
||||
(tester) async {
|
||||
await _testHandleCopyMultiplePaste(
|
||||
tester,
|
||||
Document.fromJson(paragraphData),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _testHandleCopyMultiplePaste(
|
||||
WidgetTester tester,
|
||||
Document document,
|
||||
) async {
|
||||
final editor = tester.editor..initializeWithDocument(document);
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(Selection.collapsed(Position(path: [0])));
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyA,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
handleCopy(editor.editorState);
|
||||
deleteSelectedContent(editor.editorState);
|
||||
|
||||
pasteHTML(
|
||||
editor.editorState,
|
||||
documentToHTML(Document.fromJson(paragraphData)),
|
||||
);
|
||||
expect(
|
||||
editor.editorState.document.toJson(),
|
||||
paragraphData,
|
||||
);
|
||||
await editor.updateSelection(Selection.single(path: [0], startOffset: 10));
|
||||
pasteHTML(
|
||||
editor.editorState,
|
||||
documentToHTML(Document.fromJson(paragraphData)),
|
||||
);
|
||||
expect(
|
||||
editor.document.toJson(),
|
||||
secondParagraph,
|
||||
);
|
||||
pasteHTML(
|
||||
editor.editorState,
|
||||
documentToHTML(Document.fromJson(paragraphData)),
|
||||
);
|
||||
expect(
|
||||
editor.document.toJson(),
|
||||
thirdParagraph,
|
||||
);
|
||||
await editor.dispose();
|
||||
}
|
||||
|
||||
Future<void> _testHandleCopyPaste(
|
||||
WidgetTester tester,
|
||||
Document document,
|
||||
) async {
|
||||
final editor = tester.editor..initializeWithDocument(document);
|
||||
await editor.startTesting(platform: TargetPlatform.windows);
|
||||
await editor.updateSelection(Selection.collapsed(Position(path: [0])));
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyA,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
handleCopy(editor.editorState);
|
||||
deleteSelectedContent(editor.editorState);
|
||||
await editor.updateSelection(Selection.collapsed(Position(path: [0])));
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyP,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
final clipBoardData = await AppFlowyClipboard.getData();
|
||||
handlePastePlainText(editor.editorState, clipBoardData.text!);
|
||||
expect(editor.document.toJson(), plainTextJson);
|
||||
|
||||
await editor.dispose();
|
||||
}
|
||||
|
||||
const paragraphData = {
|
||||
"document": {
|
||||
"type": "page",
|
||||
"children": [
|
||||
{
|
||||
'type': 'paragraph',
|
||||
'data': {
|
||||
'delta': [
|
||||
{'insert': 'AppFlowy Editor is a '},
|
||||
{
|
||||
'insert': 'highly customizable',
|
||||
'attributes': {'bold': true}
|
||||
},
|
||||
{'insert': ' '},
|
||||
{
|
||||
'insert': 'rich-text editor',
|
||||
'attributes': {'italic': true}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
const secondParagraph = {
|
||||
"document": {
|
||||
"type": "page",
|
||||
"children": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{"insert": "AppFlowy Editor is a "},
|
||||
{
|
||||
"insert": "highly customizable",
|
||||
"attributes": {"bold": true}
|
||||
},
|
||||
{"insert": " "},
|
||||
{
|
||||
"insert": "rich-text editor",
|
||||
"attributes": {"italic": true}
|
||||
},
|
||||
{"insert": "AppFlowy Editor is a "},
|
||||
{
|
||||
"insert": "highly customizable",
|
||||
"attributes": {"bold": true}
|
||||
},
|
||||
{"insert": " "},
|
||||
{
|
||||
"insert": "rich-text editor",
|
||||
"attributes": {"italic": true}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
const plainTextJson = {
|
||||
"document": {
|
||||
"type": "page",
|
||||
"children": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{
|
||||
"insert":
|
||||
"AppFlowy Editor is a highly customizable rich-text editor"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
const thirdParagraph = {
|
||||
"document": {
|
||||
"type": "page",
|
||||
"children": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{"insert": "AppFlowy Editor is a "},
|
||||
{
|
||||
"insert": "highly customizable",
|
||||
"attributes": {"bold": true}
|
||||
},
|
||||
{"insert": " "},
|
||||
{
|
||||
"insert": "rich-text editor",
|
||||
"attributes": {"italic": true}
|
||||
},
|
||||
{"insert": "AppFlowy Editor is a "},
|
||||
{
|
||||
"insert": "highly customizable",
|
||||
"attributes": {"bold": true}
|
||||
},
|
||||
{"insert": " "},
|
||||
{
|
||||
"insert": "rich-text editor",
|
||||
"attributes": {"italic": true}
|
||||
},
|
||||
{"insert": "AppFlowy Editor is a "},
|
||||
{
|
||||
"insert": "highly customizable",
|
||||
"attributes": {"bold": true}
|
||||
},
|
||||
{"insert": " "},
|
||||
{
|
||||
"insert": "rich-text editor",
|
||||
"attributes": {"italic": true}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,790 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../../infra/testable_editor.dart';
|
||||
|
||||
void main() async {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('arrow_keys_handler.dart', () {
|
||||
testWidgets('Presses arrow right key, move the cursor from left to right',
|
||||
(tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraphs(2, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
await editor.pressKey(key: LogicalKeyboardKey.arrowRight);
|
||||
|
||||
if (i == text.length - 1) {
|
||||
// Wrap to next node if the cursor is at the end of the current node.
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.single(
|
||||
path: [1],
|
||||
startOffset: 0,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final delta = editor.nodeAtPath([0])!.delta!;
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: delta.nextRunePosition(i),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('Cursor up/down', (tester) async {
|
||||
const text1 = 'Welcome';
|
||||
const text2 = 'Welcome to AppFlowy';
|
||||
final editor = tester.editor
|
||||
..addParagraph(initialText: text1)
|
||||
..addParagraph(initialText: text2);
|
||||
await editor.startTesting();
|
||||
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [1], startOffset: text2.length),
|
||||
);
|
||||
|
||||
await editor.pressKey(key: LogicalKeyboardKey.arrowUp);
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.single(path: [0], startOffset: text1.length),
|
||||
);
|
||||
|
||||
await editor.pressKey(key: LogicalKeyboardKey.arrowDown);
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.single(path: [1], startOffset: text1.length),
|
||||
);
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Cursor top/bottom select', (tester) async {
|
||||
const text = 'Welcome to Appflowy';
|
||||
final editor = tester.editor..addParagraphs(3, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
Future<void> select(bool isTop) async {
|
||||
return editor.pressKey(
|
||||
key: isTop ? LogicalKeyboardKey.arrowUp : LogicalKeyboardKey.arrowDown,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
}
|
||||
|
||||
final selection = Selection.collapsed(Position(path: [1], offset: 7));
|
||||
|
||||
// Welcome| to Appflowy
|
||||
await editor.updateSelection(
|
||||
selection,
|
||||
);
|
||||
|
||||
await select(true);
|
||||
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [0], offset: 0),
|
||||
),
|
||||
);
|
||||
|
||||
await select(false);
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [2], offset: 19),
|
||||
),
|
||||
);
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets('''move the cursor one word right
|
||||
MacOS: presses alt + arrow right key,
|
||||
Windows & Linux: presses ctrl + arrow right key,
|
||||
''', (tester) async {
|
||||
const text = 'Welcome to Appflowy';
|
||||
final editor = tester.editor..addParagraphs(2, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
final selection = Selection.collapsed(Position(path: [0]));
|
||||
await editor.updateSelection(
|
||||
selection,
|
||||
);
|
||||
|
||||
await _pressShortcutToNavigateAroundWord(editor);
|
||||
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.collapsed(Position(path: [0], offset: 'Welcome'.length)),
|
||||
);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowRight,
|
||||
);
|
||||
await _pressShortcutToNavigateAroundWord(editor);
|
||||
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.collapsed(Position(path: [0], offset: 'Welcome to'.length)),
|
||||
);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowRight,
|
||||
);
|
||||
await _pressShortcutToNavigateAroundWord(editor);
|
||||
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.collapsed(Position(path: [0], offset: text.length)),
|
||||
);
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets('''move the cursor one word left
|
||||
MacOS: presses alt + arrow left key,
|
||||
Windows & Linux: presses ctrl + arrow left key,
|
||||
''', (tester) async {
|
||||
const text = 'Welcome to Appflowy';
|
||||
final editor = tester.editor..addParagraphs(2, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
final selection =
|
||||
Selection.collapsed(Position(path: [0], offset: text.length));
|
||||
await editor.updateSelection(
|
||||
selection,
|
||||
);
|
||||
|
||||
await _pressShortcutToNavigateAroundWord(editor, toRight: false);
|
||||
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.collapsed(Position(path: [0], offset: 11)),
|
||||
);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowLeft,
|
||||
);
|
||||
await _pressShortcutToNavigateAroundWord(editor, toRight: false);
|
||||
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.collapsed(Position(path: [0], offset: 8)),
|
||||
);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowLeft,
|
||||
);
|
||||
await _pressShortcutToNavigateAroundWord(editor, toRight: false);
|
||||
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.collapsed(Position(path: [0])),
|
||||
);
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Presses arrow left/right key since selection is not collapsed and backward',
|
||||
(tester) async {
|
||||
await _testPressArrowKeyInNotCollapsedSelection(tester, true);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Presses arrow left/right key since selection is not collapsed and forward',
|
||||
(tester) async {
|
||||
await _testPressArrowKeyInNotCollapsedSelection(tester, false);
|
||||
});
|
||||
|
||||
testWidgets('Presses arrow left/right + shift in collapsed selection',
|
||||
(tester) async {
|
||||
const text = 'Welcome to Appflowy';
|
||||
final editor = tester.editor..addParagraphs(2, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
const offset = 8;
|
||||
final selection = Selection.single(path: [1], startOffset: offset);
|
||||
await editor.updateSelection(selection);
|
||||
|
||||
for (var i = offset - 1; i >= 0; i--) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowLeft,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [1], offset: i),
|
||||
),
|
||||
);
|
||||
}
|
||||
for (var i = text.length; i >= 0; i--) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowLeft,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [0], offset: i),
|
||||
),
|
||||
);
|
||||
}
|
||||
for (var i = 1; i <= text.length; i++) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowRight,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [0], offset: i),
|
||||
),
|
||||
);
|
||||
}
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowRight,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [1], offset: i),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Presses arrow left/right + shift in not collapsed and backward selection',
|
||||
(tester) async {
|
||||
const text = 'Welcome to Appflowy';
|
||||
final editor = tester.editor..addParagraphs(2, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
const start = 8;
|
||||
const end = 12;
|
||||
final selection = Selection.single(
|
||||
path: [0],
|
||||
startOffset: start,
|
||||
endOffset: end,
|
||||
);
|
||||
await editor.updateSelection(selection);
|
||||
for (var i = end + 1; i <= text.length; i++) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowRight,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [0], offset: i),
|
||||
),
|
||||
);
|
||||
}
|
||||
for (var i = text.length - 1; i >= 0; i--) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowLeft,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [0], offset: i),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Presses arrow left/right + command in not collapsed and forward selection',
|
||||
(tester) async {
|
||||
const text = 'Welcome to Appflowy';
|
||||
final editor = tester.editor..addParagraphs(2, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
const start = 12;
|
||||
const end = 8;
|
||||
final selection = Selection.single(
|
||||
path: [0],
|
||||
startOffset: start,
|
||||
endOffset: end,
|
||||
);
|
||||
await editor.updateSelection(selection);
|
||||
for (var i = end - 1; i >= 0; i--) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowLeft,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [0], offset: i),
|
||||
),
|
||||
);
|
||||
}
|
||||
for (var i = 1; i <= text.length; i++) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowRight,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [0], offset: i),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Presses arrow left/right/up/down + meta in collapsed selection',
|
||||
(tester) async {
|
||||
await _testPressArrowKeyWithMetaInSelection(tester, true, false);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Presses arrow left/right/up/down + meta in not collapsed and backward selection',
|
||||
(tester) async {
|
||||
await _testPressArrowKeyWithMetaInSelection(tester, false, true);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Presses arrow left/right/up/down + meta in not collapsed and forward selection',
|
||||
(tester) async {
|
||||
await _testPressArrowKeyWithMetaInSelection(tester, false, false);
|
||||
});
|
||||
|
||||
testWidgets('Presses arrow up/down + shift in not collapsed selection',
|
||||
(tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor
|
||||
..addParagraphs(2, initialText: text)
|
||||
..addEmptyParagraph()
|
||||
..addParagraph(initialText: text)
|
||||
..addEmptyParagraph()
|
||||
..addParagraphs(2, initialText: text);
|
||||
await editor.startTesting();
|
||||
final selection = Selection.single(path: [3], startOffset: 8);
|
||||
await editor.updateSelection(selection);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowUp,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
}
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [0], offset: 0),
|
||||
),
|
||||
);
|
||||
for (int i = 0; i < 7; i++) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowDown,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
}
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [6], offset: 0),
|
||||
),
|
||||
);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowUp,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
}
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [3], offset: 0),
|
||||
),
|
||||
);
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
//Before:
|
||||
//Welcome |to Appflowy 😁
|
||||
//Welcome to Appflowy 😁
|
||||
//After On Mac
|
||||
//Welcome |to Appflowy 😁
|
||||
//Welcome to Appflowy 😁|
|
||||
//expands the selection to include till end of line
|
||||
//After On Windows
|
||||
//Welcome |to Appflowy 😁
|
||||
//Welcome to| Appflowy 😁
|
||||
//expands the selection to include till end of next word
|
||||
testWidgets('Presses shift + arrow down and meta/ctrl + shift + right',
|
||||
(tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraphs(2, initialText: text);
|
||||
await editor.startTesting();
|
||||
final selection = Selection.single(path: [0], startOffset: 8);
|
||||
await editor.updateSelection(selection);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowDown,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowRight,
|
||||
isShiftPressed: true,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
if (Platform.isMacOS) {
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [1], offset: text.length),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [1], offset: 'Welcome to'.length),
|
||||
),
|
||||
);
|
||||
}
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
//Before:
|
||||
//Welcome to Appflowy 😁
|
||||
//Welcome |to Appflowy 😁
|
||||
//After On Mac
|
||||
//|Welcome to Appflowy 😁
|
||||
//Welcome |to Appflowy 😁
|
||||
//expands the selection to include till end of line
|
||||
//After On Windows
|
||||
//Welcome| to Appflowy 😁
|
||||
//Welcome |to Appflowy 😁
|
||||
//expands the selection to include till end of next word
|
||||
testWidgets('Presses shift + arrow up and meta/ctrl + shift + left',
|
||||
(tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraphs(2, initialText: text);
|
||||
await editor.startTesting();
|
||||
final selection = Selection.single(path: [1], startOffset: 8);
|
||||
await editor.updateSelection(selection);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowUp,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowLeft,
|
||||
isShiftPressed: true,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
if (Platform.isMacOS) {
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [0], offset: 0),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [0], offset: 'Welcome'.length),
|
||||
),
|
||||
);
|
||||
}
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
// press alt on MacOS to select a word
|
||||
// press ctrl on Windows and Linux to select a word
|
||||
testWidgets('Presses shift + alt/ctrl + arrow left to select a word',
|
||||
(tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraphs(2, initialText: text);
|
||||
await editor.startTesting();
|
||||
final selection = Selection.single(path: [1], startOffset: 10);
|
||||
await editor.updateSelection(selection);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowLeft,
|
||||
isShiftPressed: true,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
// <to>
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [1], offset: 8),
|
||||
),
|
||||
);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowLeft,
|
||||
isShiftPressed: true,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
// < to>
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [1], offset: 7),
|
||||
),
|
||||
);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowLeft,
|
||||
isShiftPressed: true,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
// <Welcome to>
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [1], offset: 0),
|
||||
),
|
||||
);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowLeft,
|
||||
isShiftPressed: true,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
// <😁>
|
||||
// <Welcome to>
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [0], offset: 22),
|
||||
),
|
||||
);
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
// press alt on MacOS to select a word
|
||||
// press ctrl on Windows and Linux to select a word
|
||||
testWidgets('Presses shift + alt/ctrl + arrow right to select a word',
|
||||
(tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraphs(2, initialText: text);
|
||||
await editor.startTesting();
|
||||
final selection = Selection.single(path: [0], startOffset: 10);
|
||||
await editor.updateSelection(selection);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowRight,
|
||||
isShiftPressed: true,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
// < >
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [0], offset: 11),
|
||||
),
|
||||
);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowRight,
|
||||
isShiftPressed: true,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
// < Appflowy>
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [0], offset: 19),
|
||||
),
|
||||
);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowRight,
|
||||
isShiftPressed: true,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowRight,
|
||||
isShiftPressed: true,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
// < Appflowy 😁>
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [0], offset: 22),
|
||||
),
|
||||
);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowRight,
|
||||
isShiftPressed: true,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
// < Appflowy 😁>
|
||||
// <>
|
||||
expect(
|
||||
editor.selection,
|
||||
selection.copyWith(
|
||||
end: Position(path: [1], offset: 0),
|
||||
),
|
||||
);
|
||||
await editor.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _testPressArrowKeyWithMetaInSelection(
|
||||
WidgetTester tester,
|
||||
bool isSingle,
|
||||
bool isBackward,
|
||||
) async {
|
||||
const text = 'Welcome to Appflowy';
|
||||
final editor = tester.editor..addParagraphs(2, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
final initialSelection = Selection.single(path: [0], startOffset: 8);
|
||||
final selectionAtBeginning = Selection.single(path: [0], startOffset: 0);
|
||||
final selectionAtEnd = Selection.single(path: [0], startOffset: text.length);
|
||||
final selectionAtEndOfWelcome = Selection.single(path: [0], startOffset: 7);
|
||||
|
||||
Selection selection;
|
||||
if (isSingle) {
|
||||
selection = initialSelection;
|
||||
} else {
|
||||
if (isBackward) {
|
||||
selection = Selection.single(
|
||||
path: [0],
|
||||
startOffset: 8,
|
||||
endOffset: text.length,
|
||||
);
|
||||
} else {
|
||||
selection = Selection.single(
|
||||
path: [0],
|
||||
startOffset: text.length,
|
||||
endOffset: 8,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await editor.updateSelection(selection);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowLeft,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
if (Platform.isMacOS) {
|
||||
expect(editor.selection, selectionAtBeginning);
|
||||
} else if (isSingle) {
|
||||
expect(editor.selection, selectionAtBeginning);
|
||||
} else if (isBackward) {
|
||||
expect(editor.selection, initialSelection);
|
||||
} else {
|
||||
expect(editor.selection, selectionAtEndOfWelcome);
|
||||
}
|
||||
|
||||
await editor.updateSelection(selectionAtBeginning);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowRight,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
if (Platform.isMacOS) {
|
||||
expect(editor.selection, selectionAtEnd);
|
||||
} else {
|
||||
expect(editor.selection, selectionAtEndOfWelcome);
|
||||
}
|
||||
|
||||
await editor.updateSelection(selectionAtEnd);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowUp,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
expect(
|
||||
editor.selection,
|
||||
selectionAtBeginning,
|
||||
);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.arrowDown,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.single(path: [1], startOffset: text.length),
|
||||
);
|
||||
|
||||
await editor.dispose();
|
||||
}
|
||||
|
||||
Future<void> _testPressArrowKeyInNotCollapsedSelection(
|
||||
WidgetTester tester,
|
||||
bool isBackward,
|
||||
) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraphs(2, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
final start = Position(path: [0], offset: 5);
|
||||
final end = Position(path: [1], offset: 10);
|
||||
final selection = Selection(
|
||||
start: isBackward ? start : end,
|
||||
end: isBackward ? end : start,
|
||||
);
|
||||
await editor.updateSelection(selection);
|
||||
await editor.pressKey(key: LogicalKeyboardKey.arrowLeft);
|
||||
expect(editor.selection?.start, start);
|
||||
|
||||
await editor.updateSelection(selection);
|
||||
await editor.pressKey(key: LogicalKeyboardKey.arrowRight);
|
||||
expect(editor.selection?.end, end);
|
||||
|
||||
await editor.dispose();
|
||||
}
|
||||
|
||||
Future<void> _pressShortcutToNavigateAroundWord(
|
||||
TestableEditor editor, {
|
||||
bool toRight = true,
|
||||
}) async {
|
||||
await editor.pressKey(
|
||||
key: toRight ? LogicalKeyboardKey.arrowRight : LogicalKeyboardKey.arrowLeft,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../../infra/testable_editor.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
|
||||
group('checkbox_event_handler_test.dart', () {
|
||||
testWidgets('toggle checkbox with shortcut ctrl+enter', (tester) async {
|
||||
const text = 'Checkbox1';
|
||||
final editor = tester.editor
|
||||
..addNode(
|
||||
todoListNode(
|
||||
checked: false,
|
||||
delta: Delta()..insert(text),
|
||||
),
|
||||
);
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: text.length),
|
||||
);
|
||||
|
||||
var node = editor.nodeAtPath([0])!;
|
||||
expect(node.type, 'todo_list');
|
||||
expect(node.attributes[TodoListBlockKeys.checked], false);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.enter,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
node = editor.nodeAtPath([0])!;
|
||||
expect(node.attributes[TodoListBlockKeys.checked], true);
|
||||
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: text.length),
|
||||
);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.enter,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
node = editor.nodeAtPath([0])!;
|
||||
expect(node.attributes[TodoListBlockKeys.checked], false);
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'test if all checkboxes get unchecked after toggling them, if all of them were already checked',
|
||||
(tester) async {
|
||||
const text = 'Checkbox';
|
||||
final editor = tester.editor
|
||||
..addNode(
|
||||
todoListNode(
|
||||
checked: true,
|
||||
attributes: {'delta': (Delta()..insert(text)).toJson()},
|
||||
),
|
||||
)
|
||||
..addNode(
|
||||
todoListNode(
|
||||
checked: true,
|
||||
attributes: {'delta': (Delta()..insert(text)).toJson()},
|
||||
),
|
||||
)
|
||||
..addNode(
|
||||
todoListNode(
|
||||
checked: true,
|
||||
attributes: {'delta': (Delta()..insert(text)).toJson()},
|
||||
),
|
||||
);
|
||||
|
||||
await editor.startTesting();
|
||||
|
||||
final selection =
|
||||
Selection.collapsed(Position(path: [0], offset: text.length));
|
||||
await editor.updateSelection(selection);
|
||||
|
||||
var nodes = editor.editorState.getNodesInSelection(selection);
|
||||
for (final node in nodes) {
|
||||
expect(node.type, 'todo_list');
|
||||
expect(node.attributes[TodoListBlockKeys.checked], true);
|
||||
}
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.enter,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
nodes = editor.editorState.getNodesInSelection(selection);
|
||||
for (final node in nodes) {
|
||||
expect(node.attributes[TodoListBlockKeys.checked], false);
|
||||
}
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'test if all checkboxes get checked after toggling them, if any one of them were already checked',
|
||||
(tester) async {
|
||||
const text = 'Checkbox';
|
||||
final editor = tester.editor
|
||||
..addNode(
|
||||
todoListNode(
|
||||
checked: false,
|
||||
attributes: {'delta': (Delta()..insert(text)).toJson()},
|
||||
),
|
||||
)
|
||||
..addNode(
|
||||
todoListNode(
|
||||
checked: true,
|
||||
attributes: {'delta': (Delta()..insert(text)).toJson()},
|
||||
),
|
||||
)
|
||||
..addNode(
|
||||
todoListNode(
|
||||
checked: false,
|
||||
attributes: {'delta': (Delta()..insert(text)).toJson()},
|
||||
),
|
||||
);
|
||||
|
||||
await editor.startTesting();
|
||||
|
||||
final selection = Selection(
|
||||
start: Position(path: [0], offset: 0),
|
||||
end: Position(path: [2], offset: text.length),
|
||||
);
|
||||
await editor.updateSelection(
|
||||
selection,
|
||||
);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.enter,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
final nodes = editor.editorState.getNodesInSelection(selection);
|
||||
for (final node in nodes) {
|
||||
expect(node.type, 'todo_list');
|
||||
expect(node.attributes[TodoListBlockKeys.checked], true);
|
||||
}
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../../../infra/clipboard_test.dart';
|
||||
import '../../../infra/testable_editor.dart';
|
||||
|
||||
void main() async {
|
||||
late MockClipboard mockClipboard;
|
||||
|
||||
setUp(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
mockClipboard = const MockClipboard(html: null, text: null);
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, (message) async {
|
||||
switch (message.method) {
|
||||
case "Clipboard.getData":
|
||||
return mockClipboard.getData;
|
||||
case "Clipboard.setData":
|
||||
final args = message.arguments as Map<String, dynamic>;
|
||||
mockClipboard = mockClipboard.copyWith(
|
||||
text: args['text'],
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
});
|
||||
|
||||
group('copy_command_test.dart', () {
|
||||
testWidgets('Presses Command + A in small document and copy text',
|
||||
(tester) async {
|
||||
await _testHandleCopy(tester);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _testHandleCopy(
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
final editor = tester.editor
|
||||
..initializeWithDocument(Document.fromJson(paragraphdata));
|
||||
await editor.startTesting(platform: TargetPlatform.windows);
|
||||
await editor.updateSelection(Selection.collapsed(Position(path: [0])));
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyA,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyC,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
deleteSelectedContent(editor.editorState);
|
||||
expect(editor.document.root.children.length, 1);
|
||||
expect(editor.document.root.children.first.delta!.isEmpty, true);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyC,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
final clipBoardData = await AppFlowyClipboard.getData();
|
||||
//this will be null because html content is not testable
|
||||
expect(clipBoardData.html, null);
|
||||
expect(clipBoardData.text, copiedText);
|
||||
|
||||
await editor.dispose();
|
||||
}
|
||||
|
||||
const copiedText =
|
||||
"AppFlowy Editor is a highly customizable rich-text editor";
|
||||
const paragraphdata = {
|
||||
"document": {
|
||||
"type": "page",
|
||||
"children": [
|
||||
{
|
||||
'type': 'paragraph',
|
||||
'data': {
|
||||
'delta': [
|
||||
{'insert': 'AppFlowy Editor is a '},
|
||||
{
|
||||
'insert': 'highly customizable',
|
||||
'attributes': {'bold': true}
|
||||
},
|
||||
{'insert': ' '},
|
||||
{
|
||||
'insert': 'rich-text editor',
|
||||
'attributes': {'italic': true}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,337 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../../infra/testable_editor.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
group('cut_command_test.dart', () {
|
||||
testWidgets('update selection and execute cut command', (tester) async {
|
||||
await _testCutHandle(tester, Document.fromJson(paragraphdata));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _testCutHandle(
|
||||
WidgetTester tester,
|
||||
Document document,
|
||||
) async {
|
||||
final editor = tester.editor..initializeWithDocument(document);
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection(
|
||||
start: Position(path: [2], offset: 0),
|
||||
end: Position(path: [2], offset: 69),
|
||||
),
|
||||
);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyX,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
expect(
|
||||
editor.editorState.document.toJson(),
|
||||
Document.fromJson(afterCut).toJson(),
|
||||
);
|
||||
|
||||
await editor.dispose();
|
||||
}
|
||||
|
||||
const paragraphdata = {
|
||||
"document": {
|
||||
"type": "page",
|
||||
"children": [
|
||||
{
|
||||
"type": "heading",
|
||||
"data": {
|
||||
"level": 2,
|
||||
"delta": [
|
||||
{"insert": "👋 "},
|
||||
{
|
||||
"insert": "Welcome to",
|
||||
"attributes": {"bold": true}
|
||||
},
|
||||
{"insert": " "},
|
||||
{
|
||||
"insert": "AppFlowy Editor",
|
||||
"attributes": {
|
||||
"href": "appflowy.io",
|
||||
"italic": true,
|
||||
"bold": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {"delta": []}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{"insert": "AppFlowy Editor is a"},
|
||||
{"insert": " "},
|
||||
{
|
||||
"insert": "highly customizable",
|
||||
"attributes": {"bold": true}
|
||||
},
|
||||
{"insert": " "},
|
||||
{
|
||||
"insert": "rich-text editor",
|
||||
"attributes": {"italic": true}
|
||||
},
|
||||
{"insert": " for "},
|
||||
{
|
||||
"insert": "Flutter",
|
||||
"attributes": {"underline": true}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "todo_list",
|
||||
"data": {
|
||||
"checked": true,
|
||||
"delta": [
|
||||
{"insert": "Customizable"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "todo_list",
|
||||
"data": {
|
||||
"checked": true,
|
||||
"delta": [
|
||||
{"insert": "Test-covered"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "todo_list",
|
||||
"data": {
|
||||
"checked": false,
|
||||
"delta": [
|
||||
{"insert": "more to come!"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {"delta": []}
|
||||
},
|
||||
{
|
||||
"type": "quote",
|
||||
"data": {
|
||||
"delta": [
|
||||
{"insert": "Here is an example you can give a try"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {"delta": []}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{"insert": "You can also use "},
|
||||
{
|
||||
"insert": "AppFlowy Editor",
|
||||
"attributes": {
|
||||
"italic": true,
|
||||
"bold": true,
|
||||
"font_color": "0xffD70040",
|
||||
"bg_color": "0x6000BCF0"
|
||||
}
|
||||
},
|
||||
{"insert": " as a component to build your own app."}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {"delta": []}
|
||||
},
|
||||
{
|
||||
"type": "bulleted_list",
|
||||
"data": {
|
||||
"delta": [
|
||||
{"insert": "Use / to insert blocks"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "bulleted_list",
|
||||
"data": {
|
||||
"delta": [
|
||||
{
|
||||
"insert":
|
||||
"Select text to trigger to the toolbar to format your notes."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {"delta": []}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{
|
||||
"insert":
|
||||
"If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const afterCut = {
|
||||
"document": {
|
||||
"type": "page",
|
||||
"children": [
|
||||
{
|
||||
"type": "heading",
|
||||
"data": {
|
||||
"level": 2,
|
||||
"delta": [
|
||||
{"insert": "👋 "},
|
||||
{
|
||||
"insert": "Welcome to",
|
||||
"attributes": {"bold": true}
|
||||
},
|
||||
{"insert": " "},
|
||||
{
|
||||
"insert": "AppFlowy Editor",
|
||||
"attributes": {
|
||||
"href": "appflowy.io",
|
||||
"italic": true,
|
||||
"bold": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {"delta": []}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {"delta": []}
|
||||
},
|
||||
{
|
||||
"type": "todo_list",
|
||||
"data": {
|
||||
"checked": true,
|
||||
"delta": [
|
||||
{"insert": "Customizable"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "todo_list",
|
||||
"data": {
|
||||
"checked": true,
|
||||
"delta": [
|
||||
{"insert": "Test-covered"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "todo_list",
|
||||
"data": {
|
||||
"checked": false,
|
||||
"delta": [
|
||||
{"insert": "more to come!"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {"delta": []}
|
||||
},
|
||||
{
|
||||
"type": "quote",
|
||||
"data": {
|
||||
"delta": [
|
||||
{"insert": "Here is an example you can give a try"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {"delta": []}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{"insert": "You can also use "},
|
||||
{
|
||||
"insert": "AppFlowy Editor",
|
||||
"attributes": {
|
||||
"italic": true,
|
||||
"bold": true,
|
||||
"font_color": "0xffD70040",
|
||||
"bg_color": "0x6000BCF0"
|
||||
}
|
||||
},
|
||||
{"insert": " as a component to build your own app."}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {"delta": []}
|
||||
},
|
||||
{
|
||||
"type": "bulleted_list",
|
||||
"data": {
|
||||
"delta": [
|
||||
{"insert": "Use / to insert blocks"}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "bulleted_list",
|
||||
"data": {
|
||||
"delta": [
|
||||
{
|
||||
"insert":
|
||||
"Select text to trigger to the toolbar to format your notes."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {"delta": []}
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{
|
||||
"insert":
|
||||
"If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,313 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../../infra/testable_editor.dart';
|
||||
|
||||
void main() async {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('enter_without_shift_in_text_node_handler.dart', () {
|
||||
testWidgets('Presses enter key in empty document', (tester) async {
|
||||
// Before
|
||||
//
|
||||
// [Empty Line]
|
||||
//
|
||||
// After
|
||||
//
|
||||
// [Empty Line] * 10
|
||||
//
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
// Pressing the enter key continuously.
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
await editor.pressKey(
|
||||
character: '\n',
|
||||
);
|
||||
expect(editor.documentRootLen, i + 1);
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.single(path: [i], startOffset: 0),
|
||||
);
|
||||
}
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Presses enter key in non-empty document', (tester) async {
|
||||
// Before
|
||||
//
|
||||
// Welcome to Appflowy 😁
|
||||
// Welcome to Appflowy 😁
|
||||
// Welcome to Appflowy 😁
|
||||
//
|
||||
// After
|
||||
//
|
||||
// Welcome to Appflowy 😁
|
||||
// Welcome to Appflowy 😁
|
||||
// [Empty Line]
|
||||
// Welcome to Appflowy 😁
|
||||
//
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
var lines = 3;
|
||||
|
||||
final editor = tester.editor;
|
||||
editor.addParagraphs(lines, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
expect(editor.documentRootLen, lines);
|
||||
|
||||
// Presses the enter key in last line.
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [lines - 1], startOffset: 0),
|
||||
);
|
||||
await editor.pressKey(
|
||||
character: '\n',
|
||||
);
|
||||
lines += 1;
|
||||
expect(editor.documentRootLen, lines);
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.single(path: [lines - 1], startOffset: 0),
|
||||
);
|
||||
var lastNode = editor.nodeAtPath([lines - 1]);
|
||||
expect(lastNode != null, true);
|
||||
expect(lastNode?.type, 'paragraph');
|
||||
expect(lastNode?.delta?.toPlainText(), text);
|
||||
expect(lastNode?.previous?.delta?.toPlainText(), '');
|
||||
expect(
|
||||
lastNode?.previous?.previous?.delta?.toPlainText(),
|
||||
text,
|
||||
);
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
// Before
|
||||
//
|
||||
// Welcome to Appflowy 😁
|
||||
// [Style] Welcome to Appflowy 😁
|
||||
// [Style] Welcome to Appflowy 😁
|
||||
//
|
||||
// After
|
||||
//
|
||||
// Welcome to Appflowy 😁
|
||||
// [Empty Line]
|
||||
// [Style] Welcome to Appflowy 😁
|
||||
// [Style] Welcome to Appflowy 😁
|
||||
// [Style]
|
||||
testWidgets('Presses enter key in bulleted list', (tester) async {
|
||||
await _testStyleNeedToBeCopy(tester, 'bulleted_list');
|
||||
});
|
||||
|
||||
testWidgets('Presses enter key in numbered list', (tester) async {
|
||||
await _testStyleNeedToBeCopy(tester, 'numbered_list');
|
||||
});
|
||||
|
||||
testWidgets('Presses enter key in checkbox styled text', (tester) async {
|
||||
await _testStyleNeedToBeCopy(tester, 'todo_list');
|
||||
});
|
||||
|
||||
testWidgets('Presses enter key in checkbox list indented', (tester) async {
|
||||
await _testListOutdent(tester, 'todo_list');
|
||||
});
|
||||
|
||||
testWidgets('Presses enter key in bulleted list indented', (tester) async {
|
||||
await _testListOutdent(tester, 'bulleted_list');
|
||||
});
|
||||
|
||||
testWidgets('Presses enter key in multiple selection from top to bottom',
|
||||
(tester) async {
|
||||
_testMultipleSelection(tester, true);
|
||||
});
|
||||
|
||||
testWidgets('Presses enter key in multiple selection from bottom to top',
|
||||
(tester) async {
|
||||
_testMultipleSelection(tester, false);
|
||||
});
|
||||
|
||||
testWidgets('Presses enter key in the first line', (tester) async {
|
||||
// Before
|
||||
//
|
||||
// Welcome to Appflowy 😁
|
||||
//
|
||||
// After
|
||||
//
|
||||
// [Empty Line]
|
||||
// Welcome to Appflowy 😁
|
||||
//
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraph(initialText: text);
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
await editor.pressKey(character: '\n');
|
||||
expect(editor.documentRootLen, 2);
|
||||
expect(editor.nodeAtPath([1])?.delta?.toPlainText(), text);
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _testStyleNeedToBeCopy(WidgetTester tester, String style) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final attributes = {
|
||||
'delta': (Delta()..insert(text)).toJson(),
|
||||
};
|
||||
Node? node;
|
||||
if (style == 'todo_list') {
|
||||
node = todoListNode(checked: true, attributes: attributes);
|
||||
} else if (style == 'numbered_list') {
|
||||
node = numberedListNode(attributes: attributes);
|
||||
} else if (style == 'bulleted_list') {
|
||||
node = bulletedListNode(attributes: attributes);
|
||||
} else if (style == 'quote') {
|
||||
node = quoteNode(attributes: attributes);
|
||||
}
|
||||
if (node == null) {
|
||||
throw Exception('Invalid style: $style');
|
||||
}
|
||||
final editor = tester.editor
|
||||
..addParagraph(initialText: text)
|
||||
..addNode(node)
|
||||
..addNode(node.copyWith());
|
||||
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [1], startOffset: 0),
|
||||
);
|
||||
await editor.pressKey(
|
||||
character: '\n',
|
||||
);
|
||||
expect(editor.selection, Selection.single(path: [2], startOffset: 0));
|
||||
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [3], startOffset: text.length),
|
||||
);
|
||||
await editor.pressKey(
|
||||
character: '\n',
|
||||
);
|
||||
expect(editor.selection, Selection.single(path: [4], startOffset: 0));
|
||||
|
||||
expect(editor.nodeAtPath([4])?.type, style);
|
||||
|
||||
await editor.pressKey(
|
||||
character: '\n',
|
||||
);
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.single(path: [4], startOffset: 0),
|
||||
);
|
||||
expect(editor.nodeAtPath([4])?.type, 'paragraph');
|
||||
|
||||
await editor.dispose();
|
||||
}
|
||||
|
||||
Future<void> _testListOutdent(WidgetTester tester, String style) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final attributes = {
|
||||
'delta': (Delta()..insert(text)).toJson(),
|
||||
};
|
||||
Node? node;
|
||||
if (style == 'todo_list') {
|
||||
node = todoListNode(checked: true, attributes: attributes);
|
||||
} else if (style == 'numbered_list') {
|
||||
node = numberedListNode(attributes: attributes);
|
||||
} else if (style == 'bulleted_list') {
|
||||
node = bulletedListNode(attributes: attributes);
|
||||
}
|
||||
if (node == null) {
|
||||
throw Exception('Invalid style: $style');
|
||||
}
|
||||
final editor = tester.editor
|
||||
..addParagraph(initialText: text)
|
||||
..addNode(node)
|
||||
..addNode(
|
||||
node.copyWith(
|
||||
attributes: {
|
||||
...node.attributes,
|
||||
'delta': Delta().toJson(),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [2], startOffset: 0),
|
||||
);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.tab,
|
||||
);
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.single(path: [1, 0], startOffset: 0),
|
||||
);
|
||||
|
||||
await editor.pressKey(
|
||||
character: '\n',
|
||||
);
|
||||
// clear the style
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.single(path: [1, 0], startOffset: 0),
|
||||
);
|
||||
expect(editor.nodeAtPath([1, 0])?.type, 'paragraph');
|
||||
|
||||
await editor.pressKey(
|
||||
character: '\n',
|
||||
);
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.single(path: [1, 1], startOffset: 0),
|
||||
);
|
||||
|
||||
expect(editor.nodeAtPath([1, 1])?.type, 'paragraph');
|
||||
|
||||
await editor.dispose();
|
||||
}
|
||||
|
||||
Future<void> _testMultipleSelection(
|
||||
WidgetTester tester,
|
||||
bool isBackwardSelection,
|
||||
) async {
|
||||
// Before
|
||||
//
|
||||
// Welcome to Appflowy 😁
|
||||
// Welcome to Appflowy 😁
|
||||
// Welcome to Appflowy 😁
|
||||
// Welcome to Appflowy 😁
|
||||
//
|
||||
// After
|
||||
//
|
||||
// Welcome
|
||||
// to Appflowy 😁
|
||||
//
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor;
|
||||
var lines = 4;
|
||||
|
||||
editor.addParagraphs(lines, initialText: text);
|
||||
|
||||
await editor.startTesting();
|
||||
final start = Position(path: [0], offset: 7);
|
||||
final end = Position(path: [3], offset: 8);
|
||||
await editor.updateSelection(
|
||||
Selection(
|
||||
start: isBackwardSelection ? start : end,
|
||||
end: isBackwardSelection ? end : start,
|
||||
),
|
||||
);
|
||||
await editor.pressKey(
|
||||
character: '\n',
|
||||
);
|
||||
|
||||
expect(editor.documentRootLen, 2);
|
||||
expect(editor.nodeAtPath([0])?.delta?.toPlainText(), 'Welcome');
|
||||
expect(editor.nodeAtPath([1])?.delta?.toPlainText(), 'to Appflowy 😁');
|
||||
|
||||
await editor.dispose();
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../../infra/testable_editor.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
|
||||
group('exit_editing_mode_handler.dart', () {
|
||||
testWidgets('Exit editing mode', (tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
const lines = 3;
|
||||
final editor = tester.editor..addParagraphs(lines, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
// collapsed selection
|
||||
await _testSelection(editor, Selection.single(path: [1], startOffset: 0));
|
||||
|
||||
// single selection
|
||||
await _testSelection(
|
||||
editor,
|
||||
Selection.single(path: [1], startOffset: 0, endOffset: text.length),
|
||||
);
|
||||
|
||||
// multiple selection
|
||||
await _testSelection(
|
||||
editor,
|
||||
Selection(
|
||||
start: Position(path: [0], offset: 0),
|
||||
end: Position(path: [2], offset: text.length),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _testSelection(
|
||||
TestableEditor editor,
|
||||
Selection selection,
|
||||
) async {
|
||||
await editor.updateSelection(selection);
|
||||
expect(editor.selection, selection);
|
||||
await editor.pressKey(key: LogicalKeyboardKey.escape);
|
||||
expect(editor.selection, null);
|
||||
}
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../../infra/testable_editor.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
|
||||
group('format_style_handler.dart', () {
|
||||
testWidgets('Presses Command + B to update text style', (tester) async {
|
||||
await _testUpdateTextStyleByCommandX(
|
||||
tester,
|
||||
BuiltInAttributeKey.bold,
|
||||
true,
|
||||
LogicalKeyboardKey.keyB,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Presses Command + I to update text style', (tester) async {
|
||||
await _testUpdateTextStyleByCommandX(
|
||||
tester,
|
||||
BuiltInAttributeKey.italic,
|
||||
true,
|
||||
LogicalKeyboardKey.keyI,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Presses Command + U to update text style', (tester) async {
|
||||
await _testUpdateTextStyleByCommandX(
|
||||
tester,
|
||||
BuiltInAttributeKey.underline,
|
||||
true,
|
||||
LogicalKeyboardKey.keyU,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Presses Command + Shift + S to update text style',
|
||||
(tester) async {
|
||||
await _testUpdateTextStyleByCommandX(
|
||||
tester,
|
||||
BuiltInAttributeKey.strikethrough,
|
||||
true,
|
||||
LogicalKeyboardKey.keyS,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Presses Command + E to update text style', (tester) async {
|
||||
await _testUpdateTextStyleByCommandX(
|
||||
tester,
|
||||
BuiltInAttributeKey.code,
|
||||
true,
|
||||
LogicalKeyboardKey.keyE,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _testUpdateTextStyleByCommandX(
|
||||
WidgetTester tester,
|
||||
String matchStyle,
|
||||
dynamic matchValue,
|
||||
LogicalKeyboardKey key,
|
||||
) async {
|
||||
final isShiftPressed =
|
||||
key == LogicalKeyboardKey.keyS || key == LogicalKeyboardKey.keyH;
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraphs(3, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
var selection =
|
||||
Selection.single(path: [1], startOffset: 2, endOffset: text.length - 2);
|
||||
await editor.updateSelection(selection);
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
await editor.pressKey(
|
||||
key: key,
|
||||
isShiftPressed: isShiftPressed,
|
||||
isControlPressed: true,
|
||||
);
|
||||
} else {
|
||||
await editor.pressKey(
|
||||
key: key,
|
||||
isShiftPressed: isShiftPressed,
|
||||
isMetaPressed: true,
|
||||
);
|
||||
}
|
||||
Node? node = editor.nodeAtPath([1]);
|
||||
expect(
|
||||
node?.allSatisfyInSelection(selection, (delta) {
|
||||
return delta
|
||||
.everyAttributes((element) => element[matchStyle] == matchValue);
|
||||
}),
|
||||
true,
|
||||
);
|
||||
|
||||
selection =
|
||||
Selection.single(path: [1], startOffset: 0, endOffset: text.length);
|
||||
await editor.updateSelection(selection);
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
await editor.pressKey(
|
||||
key: key,
|
||||
isShiftPressed: isShiftPressed,
|
||||
isControlPressed: true,
|
||||
);
|
||||
} else {
|
||||
await editor.pressKey(
|
||||
key: key,
|
||||
isShiftPressed: isShiftPressed,
|
||||
isMetaPressed: true,
|
||||
);
|
||||
}
|
||||
node = editor.nodeAtPath([1]);
|
||||
expect(
|
||||
node?.allSatisfyInSelection(selection, (delta) {
|
||||
return delta
|
||||
.everyAttributes((element) => element[matchStyle] == matchValue);
|
||||
}),
|
||||
true,
|
||||
);
|
||||
|
||||
await editor.updateSelection(selection);
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
await editor.pressKey(
|
||||
key: key,
|
||||
isShiftPressed: isShiftPressed,
|
||||
isControlPressed: true,
|
||||
);
|
||||
} else {
|
||||
await editor.pressKey(
|
||||
key: key,
|
||||
isShiftPressed: isShiftPressed,
|
||||
isMetaPressed: true,
|
||||
);
|
||||
}
|
||||
node = editor.nodeAtPath([1]);
|
||||
expect(
|
||||
node?.allSatisfyInSelection(selection, (delta) {
|
||||
return delta
|
||||
.everyAttributes((element) => element[matchStyle] == matchValue);
|
||||
}),
|
||||
false,
|
||||
);
|
||||
|
||||
selection = Selection(
|
||||
start: Position(path: [0], offset: 0),
|
||||
end: Position(path: [2], offset: text.length),
|
||||
);
|
||||
await editor.updateSelection(selection);
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
await editor.pressKey(
|
||||
key: key,
|
||||
isShiftPressed: isShiftPressed,
|
||||
isControlPressed: true,
|
||||
);
|
||||
} else {
|
||||
await editor.pressKey(
|
||||
key: key,
|
||||
isShiftPressed: isShiftPressed,
|
||||
isMetaPressed: true,
|
||||
);
|
||||
}
|
||||
var nodes = editor.editorState.service.selectionService.currentSelectedNodes;
|
||||
expect(nodes.length, 3);
|
||||
for (final node in nodes) {
|
||||
expect(
|
||||
node.allSatisfyInSelection(
|
||||
Selection.single(
|
||||
path: node.path,
|
||||
startOffset: 0,
|
||||
endOffset: text.length,
|
||||
), (delta) {
|
||||
return delta
|
||||
.everyAttributes((element) => element[matchStyle] == matchValue);
|
||||
}),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
await editor.updateSelection(selection);
|
||||
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
await editor.pressKey(
|
||||
key: key,
|
||||
isShiftPressed: isShiftPressed,
|
||||
isControlPressed: true,
|
||||
);
|
||||
} else {
|
||||
await editor.pressKey(
|
||||
key: key,
|
||||
isShiftPressed: isShiftPressed,
|
||||
isMetaPressed: true,
|
||||
);
|
||||
}
|
||||
nodes = editor.editorState.service.selectionService.currentSelectedNodes;
|
||||
expect(nodes.length, 3);
|
||||
for (final node in nodes) {
|
||||
expect(
|
||||
node.allSatisfyInSelection(
|
||||
Selection.single(
|
||||
path: node.path,
|
||||
startOffset: 0,
|
||||
endOffset: text.length,
|
||||
), (delta) {
|
||||
return delta
|
||||
.everyAttributes((element) => element[matchStyle] == matchValue);
|
||||
}),
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
await editor.dispose();
|
||||
}
|
||||
|
|
@ -0,0 +1,780 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../../infra/testable_editor.dart';
|
||||
|
||||
void main() async {
|
||||
bool allInSelection(
|
||||
Node? node,
|
||||
Selection selection,
|
||||
String key,
|
||||
) {
|
||||
return node?.allSatisfyInSelection(
|
||||
selection,
|
||||
(delta) => delta.whereType<TextInsert>().every(
|
||||
(element) => element.attributes?[key] == true,
|
||||
),
|
||||
) ??
|
||||
false;
|
||||
}
|
||||
|
||||
bool allCodeInSelection(Node? node, Selection selection) => allInSelection(
|
||||
node,
|
||||
selection,
|
||||
'code',
|
||||
);
|
||||
|
||||
bool allStrikethroughInSelection(Node? node, Selection selection) =>
|
||||
allInSelection(
|
||||
node,
|
||||
selection,
|
||||
'strikethrough',
|
||||
);
|
||||
|
||||
bool allBoldInSelection(Node? node, Selection selection) => allInSelection(
|
||||
node,
|
||||
selection,
|
||||
'bold',
|
||||
);
|
||||
|
||||
bool allItalicInSelection(Node? node, Selection selection) => allInSelection(
|
||||
node,
|
||||
selection,
|
||||
'bold',
|
||||
);
|
||||
|
||||
setUpAll(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
|
||||
group('markdown_syntax_to_styled_text.dart', () {
|
||||
Future<void> insertBackquote(
|
||||
TestableEditor editor, {
|
||||
int repeat = 1,
|
||||
}) async {
|
||||
for (var i = 0; i < repeat; i++) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.backquote,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
group('convert single backquote to code', () {
|
||||
testWidgets('`AppFlowy` to code AppFlowy', (tester) async {
|
||||
const text = '`AppFlowy';
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
|
||||
await editor.editorState.insertTextAtCurrentSelection(text);
|
||||
await insertBackquote(editor);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
|
||||
final allCode = allCodeInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: text.length - 1,
|
||||
),
|
||||
);
|
||||
|
||||
expect(allCode, true);
|
||||
expect(node?.delta?.toPlainText(), 'AppFlowy');
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets('App`Flowy` to code AppFlowy', (tester) async {
|
||||
const text = 'App`Flowy';
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
|
||||
await editor.editorState.insertTextAtCurrentSelection(text);
|
||||
|
||||
await insertBackquote(editor);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
|
||||
final allCode = allCodeInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 3,
|
||||
endOffset: text.length - 1,
|
||||
),
|
||||
);
|
||||
|
||||
expect(allCode, true);
|
||||
expect(node?.delta?.toPlainText(), 'AppFlowy');
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets('`` nothing changes', (tester) async {
|
||||
const text = '`';
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
await editor.editorState.insertTextAtCurrentSelection(text);
|
||||
await insertBackquote(editor);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
final allCode = allCodeInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: node!.delta!.toPlainText().length,
|
||||
),
|
||||
);
|
||||
|
||||
expect(allCode, false);
|
||||
expect(node.delta?.toPlainText(), '``');
|
||||
await editor.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
group('convert double backquote to code', () {
|
||||
testWidgets('```` nothing changes', (tester) async {
|
||||
const text = '```';
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
await editor.editorState.insertTextAtCurrentSelection(text);
|
||||
await insertBackquote(editor);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
final allCode = allCodeInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: node!.delta!.toPlainText().length,
|
||||
),
|
||||
);
|
||||
|
||||
expect(allCode, false);
|
||||
expect(node.delta?.toPlainText(), '````');
|
||||
await editor.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
group('convert double tilde to strikethrough', () {
|
||||
Future<void> insertTilde(
|
||||
TestableEditor editor, {
|
||||
int repeat = 1,
|
||||
}) async {
|
||||
for (var i = 0; i < repeat; i++) {
|
||||
await editor.pressKey(key: LogicalKeyboardKey.tilde);
|
||||
}
|
||||
}
|
||||
|
||||
testWidgets('~~AppFlowy~~ to strikethrough AppFlowy', (tester) async {
|
||||
const text = '~~AppFlowy~';
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
await editor.editorState.insertTextAtCurrentSelection(text);
|
||||
await insertTilde(editor);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
final result = allStrikethroughInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: node!.delta!.toPlainText().length,
|
||||
),
|
||||
);
|
||||
|
||||
expect(result, true);
|
||||
expect(node.delta?.toPlainText(), 'AppFlowy');
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets('App~~Flowy~~ to strikethrough AppFlowy', (tester) async {
|
||||
const text = 'App~~Flowy~';
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
await editor.editorState.insertTextAtCurrentSelection(text);
|
||||
await insertTilde(editor);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
final result = allStrikethroughInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 3,
|
||||
endOffset: node!.delta!.toPlainText().length,
|
||||
),
|
||||
);
|
||||
|
||||
expect(result, true);
|
||||
expect(node.delta?.toPlainText(), 'AppFlowy');
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets('~~~~ nothing changes', (tester) async {
|
||||
const text = '~~~';
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
await editor.editorState.insertTextAtCurrentSelection(text);
|
||||
await insertTilde(editor);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
final result = allStrikethroughInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: node!.delta!.toPlainText().length,
|
||||
),
|
||||
);
|
||||
|
||||
expect(result, false);
|
||||
expect(node.delta?.toPlainText(), '~~~~');
|
||||
await editor.dispose();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('convert double asterisk to bold', () {
|
||||
Future<void> insertAsterisk(
|
||||
TestableEditor editor, {
|
||||
int repeat = 1,
|
||||
}) async {
|
||||
for (var i = 0; i < repeat; i++) {
|
||||
await editor.pressKey(key: LogicalKeyboardKey.asterisk);
|
||||
}
|
||||
}
|
||||
|
||||
testWidgets(
|
||||
'**AppFlowy** to bold AppFlowy',
|
||||
(tester) async {
|
||||
const text = '**AppFlowy*';
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
await editor.editorState.insertTextAtCurrentSelection(text);
|
||||
await insertAsterisk(editor);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
final result = allBoldInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: node!.delta!.toPlainText().length,
|
||||
),
|
||||
);
|
||||
|
||||
expect(result, true);
|
||||
expect(node.delta?.toPlainText(), 'AppFlowy');
|
||||
await editor.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'App**Flowy** to bold AppFlowy',
|
||||
((tester) async {
|
||||
const text = 'App**Flowy*';
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
await editor.editorState.insertTextAtCurrentSelection(text);
|
||||
await insertAsterisk(editor);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
final result = allBoldInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 3,
|
||||
endOffset: node!.delta!.toPlainText().length,
|
||||
),
|
||||
);
|
||||
|
||||
expect(result, true);
|
||||
expect(node.delta?.toPlainText(), 'AppFlowy');
|
||||
await editor.dispose();
|
||||
}),
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'***AppFlowy** to bold *AppFlowy',
|
||||
((tester) async {
|
||||
const text = '***AppFlowy*';
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
await editor.editorState.insertTextAtCurrentSelection(text);
|
||||
await insertAsterisk(editor);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
final result = allBoldInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 1,
|
||||
endOffset: node!.delta!.toPlainText().length,
|
||||
),
|
||||
);
|
||||
|
||||
expect(result, true);
|
||||
expect(node.delta?.toPlainText(), '*AppFlowy');
|
||||
await editor.dispose();
|
||||
}),
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'**** nothing changes',
|
||||
((tester) async {
|
||||
const text = '***';
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
await editor.editorState.insertTextAtCurrentSelection(text);
|
||||
await insertAsterisk(editor);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
final result = allBoldInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 1,
|
||||
endOffset: node!.delta!.toPlainText().length,
|
||||
),
|
||||
);
|
||||
|
||||
expect(result, false);
|
||||
expect(node.delta?.toPlainText(), '****');
|
||||
await editor.dispose();
|
||||
}),
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'**bold and _nested_ italics**',
|
||||
(tester) async {
|
||||
const doubleAsterisks = '**';
|
||||
const singleAsterisk = '*';
|
||||
const firstBoldSegment = 'bold and ';
|
||||
const underscore = '_';
|
||||
const italicsSegment = 'nested';
|
||||
const secondBoldSegment = ' italics';
|
||||
const text = doubleAsterisks +
|
||||
firstBoldSegment +
|
||||
underscore +
|
||||
italicsSegment +
|
||||
underscore +
|
||||
secondBoldSegment +
|
||||
singleAsterisk;
|
||||
|
||||
final editor = tester.editor..addParagraph(initialText: '');
|
||||
await editor.startTesting();
|
||||
|
||||
await editor.updateSelection(Selection.collapsed(Position(path: [0])));
|
||||
for (final c in text.characters) {
|
||||
await editor.ime.insertText(c);
|
||||
}
|
||||
|
||||
await insertAsterisk(editor);
|
||||
|
||||
final node = editor.nodeAtPath([0]);
|
||||
final allTextBold = allBoldInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: text.length,
|
||||
),
|
||||
);
|
||||
|
||||
final segmentItalic = allItalicInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: firstBoldSegment.length,
|
||||
endOffset: firstBoldSegment.length + italicsSegment.length - 1,
|
||||
),
|
||||
);
|
||||
|
||||
final lastSegmentItalic = allItalicInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: firstBoldSegment.length + italicsSegment.length,
|
||||
endOffset: text.length,
|
||||
),
|
||||
);
|
||||
|
||||
const plainText = firstBoldSegment + italicsSegment + secondBoldSegment;
|
||||
final plainResult = node?.delta?.toPlainText();
|
||||
expect(allTextBold, true);
|
||||
expect(segmentItalic, true);
|
||||
expect(lastSegmentItalic, true);
|
||||
expect(plainText, plainResult);
|
||||
|
||||
await editor.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'regular then **bold and _nested_ italics**',
|
||||
(tester) async {
|
||||
const doubleAsterisks = '**';
|
||||
const singleAsterisk = '*';
|
||||
const regularSegment = 'regular then ';
|
||||
const firstBoldSegment = 'bold and ';
|
||||
const underscore = '_';
|
||||
const italicsSegment = 'nested';
|
||||
const secondBoldSegment = ' italics';
|
||||
const text = regularSegment +
|
||||
doubleAsterisks +
|
||||
firstBoldSegment +
|
||||
underscore +
|
||||
italicsSegment +
|
||||
underscore +
|
||||
secondBoldSegment +
|
||||
singleAsterisk;
|
||||
|
||||
final editor = tester.editor..addParagraph(initialText: '');
|
||||
await editor.startTesting();
|
||||
|
||||
await editor.updateSelection(Selection.collapsed(Position(path: [0])));
|
||||
for (final c in text.characters) {
|
||||
await editor.ime.insertText(c);
|
||||
}
|
||||
|
||||
await insertAsterisk(editor);
|
||||
|
||||
final node = editor.nodeAtPath([0]);
|
||||
final textInAsterisksIsBold = allBoldInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: regularSegment.length,
|
||||
endOffset: text.length,
|
||||
),
|
||||
);
|
||||
|
||||
final segmentItalic = allItalicInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: regularSegment.length + firstBoldSegment.length,
|
||||
endOffset: regularSegment.length +
|
||||
firstBoldSegment.length +
|
||||
italicsSegment.length -
|
||||
1,
|
||||
),
|
||||
);
|
||||
|
||||
final lastSegmentItalic = allItalicInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: text.length - secondBoldSegment.length,
|
||||
endOffset: text.length,
|
||||
),
|
||||
);
|
||||
const plainText = regularSegment +
|
||||
firstBoldSegment +
|
||||
italicsSegment +
|
||||
secondBoldSegment;
|
||||
final plainResult = node?.delta?.toPlainText();
|
||||
expect(textInAsterisksIsBold, true);
|
||||
expect(segmentItalic, true);
|
||||
expect(lastSegmentItalic, true);
|
||||
expect(plainText, plainResult);
|
||||
|
||||
await editor.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'**bold and _double_ _nested_ italics**',
|
||||
(tester) async {
|
||||
const doubleAsterisks = '**';
|
||||
const singleAsterisk = '*';
|
||||
const firstBoldSegment = 'bold and ';
|
||||
const firstItalicsSegment = 'double';
|
||||
const underscore = '_';
|
||||
const secondBoldSegment = ' ';
|
||||
const secondItalicsSegment = 'nested';
|
||||
const thirdBoldSegment = ' italics';
|
||||
const text = doubleAsterisks +
|
||||
firstBoldSegment +
|
||||
underscore +
|
||||
firstItalicsSegment +
|
||||
underscore +
|
||||
secondBoldSegment +
|
||||
underscore +
|
||||
secondItalicsSegment +
|
||||
underscore +
|
||||
thirdBoldSegment +
|
||||
singleAsterisk;
|
||||
|
||||
final editor = tester.editor..addParagraph(initialText: '');
|
||||
await editor.startTesting();
|
||||
|
||||
await editor.updateSelection(Selection.collapsed(Position(path: [0])));
|
||||
for (final c in text.characters) {
|
||||
await editor.ime.insertText(c);
|
||||
}
|
||||
|
||||
await insertAsterisk(editor);
|
||||
|
||||
final node = editor.nodeAtPath([0]);
|
||||
|
||||
final allTextBold = allBoldInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: text.length,
|
||||
),
|
||||
);
|
||||
|
||||
final firstSegmentItalic = allItalicInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: firstBoldSegment.length,
|
||||
endOffset: firstBoldSegment.length + firstItalicsSegment.length - 1,
|
||||
),
|
||||
);
|
||||
|
||||
const secondItalicsSegmentIndex = firstBoldSegment.length +
|
||||
firstItalicsSegment.length +
|
||||
secondBoldSegment.length;
|
||||
final secondSegmentItalic = allItalicInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: secondItalicsSegmentIndex,
|
||||
endOffset:
|
||||
secondItalicsSegmentIndex + secondItalicsSegment.length - 1,
|
||||
),
|
||||
);
|
||||
|
||||
const plainText = firstBoldSegment +
|
||||
firstItalicsSegment +
|
||||
secondBoldSegment +
|
||||
secondItalicsSegment +
|
||||
thirdBoldSegment;
|
||||
final plainResult = node?.delta?.toPlainText();
|
||||
expect(allTextBold, true);
|
||||
expect(firstSegmentItalic, true);
|
||||
expect(secondSegmentItalic, true);
|
||||
expect(plainText, plainResult);
|
||||
|
||||
await editor.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'**bold and _nested_ italics with a _ unescaped**',
|
||||
(tester) async {
|
||||
const doubleAsterisks = '**';
|
||||
const singleAsterisk = '*';
|
||||
const firstBoldSegment = 'bold and ';
|
||||
const underscore = '_';
|
||||
const italicsSegment = 'nested';
|
||||
const secondBoldSegment = ' italics with a _ unescaped';
|
||||
const text = doubleAsterisks +
|
||||
firstBoldSegment +
|
||||
underscore +
|
||||
italicsSegment +
|
||||
underscore +
|
||||
secondBoldSegment +
|
||||
singleAsterisk;
|
||||
final editor = tester.editor..addParagraph(initialText: '');
|
||||
await editor.startTesting();
|
||||
|
||||
await editor.updateSelection(Selection.collapsed(Position(path: [0])));
|
||||
for (final c in text.characters) {
|
||||
await editor.ime.insertText(c);
|
||||
}
|
||||
|
||||
await insertAsterisk(editor);
|
||||
|
||||
final node = editor.nodeAtPath([0]);
|
||||
|
||||
final allTextBold = allBoldInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: text.length,
|
||||
),
|
||||
);
|
||||
|
||||
final segmentItalic = allItalicInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: firstBoldSegment.length,
|
||||
endOffset: firstBoldSegment.length + italicsSegment.length - 1,
|
||||
),
|
||||
);
|
||||
|
||||
final lastSegmentItalic = allItalicInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: firstBoldSegment.length + italicsSegment.length,
|
||||
endOffset: text.length,
|
||||
),
|
||||
);
|
||||
|
||||
const plainText = firstBoldSegment + italicsSegment + secondBoldSegment;
|
||||
final plainResult = node?.delta?.toPlainText();
|
||||
expect(allTextBold, true);
|
||||
expect(segmentItalic, true);
|
||||
expect(lastSegmentItalic, true);
|
||||
expect(plainText, plainResult);
|
||||
|
||||
await editor.dispose();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('convert double underscore to bold', () {
|
||||
Future<void> insertUnderscore(
|
||||
TestableEditor editor, {
|
||||
int repeat = 1,
|
||||
}) async {
|
||||
for (var i = 0; i < repeat; i++) {
|
||||
await editor.pressKey(key: LogicalKeyboardKey.underscore);
|
||||
}
|
||||
}
|
||||
|
||||
testWidgets(
|
||||
'__AppFlowy__ to bold AppFlowy',
|
||||
((tester) async {
|
||||
const text = '__AppFlowy_';
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
await editor.editorState.insertTextAtCurrentSelection(text);
|
||||
await insertUnderscore(editor);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
|
||||
final result = allItalicInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: node!.delta!.toPlainText().length,
|
||||
),
|
||||
);
|
||||
|
||||
expect(result, true);
|
||||
expect(node.delta!.toPlainText(), 'AppFlowy');
|
||||
await editor.dispose();
|
||||
}),
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'App__Flowy__ to bold AppFlowy',
|
||||
((tester) async {
|
||||
const text = 'App__Flowy_';
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
await editor.editorState.insertTextAtCurrentSelection(text);
|
||||
await insertUnderscore(editor);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
|
||||
final result = allItalicInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 3,
|
||||
endOffset: node!.delta!.toPlainText().length,
|
||||
),
|
||||
);
|
||||
|
||||
expect(result, true);
|
||||
expect(node.delta!.toPlainText(), 'AppFlowy');
|
||||
await editor.dispose();
|
||||
}),
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'__*AppFlowy__ to bold *AppFlowy',
|
||||
((tester) async {
|
||||
const text = '__*AppFlowy_';
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
await editor.editorState.insertTextAtCurrentSelection(text);
|
||||
await insertUnderscore(editor);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
|
||||
final result = allItalicInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 1,
|
||||
endOffset: node!.delta!.toPlainText().length,
|
||||
),
|
||||
);
|
||||
|
||||
expect(result, true);
|
||||
expect(node.delta!.toPlainText(), '*AppFlowy');
|
||||
await editor.dispose();
|
||||
}),
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'____ nothing changes',
|
||||
((tester) async {
|
||||
const text = '___';
|
||||
final editor = tester.editor..addEmptyParagraph();
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
await editor.editorState.insertTextAtCurrentSelection(text);
|
||||
await insertUnderscore(editor);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
|
||||
final result = allItalicInSelection(
|
||||
node,
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 1,
|
||||
endOffset: node!.delta!.toPlainText().length,
|
||||
),
|
||||
);
|
||||
|
||||
expect(result, false);
|
||||
expect(node.delta!.toPlainText(), '____');
|
||||
await editor.dispose();
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../../infra/testable_editor.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
|
||||
group('outdent_handler.dart', () {
|
||||
testWidgets("press shift tab in plain text", (tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraphs(3, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
final snapshotDocument = editor.document;
|
||||
|
||||
await editor.updateSelection(Selection.single(path: [0], startOffset: 0));
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.tab,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
// nothing happens
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
expect(editor.document.toJson(), snapshotDocument.toJson());
|
||||
});
|
||||
|
||||
testWidgets("press shift tab where previous element is not list element",
|
||||
(tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor
|
||||
..addParagraph(initialText: text)
|
||||
..addNode(bulletedListNode(delta: Delta()..insert(text)))
|
||||
..addNode(bulletedListNode(delta: Delta()..insert(text)));
|
||||
await editor.startTesting();
|
||||
|
||||
final snapshotDocument = editor.document;
|
||||
|
||||
final selection = Selection.single(path: [1], startOffset: 0);
|
||||
await editor.updateSelection(selection);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.tab,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
// nothing happens
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.single(path: [1], startOffset: 0),
|
||||
);
|
||||
expect(editor.document.toJson(), snapshotDocument.toJson());
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
"press shift tab in indented list with multiple nodes in same sub-level",
|
||||
(tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor
|
||||
..addNode(todoListNode(checked: false, delta: Delta()..insert(text)))
|
||||
..addNode(todoListNode(checked: false, delta: Delta()..insert(text)))
|
||||
..addNode(todoListNode(checked: false, delta: Delta()..insert(text)));
|
||||
|
||||
await editor.startTesting();
|
||||
|
||||
final selection = Selection.collapsed(Position(path: [1]));
|
||||
await editor.updateSelection(selection);
|
||||
await editor.pressKey(key: LogicalKeyboardKey.tab);
|
||||
|
||||
await editor.updateSelection(selection);
|
||||
await editor.pressKey(key: LogicalKeyboardKey.tab);
|
||||
|
||||
// Before
|
||||
// [] Welcome to Appflowy 😁
|
||||
// [] Welcome to Appflowy 😁
|
||||
// [] Welcome to Appflowy 😁
|
||||
// After
|
||||
// [] Welcome to Appflowy 😁
|
||||
// [] Welcome to Appflowy 😁
|
||||
// [] Welcome to Appflowy 😁
|
||||
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.collapsed(Position(path: [0, 1])),
|
||||
);
|
||||
expect(
|
||||
editor.nodeAtPath([0])!.type,
|
||||
'todo_list',
|
||||
);
|
||||
expect(editor.nodeAtPath([1]), null);
|
||||
expect(editor.nodeAtPath([2]), null);
|
||||
expect(
|
||||
editor.nodeAtPath([0, 0])!.type,
|
||||
'todo_list',
|
||||
);
|
||||
expect(
|
||||
editor.nodeAtPath([0, 1])!.type,
|
||||
'todo_list',
|
||||
);
|
||||
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0, 1], startOffset: 0),
|
||||
);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.tab,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
|
||||
// Before
|
||||
// [] Welcome to Appflowy 😁
|
||||
// [] Welcome to Appflowy 😁
|
||||
// [] Welcome to Appflowy 😁
|
||||
// After
|
||||
// [] Welcome to Appflowy 😁
|
||||
// [] Welcome to Appflowy 😁
|
||||
// [] Welcome to Appflowy 😁
|
||||
|
||||
expect(
|
||||
editor.nodeAtPath([1])!.type,
|
||||
'todo_list',
|
||||
);
|
||||
expect(
|
||||
editor.nodeAtPath([0, 0])!.type,
|
||||
'todo_list',
|
||||
);
|
||||
expect(editor.nodeAtPath([0, 1]), null);
|
||||
|
||||
await editor.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
"press shift tab in indented list with only one node in same sub-level",
|
||||
(tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor
|
||||
..addNode(bulletedListNode(delta: Delta()..insert(text)))
|
||||
..addNode(bulletedListNode(delta: Delta()..insert(text)))
|
||||
..addNode(bulletedListNode(delta: Delta()..insert(text)));
|
||||
|
||||
await editor.startTesting();
|
||||
|
||||
var selection = Selection.single(path: [1], startOffset: 0);
|
||||
await editor.updateSelection(selection);
|
||||
|
||||
await editor.pressKey(key: LogicalKeyboardKey.tab);
|
||||
|
||||
// Before
|
||||
// * Welcome to Appflowy 😁
|
||||
// * Welcome to Appflowy 😁
|
||||
// * Welcome to Appflowy 😁
|
||||
// After
|
||||
// * Welcome to Appflowy 😁
|
||||
// * Welcome to Appflowy 😁
|
||||
// * Welcome to Appflowy 😁
|
||||
|
||||
expect(
|
||||
editor.selection,
|
||||
Selection.single(path: [0, 0], startOffset: 0),
|
||||
);
|
||||
expect(
|
||||
editor.nodeAtPath([0])!.type,
|
||||
'bulleted_list',
|
||||
);
|
||||
expect(
|
||||
editor.nodeAtPath([0, 0])!.type,
|
||||
'bulleted_list',
|
||||
);
|
||||
expect(
|
||||
editor.nodeAtPath([1])!.type,
|
||||
'bulleted_list',
|
||||
);
|
||||
expect(editor.nodeAtPath([2]), null);
|
||||
|
||||
await editor
|
||||
.updateSelection(Selection.single(path: [0, 0], startOffset: 0));
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.tab,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
|
||||
// Before
|
||||
// * Welcome to Appflowy 😁
|
||||
// * Welcome to Appflowy 😁
|
||||
// * Welcome to Appflowy 😁
|
||||
// After
|
||||
// * Welcome to Appflowy 😁
|
||||
// * Welcome to Appflowy 😁
|
||||
// * Welcome to Appflowy 😁
|
||||
|
||||
expect(
|
||||
editor.nodeAtPath([0])!.type,
|
||||
'bulleted_list',
|
||||
);
|
||||
expect(
|
||||
editor.nodeAtPath([1])!.type,
|
||||
'bulleted_list',
|
||||
);
|
||||
expect(
|
||||
editor.nodeAtPath([2])!.type,
|
||||
'bulleted_list',
|
||||
);
|
||||
expect(editor.nodeAtPath([0, 0]), null);
|
||||
|
||||
await editor.dispose();
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../../infra/testable_editor.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
|
||||
group('page_up_down_handler_test.dart', () {
|
||||
testWidgets('Presses PageUp and pageDown key in large document',
|
||||
(tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraphs(1000, initialText: text);
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
|
||||
final scrollService = editor.editorState.service.scrollService!;
|
||||
final page = scrollService.page!;
|
||||
final onePageHeight = scrollService.onePageHeight!;
|
||||
|
||||
// Pressing the pageDown key continuously.
|
||||
var currentOffsetY = 0.0;
|
||||
for (int i = 1; i <= page; i++) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.pageDown,
|
||||
);
|
||||
if (i == page) {
|
||||
currentOffsetY = scrollService.maxScrollExtent;
|
||||
} else {
|
||||
currentOffsetY += onePageHeight;
|
||||
}
|
||||
final dy = scrollService.dy;
|
||||
expect(dy, currentOffsetY);
|
||||
}
|
||||
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.pageDown,
|
||||
);
|
||||
final dy = scrollService.dy;
|
||||
expect(dy == scrollService.maxScrollExtent, true);
|
||||
}
|
||||
|
||||
// Pressing the pageUp key continuously.
|
||||
for (int i = page; i >= 1; i--) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.pageUp,
|
||||
);
|
||||
if (i == 1) {
|
||||
currentOffsetY = scrollService.minScrollExtent;
|
||||
} else {
|
||||
currentOffsetY -= onePageHeight;
|
||||
}
|
||||
final dy = scrollService.dy;
|
||||
expect(dy, currentOffsetY);
|
||||
}
|
||||
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.pageUp,
|
||||
);
|
||||
final dy = scrollService.dy;
|
||||
expect(dy == scrollService.minScrollExtent, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -4,133 +4,267 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../../../infra/clipboard_test.dart';
|
||||
import '../../../infra/testable_editor.dart';
|
||||
|
||||
const text = 'Welcome to AppFlowy Editor 🔥!';
|
||||
|
||||
void main() async {
|
||||
late MockClipboard mockClipboard;
|
||||
|
||||
setUp(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
mockClipboard = const MockClipboard(html: null, text: null);
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.platform, (message) async {
|
||||
switch (message.method) {
|
||||
case "Clipboard.getData":
|
||||
return mockClipboard.getData;
|
||||
case "Clipboard.setData":
|
||||
final args = message.arguments as Map<String, dynamic>;
|
||||
mockClipboard = mockClipboard.copyWith(
|
||||
text: args['text'],
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
});
|
||||
|
||||
group('paste_command_test.dart paste_plaintext', () {
|
||||
testWidgets('works with formatted text', (tester) async {
|
||||
final editor = tester.editor..addParagraph(initialText: text);
|
||||
await editor.startTesting();
|
||||
group('paste_command_test.dart', () {
|
||||
testWidgets(
|
||||
'Copy link',
|
||||
(tester) async {
|
||||
final editor = tester.editor..addParagraph(initialText: '');
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.collapsed(Position(path: [0], offset: 0)),
|
||||
);
|
||||
|
||||
await _applyFormatting(
|
||||
editor,
|
||||
BuiltInAttributeKey.underline,
|
||||
LogicalKeyboardKey.keyU,
|
||||
const link = 'https://appflowy.io/';
|
||||
AppFlowyClipboard.mockSetData(
|
||||
const AppFlowyClipboardData(text: link),
|
||||
);
|
||||
|
||||
pasteCommand.execute(editor.editorState);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final delta = editor.nodeAtPath([0])!.delta!;
|
||||
expect(delta.toPlainText(), link);
|
||||
expect(
|
||||
delta.everyAttributes(
|
||||
(element) => element[AppFlowyRichTextKeys.href] == link,
|
||||
),
|
||||
true,
|
||||
);
|
||||
|
||||
AppFlowyClipboard.mockSetData(null);
|
||||
await editor.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Presses Command + A in small document and copy text and paste text',
|
||||
(tester) async {
|
||||
await _testHandleCopyPaste(tester, Document.fromJson(paragraphData));
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Presses Command + A in small document and copy text and paste text multiple times',
|
||||
(tester) async {
|
||||
await _testHandleCopyMultiplePaste(
|
||||
tester,
|
||||
Document.fromJson(paragraphData),
|
||||
);
|
||||
|
||||
await _applyFormatting(
|
||||
editor,
|
||||
BuiltInAttributeKey.italic,
|
||||
LogicalKeyboardKey.keyI,
|
||||
);
|
||||
|
||||
await _applyFormatting(
|
||||
editor,
|
||||
BuiltInAttributeKey.bold,
|
||||
LogicalKeyboardKey.keyB,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final selection = Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: 7,
|
||||
);
|
||||
|
||||
await editor.updateSelection(selection);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyC,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
AppFlowyClipboard.mockSetData(
|
||||
AppFlowyClipboardData(
|
||||
text: editor.editorState.getTextInSelection(selection).join('\n'),
|
||||
),
|
||||
);
|
||||
|
||||
await editor.updateSelection(selection);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyV,
|
||||
isShiftPressed: true,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await editor.updateSelection(selection);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
|
||||
_checkSelectionNotFormatted(
|
||||
node!,
|
||||
selection,
|
||||
BuiltInAttributeKey.bold,
|
||||
);
|
||||
|
||||
_checkSelectionNotFormatted(
|
||||
node,
|
||||
selection,
|
||||
BuiltInAttributeKey.underline,
|
||||
);
|
||||
|
||||
_checkSelectionNotFormatted(
|
||||
node,
|
||||
selection,
|
||||
BuiltInAttributeKey.italic,
|
||||
);
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _applyFormatting(
|
||||
TestableEditor editor,
|
||||
String matchStyle,
|
||||
LogicalKeyboardKey key,
|
||||
Future<void> _testHandleCopyMultiplePaste(
|
||||
WidgetTester tester,
|
||||
Document document,
|
||||
) async {
|
||||
final selection = Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: 7,
|
||||
);
|
||||
await editor.updateSelection(selection);
|
||||
final editor = tester.editor..initializeWithDocument(document);
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(Selection.collapsed(Position(path: [0])));
|
||||
await editor.pressKey(
|
||||
key: key,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
key: LogicalKeyboardKey.keyA,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
handleCopy(editor.editorState);
|
||||
deleteSelectedContent(editor.editorState);
|
||||
|
||||
expect(
|
||||
node!.allSatisfyInSelection(selection, (delta) {
|
||||
return delta.whereType<TextInsert>().every(
|
||||
(el) => el.attributes?[matchStyle] == true,
|
||||
);
|
||||
}),
|
||||
true,
|
||||
pasteHTML(
|
||||
editor.editorState,
|
||||
documentToHTML(Document.fromJson(paragraphData)),
|
||||
);
|
||||
expect(
|
||||
editor.editorState.document.toJson(),
|
||||
paragraphData,
|
||||
);
|
||||
await editor.updateSelection(Selection.single(path: [0], startOffset: 10));
|
||||
pasteHTML(
|
||||
editor.editorState,
|
||||
documentToHTML(Document.fromJson(paragraphData)),
|
||||
);
|
||||
expect(
|
||||
editor.document.toJson(),
|
||||
secondParagraph,
|
||||
);
|
||||
pasteHTML(
|
||||
editor.editorState,
|
||||
documentToHTML(Document.fromJson(paragraphData)),
|
||||
);
|
||||
expect(
|
||||
editor.document.toJson(),
|
||||
thirdParagraph,
|
||||
);
|
||||
await editor.dispose();
|
||||
}
|
||||
|
||||
void _checkSelectionNotFormatted(
|
||||
Node node,
|
||||
Selection selection,
|
||||
String matchStyle,
|
||||
) {
|
||||
expect(
|
||||
node.allSatisfyInSelection(selection, (delta) {
|
||||
return delta.whereType<TextInsert>().every(
|
||||
(el) => el.attributes?[matchStyle] != true,
|
||||
);
|
||||
}),
|
||||
true,
|
||||
Future<void> _testHandleCopyPaste(
|
||||
WidgetTester tester,
|
||||
Document document,
|
||||
) async {
|
||||
final editor = tester.editor..initializeWithDocument(document);
|
||||
await editor.startTesting(platform: TargetPlatform.windows);
|
||||
await editor.updateSelection(Selection.collapsed(Position(path: [0])));
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyA,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
handleCopy(editor.editorState);
|
||||
deleteSelectedContent(editor.editorState);
|
||||
await editor.updateSelection(Selection.collapsed(Position(path: [0])));
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyP,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
final clipBoardData = await AppFlowyClipboard.getData();
|
||||
handlePastePlainText(editor.editorState, clipBoardData.text!);
|
||||
expect(editor.document.toJson(), plainTextJson);
|
||||
|
||||
await editor.dispose();
|
||||
}
|
||||
|
||||
const paragraphData = {
|
||||
"document": {
|
||||
"type": "page",
|
||||
"children": [
|
||||
{
|
||||
'type': 'paragraph',
|
||||
'data': {
|
||||
'delta': [
|
||||
{'insert': 'AppFlowy Editor is a '},
|
||||
{
|
||||
'insert': 'highly customizable',
|
||||
'attributes': {'bold': true}
|
||||
},
|
||||
{'insert': ' '},
|
||||
{
|
||||
'insert': 'rich-text editor',
|
||||
'attributes': {'italic': true}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
const secondParagraph = {
|
||||
"document": {
|
||||
"type": "page",
|
||||
"children": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{"insert": "AppFlowy Editor is a "},
|
||||
{
|
||||
"insert": "highly customizable",
|
||||
"attributes": {"bold": true}
|
||||
},
|
||||
{"insert": " "},
|
||||
{
|
||||
"insert": "rich-text editor",
|
||||
"attributes": {"italic": true}
|
||||
},
|
||||
{"insert": "AppFlowy Editor is a "},
|
||||
{
|
||||
"insert": "highly customizable",
|
||||
"attributes": {"bold": true}
|
||||
},
|
||||
{"insert": " "},
|
||||
{
|
||||
"insert": "rich-text editor",
|
||||
"attributes": {"italic": true}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
const plainTextJson = {
|
||||
"document": {
|
||||
"type": "page",
|
||||
"children": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{
|
||||
"insert":
|
||||
"AppFlowy Editor is a highly customizable rich-text editor"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
const thirdParagraph = {
|
||||
"document": {
|
||||
"type": "page",
|
||||
"children": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"data": {
|
||||
"delta": [
|
||||
{"insert": "AppFlowy Editor is a "},
|
||||
{
|
||||
"insert": "highly customizable",
|
||||
"attributes": {"bold": true}
|
||||
},
|
||||
{"insert": " "},
|
||||
{
|
||||
"insert": "rich-text editor",
|
||||
"attributes": {"italic": true}
|
||||
},
|
||||
{"insert": "AppFlowy Editor is a "},
|
||||
{
|
||||
"insert": "highly customizable",
|
||||
"attributes": {"bold": true}
|
||||
},
|
||||
{"insert": " "},
|
||||
{
|
||||
"insert": "rich-text editor",
|
||||
"attributes": {"italic": true}
|
||||
},
|
||||
{"insert": "AppFlowy Editor is a "},
|
||||
{
|
||||
"insert": "highly customizable",
|
||||
"attributes": {"bold": true}
|
||||
},
|
||||
{"insert": " "},
|
||||
{
|
||||
"insert": "rich-text editor",
|
||||
"attributes": {"italic": true}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../../infra/testable_editor.dart';
|
||||
|
||||
const text = 'Welcome to AppFlowy Editor 🔥!';
|
||||
|
||||
void main() async {
|
||||
setUp(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
|
||||
group('paste_plaintext_command', () {
|
||||
testWidgets('works with formatted text', (tester) async {
|
||||
final editor = tester.editor..addParagraph(initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
await _applyFormatting(
|
||||
editor,
|
||||
BuiltInAttributeKey.underline,
|
||||
LogicalKeyboardKey.keyU,
|
||||
);
|
||||
|
||||
await _applyFormatting(
|
||||
editor,
|
||||
BuiltInAttributeKey.italic,
|
||||
LogicalKeyboardKey.keyI,
|
||||
);
|
||||
|
||||
await _applyFormatting(
|
||||
editor,
|
||||
BuiltInAttributeKey.bold,
|
||||
LogicalKeyboardKey.keyB,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final selection = Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: 7,
|
||||
);
|
||||
|
||||
await editor.updateSelection(selection);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyC,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
AppFlowyClipboard.mockSetData(
|
||||
AppFlowyClipboardData(
|
||||
text: editor.editorState.getTextInSelection(selection).join('\n'),
|
||||
),
|
||||
);
|
||||
|
||||
await editor.updateSelection(selection);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyV,
|
||||
isShiftPressed: true,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await editor.updateSelection(selection);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
|
||||
_checkSelectionNotFormatted(
|
||||
node!,
|
||||
selection,
|
||||
BuiltInAttributeKey.bold,
|
||||
);
|
||||
|
||||
_checkSelectionNotFormatted(
|
||||
node,
|
||||
selection,
|
||||
BuiltInAttributeKey.underline,
|
||||
);
|
||||
|
||||
_checkSelectionNotFormatted(
|
||||
node,
|
||||
selection,
|
||||
BuiltInAttributeKey.italic,
|
||||
);
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _applyFormatting(
|
||||
TestableEditor editor,
|
||||
String matchStyle,
|
||||
LogicalKeyboardKey key,
|
||||
) async {
|
||||
final selection = Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: 7,
|
||||
);
|
||||
await editor.updateSelection(selection);
|
||||
await editor.pressKey(
|
||||
key: key,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
final node = editor.nodeAtPath([0]);
|
||||
|
||||
expect(
|
||||
node!.allSatisfyInSelection(selection, (delta) {
|
||||
return delta.whereType<TextInsert>().every(
|
||||
(el) => el.attributes?[matchStyle] == true,
|
||||
);
|
||||
}),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
void _checkSelectionNotFormatted(
|
||||
Node node,
|
||||
Selection selection,
|
||||
String matchStyle,
|
||||
) {
|
||||
expect(
|
||||
node.allSatisfyInSelection(selection, (delta) {
|
||||
return delta.whereType<TextInsert>().every(
|
||||
(el) => el.attributes?[matchStyle] != true,
|
||||
);
|
||||
}),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../../infra/testable_editor.dart';
|
||||
import '../../../util/util.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
|
||||
group('redo_undo_handler_test.dart', () {
|
||||
// TODO: need to test more cases.
|
||||
|
||||
testWidgets('Redo should do nothing if Undo is not yet performed',
|
||||
(tester) async {
|
||||
await _testRedoWithoutUndo(tester);
|
||||
});
|
||||
|
||||
testWidgets('Undo and Redo works properly with text formatting bold',
|
||||
(tester) async {
|
||||
await _testWithTextFormattingBold(tester);
|
||||
});
|
||||
|
||||
testWidgets('Undo and Redo works properly with text formatting italics',
|
||||
(tester) async {
|
||||
await _testWithTextFormattingItalics(tester);
|
||||
});
|
||||
|
||||
testWidgets('Undo and Redo works properly with text formatting underline',
|
||||
(tester) async {
|
||||
await _testWithTextFormattingUnderline(tester);
|
||||
});
|
||||
|
||||
testWidgets('Redo, Undo for backspace key, and selection is downward',
|
||||
(tester) async {
|
||||
await _testBackspaceUndoRedo(tester, true);
|
||||
});
|
||||
|
||||
testWidgets('Redo, Undo for backspace key, and selection is forward',
|
||||
(tester) async {
|
||||
await _testBackspaceUndoRedo(tester, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _testRedoWithoutUndo(WidgetTester tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraphs(3, initialText: text);
|
||||
await editor.startTesting();
|
||||
final selection = Selection.single(path: [1], startOffset: 0);
|
||||
await editor.updateSelection(selection);
|
||||
|
||||
expect(editor.documentRootLen, 3);
|
||||
|
||||
await _pressRedoCommand(editor);
|
||||
|
||||
expect(editor.documentRootLen, 3);
|
||||
|
||||
await editor.dispose();
|
||||
}
|
||||
|
||||
Future<void> _testWithTextFormattingBold(WidgetTester tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraphs(3, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
final node = editor.nodeAtPath([0])!;
|
||||
var selection = Selection.single(
|
||||
path: [0],
|
||||
startOffset: 1,
|
||||
endOffset: text.length,
|
||||
);
|
||||
var result = node.allBold(selection);
|
||||
expect(node.delta!.toPlainText(), text);
|
||||
expect(result, false);
|
||||
|
||||
final start = Position(path: [0], offset: 0);
|
||||
final end = Position(path: [0], offset: text.length);
|
||||
selection = Selection(
|
||||
start: start,
|
||||
end: end,
|
||||
);
|
||||
|
||||
await editor.updateSelection(selection);
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyB,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
|
||||
selection = Selection.single(
|
||||
path: [0],
|
||||
startOffset: 1,
|
||||
endOffset: text.length,
|
||||
);
|
||||
|
||||
result = node.allBold(selection);
|
||||
expect(result, true);
|
||||
|
||||
//undo should remove bold style and make it normal.
|
||||
await _pressUndoCommand(editor);
|
||||
|
||||
result = node.allBold(selection);
|
||||
expect(result, false);
|
||||
|
||||
//redo should make text bold.
|
||||
await _pressRedoCommand(editor);
|
||||
|
||||
result = node.allBold(selection);
|
||||
expect(result, true);
|
||||
|
||||
await editor.dispose();
|
||||
}
|
||||
|
||||
Future<void> _testWithTextFormattingItalics(WidgetTester tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraphs(3, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
final node = editor.nodeAtPath([0])!;
|
||||
var allItalics = node.allItalic(
|
||||
Selection.single(path: [0], startOffset: 1, endOffset: text.length),
|
||||
);
|
||||
|
||||
expect(node.delta!.toPlainText(), text);
|
||||
expect(allItalics, false);
|
||||
|
||||
final start = Position(path: [0], offset: 0);
|
||||
final end = Position(path: [0], offset: text.length);
|
||||
final selection = Selection(
|
||||
start: start,
|
||||
end: end,
|
||||
);
|
||||
|
||||
await editor.updateSelection(selection);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyI,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
allItalics = node.allItalic(
|
||||
Selection.single(path: [0], startOffset: 1, endOffset: text.length),
|
||||
);
|
||||
expect(allItalics, true);
|
||||
|
||||
//undo should remove italic style and make it normal.
|
||||
await _pressUndoCommand(editor);
|
||||
|
||||
allItalics = node.allItalic(
|
||||
Selection.single(path: [0], startOffset: 1, endOffset: text.length),
|
||||
);
|
||||
expect(allItalics, false);
|
||||
|
||||
//redo should make text italic again.
|
||||
await _pressRedoCommand(editor);
|
||||
|
||||
allItalics = node.allItalic(
|
||||
Selection.single(path: [0], startOffset: 1, endOffset: text.length),
|
||||
);
|
||||
expect(allItalics, true);
|
||||
|
||||
await editor.dispose();
|
||||
}
|
||||
|
||||
Future<void> _testWithTextFormattingUnderline(WidgetTester tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraphs(3, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
final node = editor.nodeAtPath([0])!;
|
||||
var allUnderline = node.allUnderline(
|
||||
Selection.single(path: [0], startOffset: 1, endOffset: text.length),
|
||||
);
|
||||
|
||||
expect(node.delta!.toPlainText(), text);
|
||||
expect(allUnderline, false);
|
||||
|
||||
final start = Position(path: [0], offset: 0);
|
||||
final end = Position(path: [0], offset: text.length);
|
||||
final selection = Selection(
|
||||
start: start,
|
||||
end: end,
|
||||
);
|
||||
|
||||
await editor.updateSelection(selection);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyU,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
allUnderline = node.allUnderline(
|
||||
Selection.single(path: [0], startOffset: 1, endOffset: text.length),
|
||||
);
|
||||
expect(allUnderline, true);
|
||||
|
||||
//undo should remove underline style and make it normal.
|
||||
await _pressUndoCommand(editor);
|
||||
|
||||
allUnderline = node.allUnderline(
|
||||
Selection.single(path: [0], startOffset: 1, endOffset: text.length),
|
||||
);
|
||||
expect(allUnderline, false);
|
||||
|
||||
//redo should make text underline.
|
||||
await _pressRedoCommand(editor);
|
||||
|
||||
allUnderline = node.allUnderline(
|
||||
Selection.single(path: [0], startOffset: 1, endOffset: text.length),
|
||||
);
|
||||
expect(allUnderline, true);
|
||||
|
||||
await editor.dispose();
|
||||
}
|
||||
|
||||
Future<void> _testBackspaceUndoRedo(
|
||||
WidgetTester tester,
|
||||
bool isDownwardSelection,
|
||||
) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraphs(3, initialText: text);
|
||||
await editor.startTesting();
|
||||
|
||||
final start = Position(path: [0], offset: text.length);
|
||||
final end = Position(path: [1], offset: text.length);
|
||||
final selection = Selection(
|
||||
start: isDownwardSelection ? start : end,
|
||||
end: isDownwardSelection ? end : start,
|
||||
);
|
||||
await editor.updateSelection(selection);
|
||||
await editor.pressKey(key: LogicalKeyboardKey.backspace);
|
||||
expect(editor.documentRootLen, 2);
|
||||
|
||||
await _pressUndoCommand(editor);
|
||||
|
||||
expect(editor.documentRootLen, 3);
|
||||
expect(editor.nodeAtPath([1])!.delta!.toPlainText(), text);
|
||||
expect(editor.selection, selection);
|
||||
|
||||
await _pressRedoCommand(editor);
|
||||
|
||||
expect(editor.documentRootLen, 2);
|
||||
|
||||
await editor.dispose();
|
||||
}
|
||||
|
||||
Future<void> _pressUndoCommand(TestableEditor editor) async {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.keyZ,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _pressRedoCommand(TestableEditor editor) async {
|
||||
await editor.pressKey(
|
||||
key: Platform.isMacOS ? LogicalKeyboardKey.keyZ : LogicalKeyboardKey.keyY,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
isShiftPressed: Platform.isMacOS,
|
||||
isControlPressed: !Platform.isMacOS,
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../../infra/testable_editor.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
});
|
||||
|
||||
group('remove_line_command.dart ', () {
|
||||
testWidgets('works properly and deletes a sentence', (tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraphs(3, initialText: text);
|
||||
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: text.length),
|
||||
);
|
||||
//Welcome to Appflowy 😁|
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.backspace,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isAltPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
//fetching all the text that is still on the editor.
|
||||
final selection = editor.selection!;
|
||||
final node = editor.editorState.getNodeAtPath(selection.end.path)!;
|
||||
|
||||
expect(node.delta!.toPlainText().isEmpty, true);
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets('works in the middle of a word', (tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraph(initialText: text);
|
||||
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 14),
|
||||
);
|
||||
//Welcome to App|flowy 😁
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.backspace,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isAltPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
//fetching all the text that is still on the editor.
|
||||
final selection = editor.selection!;
|
||||
final node = editor.editorState.getNodeAtPath(selection.end.path)!;
|
||||
|
||||
const expectedText = 'flowy 😁';
|
||||
expect(node.delta!.toPlainText(), expectedText);
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ void main() async {
|
|||
);
|
||||
await editor.startTesting();
|
||||
|
||||
// Welcome| to AppFlowy Editor 🔥!
|
||||
// Welcome |to AppFlowy Editor 🔥!
|
||||
const welcome = 'Welcome';
|
||||
final selection = Selection.collapsed(
|
||||
Position(path: [0], offset: welcome.length + 1),
|
||||
|
|
@ -123,6 +123,133 @@ void main() async {
|
|||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets('repeatedly till line is empty', (tester) async {
|
||||
List<String> words = ["Welcome", " ", "to", " ", "Appflowy", " ", "😁"];
|
||||
final text = words.join();
|
||||
final editor = tester.editor..addParagraph(initialText: text);
|
||||
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: text.length),
|
||||
);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.backspace,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
//fetching all the text that is still on the editor.
|
||||
final selection = editor.selection!;
|
||||
assert(selection.isSingle, true);
|
||||
var node = editor.nodeAtPath(selection.end.path)!;
|
||||
|
||||
words.removeLast();
|
||||
//expected: Welcome_to_Appflowy_
|
||||
//here _ actually represents ' '
|
||||
expect(node.delta!.toPlainText(), words.join());
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.backspace,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
//fetching all the text that is still on the editor.
|
||||
node = editor.nodeAtPath(selection.end.path)!;
|
||||
|
||||
//removes the whitespace
|
||||
words.removeLast();
|
||||
words.removeLast();
|
||||
//expected is: Welcome_to_
|
||||
expect(node.delta!.toPlainText(), words.join());
|
||||
|
||||
//we divide words.length by 2 becuase we know half words are whitespaces.
|
||||
for (var i = 0; i < words.length / 2; i++) {
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.backspace,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
);
|
||||
}
|
||||
|
||||
node = editor.nodeAtPath(selection.end.path)!;
|
||||
|
||||
expect(node.delta!.toPlainText(), '');
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets('at the middle of a word', (tester) async {
|
||||
const text = 'Welcome to Appflowy 😁';
|
||||
final editor = tester.editor..addParagraph(initialText: text);
|
||||
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.backspace,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
//fetching all the text that is still on the editor.
|
||||
final selection = editor.selection!;
|
||||
var node = editor.editorState.getNodeAtPath(selection.end.path)!;
|
||||
|
||||
//nothing happens when there is no words to the left of the cursor
|
||||
expect(node.delta!.toPlainText(), text);
|
||||
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 14),
|
||||
);
|
||||
//Welcome to App|flowy 😁
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.backspace,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
//fetching all the text that is still on the editor.
|
||||
node = editor.editorState.getNodeAtPath(selection.end.path)!;
|
||||
|
||||
const expectedText = 'Welcome to flowy 😁';
|
||||
expect(node.delta!.toPlainText(), expectedText);
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
|
||||
testWidgets('works properly with only single whitespace', (tester) async {
|
||||
//edge case that checks if pressing ctrl+backspace on null value
|
||||
//after removing a whitespace, does not throw an exception.
|
||||
const text = ' ';
|
||||
final editor = tester.editor..addParagraph(initialText: text);
|
||||
|
||||
await editor.startTesting();
|
||||
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: text.length),
|
||||
);
|
||||
// |
|
||||
|
||||
await editor.pressKey(
|
||||
key: LogicalKeyboardKey.backspace,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isAltPressed: Platform.isMacOS,
|
||||
);
|
||||
|
||||
//fetching all the text that is still on the editor.
|
||||
final selection = editor.selection!;
|
||||
final node = editor.editorState.getNodeAtPath(selection.end.path)!;
|
||||
|
||||
expect(node.delta!.toPlainText().isEmpty, true);
|
||||
|
||||
await editor.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
group('remove the right word ', () {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue