瀏覽代碼

refactor: move selection to core/location

Lucas.Xu 2 年之前
父節點
當前提交
d02c29426e
共有 27 個文件被更改,包括 158 次插入73 次删除
  1. 2 2
      frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart
  2. 1 1
      frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_built_in_text.dart
  3. 1 1
      frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_text.dart
  4. 1 1
      frontend/app_flowy/packages/appflowy_editor/lib/src/commands/text_command_infra.dart
  5. 0 0
      frontend/app_flowy/packages/appflowy_editor/lib/src/core/location/position.dart
  6. 38 30
      frontend/app_flowy/packages/appflowy_editor/lib/src/core/location/selection.dart
  7. 1 1
      frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart
  8. 1 1
      frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/node_extensions.dart
  9. 2 2
      frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/text_node_extensions.dart
  10. 1 1
      frontend/app_flowy/packages/appflowy_editor/lib/src/operation/transaction.dart
  11. 2 2
      frontend/app_flowy/packages/appflowy_editor/lib/src/operation/transaction_builder.dart
  12. 2 2
      frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_node_widget.dart
  13. 2 2
      frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/default_selectable.dart
  14. 2 2
      frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart
  15. 2 2
      frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/selectable.dart
  16. 2 2
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart
  17. 1 1
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/input_service.dart
  18. 4 4
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/arrow_keys_handler.dart
  19. 4 4
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart
  20. 1 1
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/number_list_helper.dart
  21. 2 2
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/select_all_handler.dart
  22. 2 2
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart
  23. 4 4
      frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart
  24. 1 1
      frontend/app_flowy/packages/appflowy_editor/lib/src/undo_manager.dart
  25. 0 0
      frontend/app_flowy/packages/appflowy_editor/test/core/location/position_test.dart
  26. 77 0
      frontend/app_flowy/packages/appflowy_editor/test/core/location/selection_test.dart
  27. 2 2
      frontend/app_flowy/packages/appflowy_editor/test/legacy/flowy_editor_test.dart

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart

@@ -5,8 +5,8 @@ export 'src/infra/log.dart';
 export 'src/render/style/editor_style.dart';
 export 'src/core/document/node.dart';
 export 'src/core/document/path.dart';
-export 'src/core/selection/position.dart';
-export 'src/document/selection.dart';
+export 'src/core/location/position.dart';
+export 'src/core/location/selection.dart';
 export 'src/document/state_tree.dart';
 export 'src/core/document/text_delta.dart';
 export 'src/core/document/attributes.dart';

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_built_in_text.dart

@@ -4,7 +4,7 @@ import 'package:appflowy_editor/src/core/document/attributes.dart';
 import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
 import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/core/document/path.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 
 Future<void> formatBuiltInTextAttributes(

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_text.dart

@@ -4,7 +4,7 @@ import 'package:appflowy_editor/src/commands/text_command_infra.dart';
 import 'package:appflowy_editor/src/core/document/attributes.dart';
 import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/core/document/path.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/operation/transaction_builder.dart';
 import 'package:flutter/widgets.dart';

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/commands/text_command_infra.dart

@@ -1,6 +1,6 @@
 import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/core/document/path.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 
 // get formatted [TextNode]

+ 0 - 0
frontend/app_flowy/packages/appflowy_editor/lib/src/core/selection/position.dart → frontend/app_flowy/packages/appflowy_editor/lib/src/core/location/position.dart


+ 38 - 30
frontend/app_flowy/packages/appflowy_editor/lib/src/document/selection.dart → frontend/app_flowy/packages/appflowy_editor/lib/src/core/location/selection.dart

@@ -1,5 +1,5 @@
 import 'package:appflowy_editor/src/core/document/path.dart';
-import 'package:appflowy_editor/src/core/selection/position.dart';
+import 'package:appflowy_editor/src/core/location/position.dart';
 
 /// Selection represents the selected area or the cursor area in the editor.
 ///
@@ -36,31 +36,58 @@ class Selection {
   final Position start;
   final Position end;
 
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) return true;
+
+    return other is Selection && other.start == start && other.end == end;
+  }
+
+  @override
+  int get hashCode => start.hashCode ^ end.hashCode;
+
+  @override
+  String toString() => 'start = $start, end = $end';
+
+  /// Returns a Boolean indicating whether the selection's start and end points
+  /// are at the same position.
   bool get isCollapsed => start == end;
+
+  /// Returns a Boolean indicating whether the selection's start and end points
+  /// are at the same path.
   bool get isSingle => start.path.equals(end.path);
+
+  /// Returns a Boolean indicating whether the selection is forward.
   bool get isForward =>
       (start.path > end.path) || (isSingle && start.offset > end.offset);
+
+  /// Returns a Boolean indicating whether the selection is backward.
   bool get isBackward =>
       (start.path < end.path) || (isSingle && start.offset < end.offset);
 
