mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #853 from LucasXu0/test/page_up_down
Implement more tests.
This commit is contained in:
commit
dcbf657d84
@ -28,10 +28,12 @@ List<String> defaultListToolbarEventNames = [
|
|||||||
'H1',
|
'H1',
|
||||||
'H2',
|
'H2',
|
||||||
'H3',
|
'H3',
|
||||||
// 'B-List',
|
|
||||||
// 'N-List',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
mixin ToolbarMixin<T extends StatefulWidget> on State<T> {
|
||||||
|
void hide();
|
||||||
|
}
|
||||||
|
|
||||||
class ToolbarWidget extends StatefulWidget {
|
class ToolbarWidget extends StatefulWidget {
|
||||||
const ToolbarWidget({
|
const ToolbarWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -50,7 +52,7 @@ class ToolbarWidget extends StatefulWidget {
|
|||||||
State<ToolbarWidget> createState() => _ToolbarWidgetState();
|
State<ToolbarWidget> createState() => _ToolbarWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ToolbarWidgetState extends State<ToolbarWidget> {
|
class _ToolbarWidgetState extends State<ToolbarWidget> with ToolbarMixin {
|
||||||
final GlobalKey _listToolbarKey = GlobalKey();
|
final GlobalKey _listToolbarKey = GlobalKey();
|
||||||
|
|
||||||
final toolbarHeight = 32.0;
|
final toolbarHeight = 32.0;
|
||||||
@ -63,21 +65,6 @@ class _ToolbarWidgetState extends State<ToolbarWidget> {
|
|||||||
|
|
||||||
OverlayEntry? _listToolbarOverlay;
|
OverlayEntry? _listToolbarOverlay;
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
widget.editorState.service.selectionService.currentSelection
|
|
||||||
.addListener(_onSelectionChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
widget.editorState.service.selectionService.currentSelection
|
|
||||||
.removeListener(_onSelectionChange);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Positioned(
|
return Positioned(
|
||||||
@ -92,6 +79,12 @@ class _ToolbarWidgetState extends State<ToolbarWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void hide() {
|
||||||
|
_listToolbarOverlay?.remove();
|
||||||
|
_listToolbarOverlay = null;
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildToolbar(BuildContext context) {
|
Widget _buildToolbar(BuildContext context) {
|
||||||
return Material(
|
return Material(
|
||||||
borderRadius: BorderRadius.circular(cornerRadius),
|
borderRadius: BorderRadius.circular(cornerRadius),
|
||||||
@ -212,9 +205,4 @@ class _ToolbarWidgetState extends State<ToolbarWidget> {
|
|||||||
}
|
}
|
||||||
assert(false, 'Could not find the event handler for $eventName');
|
assert(false, 'Could not find the event handler for $eventName');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSelectionChange() {
|
|
||||||
_listToolbarOverlay?.remove();
|
|
||||||
_listToolbarOverlay = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,25 +2,16 @@ import 'package:flowy_editor/flowy_editor.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
double? getEditorHeight(EditorState editorState) {
|
|
||||||
final renderObj =
|
|
||||||
editorState.service.scrollServiceKey.currentContext?.findRenderObject();
|
|
||||||
if (renderObj is RenderBox) {
|
|
||||||
return renderObj.size.height;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
FlowyKeyEventHandler pageUpDownHandler = (editorState, event) {
|
FlowyKeyEventHandler pageUpDownHandler = (editorState, event) {
|
||||||
if (event.logicalKey == LogicalKeyboardKey.pageUp) {
|
if (event.logicalKey == LogicalKeyboardKey.pageUp) {
|
||||||
final scrollHeight = getEditorHeight(editorState);
|
final scrollHeight = editorState.service.scrollService?.onePageHeight;
|
||||||
final scrollService = editorState.service.scrollService;
|
final scrollService = editorState.service.scrollService;
|
||||||
if (scrollHeight != null && scrollService != null) {
|
if (scrollHeight != null && scrollService != null) {
|
||||||
scrollService.scrollTo(scrollService.dy - scrollHeight);
|
scrollService.scrollTo(scrollService.dy - scrollHeight);
|
||||||
}
|
}
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.pageDown) {
|
} else if (event.logicalKey == LogicalKeyboardKey.pageDown) {
|
||||||
final scrollHeight = getEditorHeight(editorState);
|
final scrollHeight = editorState.service.scrollService?.onePageHeight;
|
||||||
final scrollService = editorState.service.scrollService;
|
final scrollService = editorState.service.scrollService;
|
||||||
if (scrollHeight != null && scrollService != null) {
|
if (scrollHeight != null && scrollService != null) {
|
||||||
scrollService.scrollTo(scrollService.dy + scrollHeight);
|
scrollService.scrollTo(scrollService.dy + scrollHeight);
|
||||||
|
@ -11,6 +11,9 @@ import 'package:flowy_editor/src/extensions/node_extensions.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
List<PopupListItem> get popupListItems => _popupListItems;
|
||||||
|
|
||||||
final List<PopupListItem> _popupListItems = [
|
final List<PopupListItem> _popupListItems = [
|
||||||
PopupListItem(
|
PopupListItem(
|
||||||
text: 'Text',
|
text: 'Text',
|
||||||
@ -94,6 +97,14 @@ FlowyKeyEventHandler slashShortcutHandler = (editorState, event) {
|
|||||||
_editorState = editorState;
|
_editorState = editorState;
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_selectionChangeBySlash = false;
|
_selectionChangeBySlash = false;
|
||||||
|
|
||||||
|
editorState.service.selectionService.currentSelection
|
||||||
|
.removeListener(clearPopupList);
|
||||||
|
editorState.service.selectionService.currentSelection
|
||||||
|
.addListener(clearPopupList);
|
||||||
|
|
||||||
|
editorState.service.scrollService?.disable();
|
||||||
|
|
||||||
showPopupList(context, editorState, selectionRects.first.bottomRight);
|
showPopupList(context, editorState, selectionRects.first.bottomRight);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -115,23 +126,20 @@ void showPopupList(
|
|||||||
);
|
);
|
||||||
|
|
||||||
Overlay.of(context)?.insert(_popupListOverlay!);
|
Overlay.of(context)?.insert(_popupListOverlay!);
|
||||||
|
|
||||||
editorState.service.selectionService.currentSelection
|
|
||||||
.removeListener(clearPopupList);
|
|
||||||
editorState.service.selectionService.currentSelection
|
|
||||||
.addListener(clearPopupList);
|
|
||||||
|
|
||||||
editorState.service.scrollService?.disable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearPopupList() {
|
void clearPopupList() {
|
||||||
if (_popupListOverlay == null || _editorState == null) {
|
if (_popupListOverlay == null || _editorState == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final selection =
|
final isSelectionDisposed =
|
||||||
_editorState?.service.selectionService.currentSelection.value;
|
_editorState?.service.selectionServiceKey.currentState != null;
|
||||||
if (selection == null) {
|
if (isSelectionDisposed) {
|
||||||
return;
|
final selection =
|
||||||
|
_editorState?.service.selectionService.currentSelection.value;
|
||||||
|
if (selection == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (_selectionChangeBySlash) {
|
if (_selectionChangeBySlash) {
|
||||||
_selectionChangeBySlash = false;
|
_selectionChangeBySlash = false;
|
||||||
|
@ -3,9 +3,10 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flowy_editor/src/document/node.dart';
|
import 'package:flowy_editor/src/document/node.dart';
|
||||||
import 'package:flowy_editor/src/service/default_text_operations/format_rich_text_style.dart';
|
import 'package:flowy_editor/src/service/default_text_operations/format_rich_text_style.dart';
|
||||||
import 'package:flowy_editor/src/service/keyboard_service.dart';
|
import 'package:flowy_editor/src/service/keyboard_service.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
FlowyKeyEventHandler updateTextStyleByCommandXHandler = (editorState, event) {
|
FlowyKeyEventHandler updateTextStyleByCommandXHandler = (editorState, event) {
|
||||||
if (!event.isMetaPressed || event.character == null) {
|
if (!event.isMetaPressed) {
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,26 +18,19 @@ FlowyKeyEventHandler updateTextStyleByCommandXHandler = (editorState, event) {
|
|||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (event.character!) {
|
if (event.logicalKey == LogicalKeyboardKey.keyB) {
|
||||||
// bold
|
formatBold(editorState);
|
||||||
case 'B':
|
return KeyEventResult.handled;
|
||||||
case 'b':
|
} else if (event.logicalKey == LogicalKeyboardKey.keyI) {
|
||||||
formatBold(editorState);
|
formatItalic(editorState);
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
case 'I':
|
} else if (event.logicalKey == LogicalKeyboardKey.keyU) {
|
||||||
case 'i':
|
formatUnderline(editorState);
|
||||||
formatItalic(editorState);
|
return KeyEventResult.handled;
|
||||||
return KeyEventResult.handled;
|
} else if (event.logicalKey == LogicalKeyboardKey.keyS &&
|
||||||
case 'U':
|
event.isShiftPressed) {
|
||||||
case 'u':
|
formatStrikethrough(editorState);
|
||||||
formatUnderline(editorState);
|
return KeyEventResult.handled;
|
||||||
return KeyEventResult.handled;
|
|
||||||
case 'S':
|
|
||||||
case 's':
|
|
||||||
formatStrikethrough(editorState);
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flowy_editor/src/extensions/object_extensions.dart';
|
||||||
|
|
||||||
abstract class FlowyScrollService {
|
abstract class FlowyScrollService {
|
||||||
double get dy;
|
double get dy;
|
||||||
|
double? get onePageHeight;
|
||||||
|
|
||||||
|
int? get page;
|
||||||
|
|
||||||
|
double get maxScrollExtent;
|
||||||
|
double get minScrollExtent;
|
||||||
|
|
||||||
void scrollTo(double dy);
|
void scrollTo(double dy);
|
||||||
|
|
||||||
@ -32,6 +39,27 @@ class _FlowyScrollState extends State<FlowyScroll>
|
|||||||
@override
|
@override
|
||||||
double get dy => _scrollController.position.pixels;
|
double get dy => _scrollController.position.pixels;
|
||||||
|
|
||||||
|
@override
|
||||||
|
double? get onePageHeight {
|
||||||
|
final renderBox = context.findRenderObject()?.unwrapOrNull<RenderBox>();
|
||||||
|
return renderBox?.size.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
double get maxScrollExtent => _scrollController.position.maxScrollExtent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
double get minScrollExtent => _scrollController.position.minScrollExtent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int? get page {
|
||||||
|
if (onePageHeight != null) {
|
||||||
|
final scrollExtent = maxScrollExtent - minScrollExtent;
|
||||||
|
return (scrollExtent / onePageHeight!).ceil();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Listener(
|
return Listener(
|
||||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:flowy_editor/flowy_editor.dart';
|
import 'package:flowy_editor/flowy_editor.dart';
|
||||||
import 'package:flowy_editor/src/render/selection/toolbar_widget.dart';
|
import 'package:flowy_editor/src/render/selection/toolbar_widget.dart';
|
||||||
|
import 'package:flowy_editor/src/extensions/object_extensions.dart';
|
||||||
|
|
||||||
abstract class FlowyToolbarService {
|
abstract class FlowyToolbarService {
|
||||||
/// Show the toolbar widget beside the offset.
|
/// Show the toolbar widget beside the offset.
|
||||||
@ -28,12 +29,15 @@ class FlowyToolbar extends StatefulWidget {
|
|||||||
class _FlowyToolbarState extends State<FlowyToolbar>
|
class _FlowyToolbarState extends State<FlowyToolbar>
|
||||||
implements FlowyToolbarService {
|
implements FlowyToolbarService {
|
||||||
OverlayEntry? _toolbarOverlay;
|
OverlayEntry? _toolbarOverlay;
|
||||||
|
final _toolbarWidgetKey = GlobalKey(debugLabel: '_toolbar_widget');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void showInOffset(Offset offset, LayerLink layerLink) {
|
void showInOffset(Offset offset, LayerLink layerLink) {
|
||||||
_toolbarOverlay?.remove();
|
hide();
|
||||||
|
|
||||||
_toolbarOverlay = OverlayEntry(
|
_toolbarOverlay = OverlayEntry(
|
||||||
builder: (context) => ToolbarWidget(
|
builder: (context) => ToolbarWidget(
|
||||||
|
key: _toolbarWidgetKey,
|
||||||
editorState: widget.editorState,
|
editorState: widget.editorState,
|
||||||
layerLink: layerLink,
|
layerLink: layerLink,
|
||||||
offset: offset.translate(0, -37.0),
|
offset: offset.translate(0, -37.0),
|
||||||
@ -45,6 +49,7 @@ class _FlowyToolbarState extends State<FlowyToolbar>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void hide() {
|
void hide() {
|
||||||
|
_toolbarWidgetKey.currentState?.unwrapOrNull<ToolbarMixin>()?.hide();
|
||||||
_toolbarOverlay?.remove();
|
_toolbarOverlay?.remove();
|
||||||
_toolbarOverlay = null;
|
_toolbarOverlay = null;
|
||||||
}
|
}
|
||||||
@ -55,4 +60,11 @@ class _FlowyToolbarState extends State<FlowyToolbar>
|
|||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
hide();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,8 +72,20 @@ class EditorWidgetTester {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> pressLogicKey(LogicalKeyboardKey key) async {
|
Future<void> pressLogicKey(
|
||||||
final testRawKeyEventData = TestRawKeyEventData(logicalKey: key).toKeyEvent;
|
LogicalKeyboardKey key, {
|
||||||
|
bool isControlPressed = false,
|
||||||
|
bool isShiftPressed = false,
|
||||||
|
bool isAltPressed = false,
|
||||||
|
bool isMetaPressed = false,
|
||||||
|
}) async {
|
||||||
|
final testRawKeyEventData = TestRawKeyEventData(
|
||||||
|
logicalKey: key,
|
||||||
|
isControlPressed: isControlPressed,
|
||||||
|
isShiftPressed: isShiftPressed,
|
||||||
|
isAltPressed: isAltPressed,
|
||||||
|
isMetaPressed: isMetaPressed,
|
||||||
|
).toKeyEvent;
|
||||||
_editorState.service.keyboardService!.onKey(testRawKeyEventData);
|
_editorState.service.keyboardService!.onKey(testRawKeyEventData);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,25 @@
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class TestRawKeyEvent extends RawKeyDownEvent {
|
class TestRawKeyEvent extends RawKeyDownEvent {
|
||||||
const TestRawKeyEvent({required super.data});
|
const TestRawKeyEvent({
|
||||||
|
required super.data,
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestRawKeyEventData extends RawKeyEventData {
|
class TestRawKeyEventData extends RawKeyEventData {
|
||||||
@ -46,7 +64,13 @@ class TestRawKeyEventData extends RawKeyEventData {
|
|||||||
String get keyLabel => throw UnimplementedError();
|
String get keyLabel => throw UnimplementedError();
|
||||||
|
|
||||||
RawKeyEvent get toKeyEvent {
|
RawKeyEvent get toKeyEvent {
|
||||||
return TestRawKeyEvent(data: this);
|
return TestRawKeyEvent(
|
||||||
|
data: this,
|
||||||
|
isAltPressed: isAltPressed,
|
||||||
|
isControlPressed: isControlPressed,
|
||||||
|
isMetaPressed: isMetaPressed,
|
||||||
|
isShiftPressed: isShiftPressed,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +85,36 @@ extension on LogicalKeyboardKey {
|
|||||||
if (this == LogicalKeyboardKey.delete) {
|
if (this == LogicalKeyboardKey.delete) {
|
||||||
return PhysicalKeyboardKey.delete;
|
return PhysicalKeyboardKey.delete;
|
||||||
}
|
}
|
||||||
|
if (this == LogicalKeyboardKey.pageDown) {
|
||||||
|
return PhysicalKeyboardKey.pageDown;
|
||||||
|
}
|
||||||
|
if (this == LogicalKeyboardKey.pageUp) {
|
||||||
|
return PhysicalKeyboardKey.pageUp;
|
||||||
|
}
|
||||||
|
if (this == LogicalKeyboardKey.slash) {
|
||||||
|
return PhysicalKeyboardKey.slash;
|
||||||
|
}
|
||||||
|
if (this == LogicalKeyboardKey.arrowDown) {
|
||||||
|
return PhysicalKeyboardKey.arrowDown;
|
||||||
|
}
|
||||||
|
if (this == LogicalKeyboardKey.keyA) {
|
||||||
|
return PhysicalKeyboardKey.keyA;
|
||||||
|
}
|
||||||
|
if (this == LogicalKeyboardKey.keyB) {
|
||||||
|
return PhysicalKeyboardKey.keyB;
|
||||||
|
}
|
||||||
|
if (this == LogicalKeyboardKey.keyI) {
|
||||||
|
return PhysicalKeyboardKey.keyI;
|
||||||
|
}
|
||||||
|
if (this == LogicalKeyboardKey.keyS) {
|
||||||
|
return PhysicalKeyboardKey.keyS;
|
||||||
|
}
|
||||||
|
if (this == LogicalKeyboardKey.keyU) {
|
||||||
|
return PhysicalKeyboardKey.keyU;
|
||||||
|
}
|
||||||
|
if (this == LogicalKeyboardKey.keyZ) {
|
||||||
|
return PhysicalKeyboardKey.keyZ;
|
||||||
|
}
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
import 'package:flowy_editor/flowy_editor.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('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;
|
||||||
|
for (var i = 0; i < 1000; i++) {
|
||||||
|
editor.insertTextNode(text);
|
||||||
|
}
|
||||||
|
await editor.startTesting();
|
||||||
|
await editor.updateSelection(
|
||||||
|
Selection.single(path: [0], startOffset: 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
final scrollService = editor.editorState.service.scrollService;
|
||||||
|
|
||||||
|
expect(scrollService != null, true);
|
||||||
|
|
||||||
|
if (scrollService == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final page = scrollService.page;
|
||||||
|
final onePageHeight = scrollService.onePageHeight;
|
||||||
|
expect(page != null, true);
|
||||||
|
expect(onePageHeight != null, true);
|
||||||
|
|
||||||
|
// Pressing the pageDown key continuously.
|
||||||
|
var currentOffsetY = 0.0;
|
||||||
|
for (int i = 1; i <= page!; i++) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.pageDown,
|
||||||
|
);
|
||||||
|
currentOffsetY += onePageHeight!;
|
||||||
|
final dy = scrollService.dy;
|
||||||
|
expect(dy, currentOffsetY);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i <= 5; i++) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
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.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.pageUp,
|
||||||
|
);
|
||||||
|
currentOffsetY -= onePageHeight!;
|
||||||
|
final dy = editor.editorState.service.scrollService?.dy;
|
||||||
|
expect(dy, currentOffsetY);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i <= 5; i++) {
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.pageUp,
|
||||||
|
);
|
||||||
|
final dy = scrollService.dy;
|
||||||
|
expect(dy == scrollService.minScrollExtent, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
import 'package:flowy_editor/flowy_editor.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('redo_undo_handler_test.dart', () {
|
||||||
|
// TODO: need to test more cases.
|
||||||
|
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> _testBackspaceUndoRedo(
|
||||||
|
WidgetTester tester, bool isDownwardSelection) async {
|
||||||
|
const text = 'Welcome to Appflowy 😁';
|
||||||
|
final editor = tester.editor
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(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.pressLogicKey(LogicalKeyboardKey.backspace);
|
||||||
|
expect(editor.documentLength, 2);
|
||||||
|
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.keyZ,
|
||||||
|
isMetaPressed: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(editor.documentLength, 3);
|
||||||
|
expect((editor.nodeAtPath([1]) as TextNode).toRawString(), text);
|
||||||
|
expect(editor.documentSelection, selection);
|
||||||
|
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
LogicalKeyboardKey.keyZ,
|
||||||
|
isMetaPressed: true,
|
||||||
|
isShiftPressed: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(editor.documentLength, 2);
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
import 'package:flowy_editor/flowy_editor.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('select_all_handler_test.dart', () {
|
||||||
|
testWidgets('Presses Command + A in small document', (tester) async {
|
||||||
|
await _testSelectAllHandler(tester, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Presses Command + A in small document', (tester) async {
|
||||||
|
await _testSelectAllHandler(tester, 1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _testSelectAllHandler(WidgetTester tester, int lines) async {
|
||||||
|
const text = 'Welcome to Appflowy 😁';
|
||||||
|
final editor = tester.editor;
|
||||||
|
for (var i = 0; i < lines; i++) {
|
||||||
|
editor.insertTextNode(text);
|
||||||
|
}
|
||||||
|
await editor.startTesting();
|
||||||
|
await editor.pressLogicKey(LogicalKeyboardKey.keyA, isMetaPressed: true);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
editor.documentSelection,
|
||||||
|
Selection(
|
||||||
|
start: Position(path: [0], offset: 0),
|
||||||
|
end: Position(path: [lines - 1], offset: text.length),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
import 'package:flowy_editor/flowy_editor.dart';
|
||||||
|
import 'package:flowy_editor/src/service/internal_key_event_handlers/slash_handler.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('slash_handler.dart', () {
|
||||||
|
testWidgets('Presses / to trigger popup list ', (tester) async {
|
||||||
|
const text = 'Welcome to Appflowy 😁';
|
||||||
|
const lines = 3;
|
||||||
|
final editor = tester.editor;
|
||||||
|
for (var i = 0; i < lines; i++) {
|
||||||
|
editor.insertTextNode(text);
|
||||||
|
}
|
||||||
|
await editor.startTesting();
|
||||||
|
await editor.updateSelection(Selection.single(path: [1], startOffset: 0));
|
||||||
|
await editor.pressLogicKey(LogicalKeyboardKey.slash);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 1000));
|
||||||
|
|
||||||
|
expect(find.byType(PopupListWidget, skipOffstage: false), findsOneWidget);
|
||||||
|
|
||||||
|
for (final item in popupListItems) {
|
||||||
|
expect(find.byWidget(item.icon), findsOneWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
await editor.updateSelection(Selection.single(path: [1], startOffset: 0));
|
||||||
|
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||||
|
|
||||||
|
expect(find.byType(PopupListWidget, skipOffstage: false), findsNothing);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
import 'package:flowy_editor/flowy_editor.dart';
|
||||||
|
import 'package:flowy_editor/src/render/rich_text/rich_text_style.dart';
|
||||||
|
import 'package:flowy_editor/src/extensions/text_node_extensions.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('update_text_style_by_command_x_handler.dart', () {
|
||||||
|
testWidgets('Presses Command + B to update text style', (tester) async {
|
||||||
|
await _testUpdateTextStyleByCommandX(
|
||||||
|
tester,
|
||||||
|
StyleKey.bold,
|
||||||
|
LogicalKeyboardKey.keyB,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Presses Command + I to update text style', (tester) async {
|
||||||
|
await _testUpdateTextStyleByCommandX(
|
||||||
|
tester,
|
||||||
|
StyleKey.italic,
|
||||||
|
LogicalKeyboardKey.keyI,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Presses Command + U to update text style', (tester) async {
|
||||||
|
await _testUpdateTextStyleByCommandX(
|
||||||
|
tester,
|
||||||
|
StyleKey.underline,
|
||||||
|
LogicalKeyboardKey.keyU,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Presses Command + S to update text style', (tester) async {
|
||||||
|
await _testUpdateTextStyleByCommandX(
|
||||||
|
tester,
|
||||||
|
StyleKey.strikethrough,
|
||||||
|
LogicalKeyboardKey.keyS,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _testUpdateTextStyleByCommandX(
|
||||||
|
WidgetTester tester, String matchStyle, LogicalKeyboardKey key) async {
|
||||||
|
const text = 'Welcome to Appflowy 😁';
|
||||||
|
final editor = tester.editor
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(text)
|
||||||
|
..insertTextNode(text);
|
||||||
|
await editor.startTesting();
|
||||||
|
|
||||||
|
var selection =
|
||||||
|
Selection.single(path: [1], startOffset: 2, endOffset: text.length - 2);
|
||||||
|
await editor.updateSelection(selection);
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
key,
|
||||||
|
isShiftPressed: key == LogicalKeyboardKey.keyS,
|
||||||
|
isMetaPressed: true,
|
||||||
|
);
|
||||||
|
var textNode = editor.nodeAtPath([1]) as TextNode;
|
||||||
|
expect(textNode.allSatisfyInSelection(matchStyle, selection), true);
|
||||||
|
|
||||||
|
selection =
|
||||||
|
Selection.single(path: [1], startOffset: 0, endOffset: text.length);
|
||||||
|
await editor.updateSelection(selection);
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
key,
|
||||||
|
isShiftPressed: key == LogicalKeyboardKey.keyS,
|
||||||
|
isMetaPressed: true,
|
||||||
|
);
|
||||||
|
textNode = editor.nodeAtPath([1]) as TextNode;
|
||||||
|
expect(textNode.allSatisfyInSelection(matchStyle, selection), true);
|
||||||
|
|
||||||
|
await editor.updateSelection(selection);
|
||||||
|
await editor.pressLogicKey(
|
||||||
|
key,
|
||||||
|
isShiftPressed: key == LogicalKeyboardKey.keyS,
|
||||||
|
isMetaPressed: true,
|
||||||
|
);
|
||||||
|
textNode = editor.nodeAtPath([1]) as TextNode;
|
||||||
|
expect(textNode.allSatisfyInSelection(matchStyle, selection), false);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user