mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #844 from LucasXu0/feat/widget_test
feat: implement editor test infra
This commit is contained in:
commit
fb8234b399
6
.github/workflows/dart_test.yml
vendored
6
.github/workflows/dart_test.yml
vendored
@ -78,9 +78,3 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
flutter pub get
|
flutter pub get
|
||||||
flutter test
|
flutter test
|
||||||
|
|
||||||
- name: Run FlowyEditor tests
|
|
||||||
working-directory: frontend/app_flowy/packages/flowy_editor
|
|
||||||
run: |
|
|
||||||
flutter pub get
|
|
||||||
flutter test
|
|
||||||
|
36
.github/workflows/flowy_editor_test.yml
vendored
Normal file
36
.github/workflows/flowy_editor_test.yml
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
name: FlowyEditor test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "main"
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "main"
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: "stable"
|
||||||
|
flutter-version: "3.0.5"
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Run FlowyEditor tests
|
||||||
|
working-directory: frontend/app_flowy/packages/flowy_editor
|
||||||
|
run: |
|
||||||
|
flutter pub get
|
||||||
|
flutter test
|
@ -6,6 +6,7 @@ export 'src/document/position.dart';
|
|||||||
export 'src/document/selection.dart';
|
export 'src/document/selection.dart';
|
||||||
export 'src/document/state_tree.dart';
|
export 'src/document/state_tree.dart';
|
||||||
export 'src/document/text_delta.dart';
|
export 'src/document/text_delta.dart';
|
||||||
|
export 'src/document/attributes.dart';
|
||||||
export 'src/editor_state.dart';
|
export 'src/editor_state.dart';
|
||||||
export 'src/operation/operation.dart';
|
export 'src/operation/operation.dart';
|
||||||
export 'src/operation/transaction.dart';
|
export 'src/operation/transaction.dart';
|
||||||
|
@ -111,6 +111,27 @@ class Node extends ChangeNotifier with LinkedListEntry<Node> {
|
|||||||
return childAtIndex(path.first)?.childAtPath(path.sublist(1));
|
return childAtIndex(path.first)?.childAtPath(path.sublist(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void insert(Node entry, {int? index}) {
|
||||||
|
index ??= children.length;
|
||||||
|
|
||||||
|
if (children.isEmpty) {
|
||||||
|
entry.parent = this;
|
||||||
|
children.add(entry);
|
||||||
|
notifyListeners();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final length = children.length;
|
||||||
|
|
||||||
|
if (index >= length) {
|
||||||
|
children.last.insertAfter(entry);
|
||||||
|
} else if (index <= 0) {
|
||||||
|
children.first.insertBefore(entry);
|
||||||
|
} else {
|
||||||
|
childAtIndex(index)?.insertBefore(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void insertAfter(Node entry) {
|
void insertAfter(Node entry) {
|
||||||
entry.parent = parent;
|
entry.parent = parent;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flowy_editor/src/document/node.dart';
|
import 'package:flowy_editor/src/document/node.dart';
|
||||||
import 'package:flowy_editor/src/document/path.dart';
|
import 'package:flowy_editor/src/document/path.dart';
|
||||||
import 'package:flowy_editor/src/document/text_delta.dart';
|
import 'package:flowy_editor/src/document/text_delta.dart';
|
||||||
@ -27,9 +29,18 @@ class StateTree {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Node? insertedNode = root.childAtPath(
|
Node? insertedNode = root.childAtPath(
|
||||||
path.sublist(0, path.length - 1) + [path.last - 1],
|
path.sublist(0, path.length - 1) + [max(0, path.last - 1)],
|
||||||
);
|
);
|
||||||
if (insertedNode == null) {
|
if (insertedNode == null) {
|
||||||
|
final insertedNode = root.childAtPath(
|
||||||
|
path.sublist(0, path.length - 1),
|
||||||
|
);
|
||||||
|
if (insertedNode != null) {
|
||||||
|
for (final node in nodes) {
|
||||||
|
insertedNode.insert(node);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
@ -51,6 +51,9 @@ class EditorState {
|
|||||||
final UndoManager undoManager = UndoManager();
|
final UndoManager undoManager = UndoManager();
|
||||||
Selection? _cursorSelection;
|
Selection? _cursorSelection;
|
||||||
|
|
||||||
|
/// TODO: only for testing.
|
||||||
|
bool disableSealTimer = false;
|
||||||
|
|
||||||
Selection? get cursorSelection {
|
Selection? get cursorSelection {
|
||||||
return _cursorSelection;
|
return _cursorSelection;
|
||||||
}
|
}
|
||||||
@ -106,6 +109,9 @@ class EditorState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_debouncedSealHistoryItem() {
|
_debouncedSealHistoryItem() {
|
||||||
|
if (disableSealTimer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
_debouncedSealHistoryItemTimer?.cancel();
|
_debouncedSealHistoryItemTimer?.cancel();
|
||||||
_debouncedSealHistoryItemTimer =
|
_debouncedSealHistoryItemTimer =
|
||||||
Timer(const Duration(milliseconds: 1000), () {
|
Timer(const Duration(milliseconds: 1000), () {
|
||||||
|
@ -24,11 +24,18 @@ FlowyKeyEventHandler enterWithoutShiftInTextNodesHandler =
|
|||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
|
|
||||||
final nodes = editorState.service.selectionService.currentSelectedNodes;
|
var selection = editorState.service.selectionService.currentSelection.value;
|
||||||
|
var nodes = editorState.service.selectionService.currentSelectedNodes;
|
||||||
|
if (selection == null) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
if (selection.isForward) {
|
||||||
|
selection = selection.reversed;
|
||||||
|
nodes = nodes.reversed.toList(growable: false);
|
||||||
|
}
|
||||||
final textNodes = nodes.whereType<TextNode>().toList(growable: false);
|
final textNodes = nodes.whereType<TextNode>().toList(growable: false);
|
||||||
final selection = editorState.service.selectionService.currentSelection.value;
|
|
||||||
|
|
||||||
if (selection == null || nodes.length != textNodes.length) {
|
if (nodes.length != textNodes.length) {
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +43,7 @@ FlowyKeyEventHandler enterWithoutShiftInTextNodesHandler =
|
|||||||
if (!selection.isSingle) {
|
if (!selection.isSingle) {
|
||||||
final length = textNodes.length;
|
final length = textNodes.length;
|
||||||
final List<TextNode> subTextNodes =
|
final List<TextNode> subTextNodes =
|
||||||
length >= 3 ? textNodes.sublist(1, textNodes.length - 2) : [];
|
length >= 3 ? textNodes.sublist(1, textNodes.length - 1) : [];
|
||||||
final afterSelection = Selection.collapsed(
|
final afterSelection = Selection.collapsed(
|
||||||
Position(path: textNodes.first.path.next, offset: 0),
|
Position(path: textNodes.first.path.next, offset: 0),
|
||||||
);
|
);
|
||||||
@ -86,7 +93,7 @@ FlowyKeyEventHandler enterWithoutShiftInTextNodesHandler =
|
|||||||
);
|
);
|
||||||
TransactionBuilder(editorState)
|
TransactionBuilder(editorState)
|
||||||
..insertNode(
|
..insertNode(
|
||||||
textNode.path.next,
|
textNode.path,
|
||||||
TextNode.empty(),
|
TextNode.empty(),
|
||||||
)
|
)
|
||||||
..afterSelection = afterSelection
|
..afterSelection = afterSelection
|
||||||
|
@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
abstract class FlowyKeyboardService {
|
abstract class FlowyKeyboardService {
|
||||||
|
KeyEventResult onKey(RawKeyEvent event);
|
||||||
void enable();
|
void enable();
|
||||||
void disable();
|
void disable();
|
||||||
}
|
}
|
||||||
@ -65,15 +66,8 @@ class _FlowyKeyboardState extends State<FlowyKeyboard>
|
|||||||
_focusNode.unfocus();
|
_focusNode.unfocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onFocusChange(bool value) {
|
@override
|
||||||
debugPrint('[KeyBoard Service] focus change $value');
|
KeyEventResult onKey(RawKeyEvent event) {
|
||||||
}
|
|
||||||
|
|
||||||
KeyEventResult _onKey(FocusNode node, RawKeyEvent event) {
|
|
||||||
if (!isFocus) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
debugPrint('on keyboard event $event');
|
debugPrint('on keyboard event $event');
|
||||||
|
|
||||||
if (event is! RawKeyDownEvent) {
|
if (event is! RawKeyDownEvent) {
|
||||||
@ -97,4 +91,16 @@ class _FlowyKeyboardState extends State<FlowyKeyboard>
|
|||||||
|
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onFocusChange(bool value) {
|
||||||
|
debugPrint('[KeyBoard Service] focus change $value');
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEventResult _onKey(FocusNode node, RawKeyEvent event) {
|
||||||
|
if (!isFocus) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
return onKey(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,8 @@ class FlowyRenderPlugin extends FlowyRenderPluginService {
|
|||||||
node.key = key;
|
node.key = key;
|
||||||
return _autoUpdateNodeWidget(builder, context);
|
return _autoUpdateNodeWidget(builder, context);
|
||||||
} else {
|
} else {
|
||||||
assert(false, 'Could not query the builder with this $name');
|
assert(false,
|
||||||
|
'Could not query the builder with this $name, or nodeValidator return false.');
|
||||||
// TODO: return a placeholder widget with tips.
|
// TODO: return a placeholder widget with tips.
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
|
@ -187,11 +187,11 @@ class _FlowySelectionState extends State<FlowySelection>
|
|||||||
if (selection != null) {
|
if (selection != null) {
|
||||||
if (selection.isCollapsed) {
|
if (selection.isCollapsed) {
|
||||||
/// updates cursor area.
|
/// updates cursor area.
|
||||||
debugPrint('updating cursor');
|
debugPrint('updating cursor, $selection');
|
||||||
_updateCursorAreas(selection.start);
|
_updateCursorAreas(selection.start);
|
||||||
} else {
|
} else {
|
||||||
// updates selection area.
|
// updates selection area.
|
||||||
debugPrint('updating selection');
|
debugPrint('updating selection, $selection');
|
||||||
_updateSelectionAreas(selection);
|
_updateSelectionAreas(selection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,108 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:flowy_editor/flowy_editor.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'test_raw_key_event.dart';
|
||||||
|
|
||||||
|
class EditorWidgetTester {
|
||||||
|
EditorWidgetTester({
|
||||||
|
required this.tester,
|
||||||
|
});
|
||||||
|
|
||||||
|
final WidgetTester tester;
|
||||||
|
late EditorState _editorState;
|
||||||
|
|
||||||
|
EditorState get editorState => _editorState;
|
||||||
|
Node get root => _editorState.document.root;
|
||||||
|
|
||||||
|
int get documentLength => _editorState.document.root.children.length;
|
||||||
|
Selection? get documentSelection =>
|
||||||
|
_editorState.service.selectionService.currentSelection.value;
|
||||||
|
|
||||||
|
Future<EditorWidgetTester> startTesting() async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: FlowyEditor(
|
||||||
|
editorState: _editorState,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initialize() {
|
||||||
|
_editorState = _createEmptyDocument();
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert<T extends Node>(T node) {
|
||||||
|
_editorState.document.root.insert(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void insertEmptyTextNode() {
|
||||||
|
insert(TextNode.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void insertTextNode(String? text, {Attributes? attributes}) {
|
||||||
|
insert(
|
||||||
|
TextNode(
|
||||||
|
type: 'text',
|
||||||
|
delta: Delta(
|
||||||
|
[TextInsert(text ?? 'Test')],
|
||||||
|
),
|
||||||
|
attributes: attributes,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node? nodeAtPath(Path path) {
|
||||||
|
return root.childAtPath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateSelection(Selection? selection) async {
|
||||||
|
if (selection == null) {
|
||||||
|
_editorState.service.selectionService.clearSelection();
|
||||||
|
} else {
|
||||||
|
_editorState.service.selectionService.updateSelection(selection);
|
||||||
|
}
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> pressLogicKey(LogicalKeyboardKey key) async {
|
||||||
|
late RawKeyEvent testRawKeyEventData;
|
||||||
|
if (key == LogicalKeyboardKey.enter) {
|
||||||
|
testRawKeyEventData = const TestRawKeyEventData(
|
||||||
|
logicalKey: LogicalKeyboardKey.enter,
|
||||||
|
physicalKey: PhysicalKeyboardKey.enter,
|
||||||
|
).toKeyEvent;
|
||||||
|
}
|
||||||
|
_editorState.service.keyboardService!.onKey(testRawKeyEventData);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
Node _createEmptyEditorRoot() {
|
||||||
|
return Node(
|
||||||
|
type: 'editor',
|
||||||
|
children: LinkedList(),
|
||||||
|
attributes: {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorState _createEmptyDocument() {
|
||||||
|
return EditorState(
|
||||||
|
document: StateTree(
|
||||||
|
root: _createEmptyEditorRoot(),
|
||||||
|
),
|
||||||
|
)..disableSealTimer = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TestEditorExtension on WidgetTester {
|
||||||
|
EditorWidgetTester get editor =>
|
||||||
|
EditorWidgetTester(tester: this)..initialize();
|
||||||
|
EditorState get editorState => editor.editorState;
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class TestRawKeyEvent extends RawKeyDownEvent {
|
||||||
|
const TestRawKeyEvent({required super.data});
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestRawKeyEventData extends RawKeyEventData {
|
||||||
|
const TestRawKeyEventData({
|
||||||
|
required this.logicalKey,
|
||||||
|
required this.physicalKey,
|
||||||
|
this.isControlPressed = false,
|
||||||
|
this.isShiftPressed = false,
|
||||||
|
this.isAltPressed = false,
|
||||||
|
this.isMetaPressed = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
final bool isControlPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final bool isShiftPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final bool isAltPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final bool isMetaPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final LogicalKeyboardKey logicalKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final PhysicalKeyboardKey physicalKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
KeyboardSide? getModifierSide(ModifierKey key) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isModifierPressed(ModifierKey key,
|
||||||
|
{KeyboardSide side = KeyboardSide.any}) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get keyLabel => throw UnimplementedError();
|
||||||
|
|
||||||
|
RawKeyEvent get toKeyEvent {
|
||||||
|
return TestRawKeyEvent(data: this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,198 @@
|
|||||||
|
import 'package:flowy_editor/flowy_editor.dart';
|
||||||
|
import 'package:flowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import '../../infra/test_editor.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
setUpAll(() {
|
||||||
|
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..insertEmptyTextNode();
|
||||||
|
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.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.enter,
|
||||||
|
);
|
||||||
|
expect(editor.documentLength, i + 1);
|
||||||
|
expect(editor.documentSelection,
|
||||||
|
Selection.single(path: [i], startOffset: 0));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
for (var i = 1; i <= lines; i++) {
|
||||||
|
editor.insertTextNode(text);
|
||||||
|
}
|
||||||
|
await editor.startTesting();
|
||||||
|
|
||||||
|
expect(editor.documentLength, lines);
|
||||||
|
|
||||||
|
// Presses the enter key in last line.
|
||||||
|
await editor.updateSelection(
|
||||||
|
Selection.single(path: [lines - 1], startOffset: 0),
|
||||||
|
);
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.enter,
|
||||||
|
);
|
||||||
|
lines += 1;
|
||||||
|
expect(editor.documentLength, lines);
|
||||||
|
expect(editor.documentSelection,
|
||||||
|
Selection.single(path: [lines - 1], startOffset: 0));
|
||||||
|
var lastNode = editor.nodeAtPath([lines - 1]);
|
||||||
|
expect(lastNode != null, true);
|
||||||
|
expect(lastNode is TextNode, true);
|
||||||
|
lastNode = lastNode as TextNode;
|
||||||
|
expect(lastNode.delta.toRawString(), text);
|
||||||
|
expect((lastNode.previous as TextNode).delta.toRawString(), '');
|
||||||
|
expect(
|
||||||
|
(lastNode.previous!.previous as TextNode).delta.toRawString(), text);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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, StyleKey.bulletedList);
|
||||||
|
});
|
||||||
|
testWidgets('Presses enter key in numbered list', (tester) async {
|
||||||
|
await _testStyleNeedToBeCopy(tester, StyleKey.numberList);
|
||||||
|
});
|
||||||
|
testWidgets('Presses enter key in checkbox styled text', (tester) async {
|
||||||
|
await _testStyleNeedToBeCopy(tester, StyleKey.checkbox);
|
||||||
|
});
|
||||||
|
testWidgets('Presses enter key in quoted text', (tester) async {
|
||||||
|
await _testStyleNeedToBeCopy(tester, StyleKey.quote);
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _testStyleNeedToBeCopy(WidgetTester tester, String style) async {
|
||||||
|
const text = 'Welcome to Appflowy 😁';
|
||||||
|
Attributes attributes = {
|
||||||
|
StyleKey.subtype: style,
|
||||||
|
};
|
||||||
|
if (style == StyleKey.checkbox) {
|
||||||
|
attributes[StyleKey.checkbox] = true;
|
||||||
|
} else if (style == StyleKey.numberList) {
|
||||||
|
attributes[StyleKey.number] = 1;
|
||||||
|
}
|
||||||
|
final editor = tester.editor
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(text, attributes: attributes)
|
||||||
|
..insertTextNode(text, attributes: attributes);
|
||||||
|
|
||||||
|
await editor.startTesting();
|
||||||
|
await editor.updateSelection(
|
||||||
|
Selection.single(path: [1], startOffset: 0),
|
||||||
|
);
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.enter,
|
||||||
|
);
|
||||||
|
expect(editor.documentSelection, Selection.single(path: [2], startOffset: 0));
|
||||||
|
|
||||||
|
await editor.updateSelection(
|
||||||
|
Selection.single(path: [3], startOffset: text.length),
|
||||||
|
);
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.enter,
|
||||||
|
);
|
||||||
|
expect(editor.documentSelection, Selection.single(path: [4], startOffset: 0));
|
||||||
|
expect(editor.nodeAtPath([4])?.subtype, style);
|
||||||
|
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.enter,
|
||||||
|
);
|
||||||
|
expect(editor.documentSelection, Selection.single(path: [4], startOffset: 0));
|
||||||
|
expect(editor.nodeAtPath([4])?.subtype, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
for (var i = 1; i <= lines; i++) {
|
||||||
|
editor.insertTextNode(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.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.enter,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(editor.documentLength, 2);
|
||||||
|
expect((editor.nodeAtPath([0]) as TextNode).toRawString(), 'Welcome');
|
||||||
|
expect((editor.nodeAtPath([1]) as TextNode).toRawString(), 'to Appflowy 😁');
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user