-  Selection get normalize {
-    if (isForward) {
-      return reversed;
-    }
-    return this;
-  }
+  /// Returns a normalized selection that direction is forward.
+  Selection get normalized => isBackward ? copyWith() : reversed.copyWith();
 
+  /// Returns a reversed selection.
   Selection get reversed => copyWith(start: end, end: start);
 
-  int get startIndex => normalize.start.offset;
-  int get endIndex => normalize.end.offset;
+  /// Returns the offset in the starting position under the normalized selection.
+  int get startIndex => normalized.start.offset;
+
+  /// Returns the offset in the ending position under the normalized selection.
+  int get endIndex => normalized.end.offset;
+
   int get length => endIndex - startIndex;
 
+  /// Collapses the current selection to a single point.
+  ///
+  /// If [atStart] is true, the selection will be collapsed to the start point.
+  /// If [atStart] is false, the selection will be collapsed to the end point.
   Selection collapse({bool atStart = false}) {
     if (atStart) {
-      return Selection(start: start, end: start);
+      return copyWith(end: start);
     } else {
-      return Selection(start: end, end: end);
+      return copyWith(start: end);
     }
   }
 
@@ -71,29 +98,10 @@ class Selection {
     );
   }
 
-  Selection copy() => Selection(start: start, end: end);
-
   Map<String, dynamic> toJson() {
     return {
       'start': start.toJson(),
       'end': end.toJson(),
     };
   }
-
-  @override
-  bool operator ==(Object other) {
-    if (other is! Selection) {
-      return false;
-    }
-    if (identical(this, other)) {
-      return true;
-    }
-    return start == other.start && end == other.end;
-  }
-
-  @override
-  int get hashCode => Object.hash(start, end);
-
-  @override
-  String toString() => '[Selection] start = $start, end = $end';
 }

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart

@@ -5,7 +5,7 @@ import 'package:appflowy_editor/src/render/style/editor_style.dart';
 import 'package:appflowy_editor/src/service/service.dart';
 import 'package:flutter/material.dart';
 
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/document/state_tree.dart';
 import 'package:appflowy_editor/src/operation/operation.dart';
 import 'package:appflowy_editor/src/operation/transaction.dart';

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/node_extensions.dart

@@ -1,6 +1,6 @@
 import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/core/document/path.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/extensions/object_extensions.dart';
 import 'package:appflowy_editor/src/render/selection/selectable.dart';
 import 'package:flutter/material.dart';

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/text_node_extensions.dart

@@ -1,7 +1,7 @@
 import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/core/document/path.dart';
-import 'package:appflowy_editor/src/core/selection/position.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/position.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/core/document/text_delta.dart';
 import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
 

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/operation/transaction.dart

@@ -1,6 +1,6 @@
 import 'dart:collection';
 import 'package:flutter/material.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import './operation.dart';
 
 /// A [Transaction] has a list of [Operation] objects that will be applied

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/operation/transaction_builder.dart

@@ -4,8 +4,8 @@ import 'dart:math';
 import 'package:appflowy_editor/src/core/document/attributes.dart';
 import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/core/document/path.dart';
-import 'package:appflowy_editor/src/core/selection/position.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/position.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/core/document/text_delta.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/operation/operation.dart';

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_node_widget.dart

@@ -1,7 +1,7 @@
 import 'package:appflowy_editor/src/extensions/object_extensions.dart';
 import 'package:appflowy_editor/src/core/document/node.dart';
-import 'package:appflowy_editor/src/core/selection/position.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/position.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/infra/flowy_svg.dart';
 import 'package:appflowy_editor/src/render/selection/selectable.dart';
 import 'package:flutter/material.dart';

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/default_selectable.dart

@@ -1,5 +1,5 @@
-import 'package:appflowy_editor/src/core/selection/position.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/position.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/render/selection/selectable.dart';
 import 'package:flutter/material.dart';
 

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart

@@ -7,8 +7,8 @@ import 'package:flutter/rendering.dart';
 
 import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/core/document/path.dart';
-import 'package:appflowy_editor/src/core/selection/position.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/position.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/core/document/text_delta.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/extensions/url_launcher_extension.dart';

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection/selectable.dart

@@ -1,5 +1,5 @@
-import 'package:appflowy_editor/src/core/selection/position.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/position.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:flutter/material.dart';
 
 enum CursorStyle {

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart

@@ -1,8 +1,8 @@
 import 'package:appflowy_editor/src/core/document/attributes.dart';
 import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/core/document/path.dart';
-import 'package:appflowy_editor/src/core/selection/position.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/position.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/extensions/text_node_extensions.dart';
 import 'package:appflowy_editor/src/operation/transaction_builder.dart';

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/service/input_service.dart

@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 
 import 'package:appflowy_editor/src/core/document/node.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/extensions/node_extensions.dart';
 import 'package:appflowy_editor/src/operation/transaction_builder.dart';

+ 4 - 4
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/arrow_keys_handler.dart

@@ -220,7 +220,7 @@ ShortcutEventHandler cursorEndSelect = (editorState, event) {
 KeyEventResult cursorUp(EditorState editorState, RawKeyEvent event) {
   final nodes = editorState.service.selectionService.currentSelectedNodes;
   final selection =
-      editorState.service.selectionService.currentSelection.value?.normalize;
+      editorState.service.selectionService.currentSelection.value?.normalized;
   if (nodes.isEmpty || selection == null) {
     return KeyEventResult.ignored;
   }
@@ -234,7 +234,7 @@ KeyEventResult cursorUp(EditorState editorState, RawKeyEvent event) {
 KeyEventResult cursorDown(EditorState editorState, RawKeyEvent event) {
   final nodes = editorState.service.selectionService.currentSelectedNodes;
   final selection =
-      editorState.service.selectionService.currentSelection.value?.normalize;
+      editorState.service.selectionService.currentSelection.value?.normalized;
   if (nodes.isEmpty || selection == null) {
     return KeyEventResult.ignored;
   }
@@ -248,7 +248,7 @@ KeyEventResult cursorDown(EditorState editorState, RawKeyEvent event) {
 KeyEventResult cursorLeft(EditorState editorState, RawKeyEvent event) {
   final nodes = editorState.service.selectionService.currentSelectedNodes;
   final selection =
-      editorState.service.selectionService.currentSelection.value?.normalize;
+      editorState.service.selectionService.currentSelection.value?.normalized;
   if (nodes.isEmpty || selection == null) {
     return KeyEventResult.ignored;
   }
@@ -270,7 +270,7 @@ KeyEventResult cursorLeft(EditorState editorState, RawKeyEvent event) {
 KeyEventResult cursorRight(EditorState editorState, RawKeyEvent event) {
   final nodes = editorState.service.selectionService.currentSelectedNodes;
   final selection =
-      editorState.service.selectionService.currentSelection.value?.normalize;
+      editorState.service.selectionService.currentSelection.value?.normalized;
   if (nodes.isEmpty || selection == null) {
     return KeyEventResult.ignored;
   }

+ 4 - 4
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart

@@ -25,7 +25,7 @@ Selection _computeSelectionAfterPasteMultipleNodes(
 }
 
 void _handleCopy(EditorState editorState) async {
-  final selection = editorState.cursorSelection?.normalize;
+  final selection = editorState.cursorSelection?.normalized;
   if (selection == null || selection.isCollapsed) {
     return;
   }
@@ -65,7 +65,7 @@ void _handleCopy(EditorState editorState) async {
 }
 
 void _pasteHTML(EditorState editorState, String html) {
-  final selection = editorState.cursorSelection?.normalize;
+  final selection = editorState.cursorSelection?.normalized;
   if (selection == null) {
     return;
   }
@@ -234,7 +234,7 @@ Delta _lineContentToDelta(String lineContent) {
 }
 
 void _handlePastePlainText(EditorState editorState, String plainText) {
-  final selection = editorState.cursorSelection?.normalize;
+  final selection = editorState.cursorSelection?.normalized;
   if (selection == null) {
     return;
   }
@@ -300,7 +300,7 @@ void _handleCut(EditorState editorState) {
 }
 
 void _deleteSelectedContent(EditorState editorState) {
-  final selection = editorState.cursorSelection?.normalize;
+  final selection = editorState.cursorSelection?.normalized;
   if (selection == null || selection.isCollapsed) {
     return;
   }

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/number_list_helper.dart

@@ -1,4 +1,4 @@
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
 import 'package:appflowy_editor/src/operation/transaction_builder.dart';

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/select_all_handler.dart

@@ -1,6 +1,6 @@
 import 'package:appflowy_editor/src/core/document/node.dart';
-import 'package:appflowy_editor/src/core/selection/position.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/position.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart';
 import 'package:flutter/material.dart';
 

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart

@@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
 import 'package:appflowy_editor/src/core/document/node.dart';
-import 'package:appflowy_editor/src/core/selection/position.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/position.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/operation/transaction_builder.dart';
 import './number_list_helper.dart';

+ 4 - 4
frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart

@@ -4,8 +4,8 @@ import 'package:flutter/material.dart';
 import 'package:appflowy_editor/src/core/document/node.dart';
 import 'package:appflowy_editor/src/core/document/node_iterator.dart';
 import 'package:appflowy_editor/src/core/document/path.dart';
-import 'package:appflowy_editor/src/core/selection/position.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/position.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/editor_state.dart';
 import 'package:appflowy_editor/src/extensions/node_extensions.dart';
 import 'package:appflowy_editor/src/extensions/object_extensions.dart';
@@ -366,7 +366,7 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
 
     final backwardNodes =
         selection.isBackward ? nodes : nodes.reversed.toList(growable: false);
-    final normalizedSelection = selection.normalize;
+    final normalizedSelection = selection.normalized;
     assert(normalizedSelection.isBackward);
 
     Log.selection.debug('update selection areas, $normalizedSelection');
@@ -378,7 +378,7 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
         continue;
       }
 
-      var newSelection = normalizedSelection.copy();
+      var newSelection = normalizedSelection.copyWith();
 
       /// In the case of multiple selections,
       ///  we need to return a new selection for each selected node individually.

+ 1 - 1
frontend/app_flowy/packages/appflowy_editor/lib/src/undo_manager.dart

@@ -1,6 +1,6 @@
 import 'dart:collection';
 
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:appflowy_editor/src/infra/log.dart';
 import 'package:appflowy_editor/src/operation/operation.dart';
 import 'package:appflowy_editor/src/operation/transaction_builder.dart';

+ 0 - 0
frontend/app_flowy/packages/appflowy_editor/test/core/selection/position_test.dart → frontend/app_flowy/packages/appflowy_editor/test/core/location/position_test.dart


+ 77 - 0
frontend/app_flowy/packages/appflowy_editor/test/core/location/selection_test.dart

@@ -0,0 +1,77 @@
+import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() async {
+  group('selection.dart', () {
+    test('test selection equality', () {
+      final position = Position(path: [0, 1, 2], offset: 3);
+      final selectionA = Selection(start: position, end: position);
+      final selectionB = Selection.collapsed(position);
+      expect(selectionA, selectionB);
+      expect(selectionA.hashCode, selectionB.hashCode);
+
+      final newPosition = Position(path: [1, 2, 3], offset: 4);
+
+      final selectionC = selectionA.copyWith(start: newPosition);
+      expect(selectionC.start, newPosition);
+      expect(selectionC.end, position);
+      expect(selectionC.isCollapsed, false);
+
+      final selectionD = selectionA.copyWith(end: newPosition);
+      expect(selectionD.start, position);
+      expect(selectionD.end, newPosition);
+      expect(selectionD.isCollapsed, false);
+
+      final selectionE = Selection.single(path: [0, 1, 2], startOffset: 3);
+      expect(selectionE, selectionA);
+      expect(selectionE.isSingle, true);
+      expect(selectionE.isCollapsed, true);
+    });
+
+    test('test selection direction', () {
+      final start = Position(path: [0, 1, 2], offset: 3);
+      final end = Position(path: [1, 2, 3], offset: 3);
+      final backwardSelection = Selection(start: start, end: end);
+      expect(backwardSelection.isBackward, true);
+      final forwardSelection = Selection(start: end, end: start);
+      expect(forwardSelection.isForward, true);
+
+      expect(backwardSelection.reversed, forwardSelection);
+      expect(forwardSelection.normalized, backwardSelection);
+
+      expect(backwardSelection.startIndex, 3);
+      expect(backwardSelection.endIndex, 3);
+    });
+
+    test('test selection collapsed', () {
+      final start = Position(path: [0, 1, 2], offset: 3);
+      final end = Position(path: [1, 2, 3], offset: 3);
+      final selection = Selection(start: start, end: end);
+      final collapsedAtStart = selection.collapse(atStart: true);
+      expect(collapsedAtStart.isCollapsed, true);
+      expect(collapsedAtStart.start, start);
+      expect(collapsedAtStart.end, start);
+
+      final collapsedAtEnd = selection.collapse(atStart: false);
+      expect(collapsedAtEnd.isCollapsed, true);
+      expect(collapsedAtEnd.start, end);
+      expect(collapsedAtEnd.end, end);
+    });
+
+    test('test selection toJson', () {
+      final start = Position(path: [0, 1, 2], offset: 3);
+      final end = Position(path: [1, 2, 3], offset: 3);
+      final selection = Selection(start: start, end: end);
+      expect(selection.toJson(), {
+        'start': {
+          'path': [0, 1, 2],
+          'offset': 3
+        },
+        'end': {
+          'path': [1, 2, 3],
+          'offset': 3
+        }
+      });
+    });
+  });
+}

+ 2 - 2
frontend/app_flowy/packages/appflowy_editor/test/legacy/flowy_editor_test.dart

@@ -1,6 +1,6 @@
 import 'package:appflowy_editor/src/core/document/path.dart';
-import 'package:appflowy_editor/src/core/selection/position.dart';
-import 'package:appflowy_editor/src/document/selection.dart';
+import 'package:appflowy_editor/src/core/location/position.dart';
+import 'package:appflowy_editor/src/core/location/selection.dart';
 import 'package:flutter_test/flutter_test.dart';
 
 void main() {