mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-11-02 11:04:02 +00:00
fix: ingore keyup event in cover title (#6468)
* fix: ingore keyup event in cover title * feat: add text field with line metric * chore: refactor test strcuture * test: add arrow down key test
This commit is contained in:
parent
caa882dc37
commit
153416604d
@ -1,28 +1,18 @@
|
||||
import 'document/document_delete_block_test.dart' as document_delete_block_test;
|
||||
import 'document/document_drag_block_test.dart' as document_drag_block_test;
|
||||
import 'document/document_option_actions_test.dart'
|
||||
as document_option_actions_test;
|
||||
import 'document/document_test_runner.dart' as document_test_runner;
|
||||
import 'sidebar/sidebar_move_page_test.dart' as sidebar_move_page_test;
|
||||
import 'uncategorized/anon_user_continue_test.dart' as anon_user_continue_test;
|
||||
import 'uncategorized/appflowy_cloud_auth_test.dart'
|
||||
as appflowy_cloud_auth_test;
|
||||
import 'uncategorized/empty_test.dart' as preset_af_cloud_env_test;
|
||||
import 'uncategorized/user_setting_sync_test.dart' as user_sync_test;
|
||||
import 'uncategorized/uncategorized_test_runner.dart'
|
||||
as uncategorized_test_runner;
|
||||
import 'workspace/workspace_test_runner.dart' as workspace_test_runner;
|
||||
|
||||
Future<void> main() async {
|
||||
preset_af_cloud_env_test.main();
|
||||
appflowy_cloud_auth_test.main();
|
||||
user_sync_test.main();
|
||||
anon_user_continue_test.main();
|
||||
// uncategorized
|
||||
uncategorized_test_runner.main();
|
||||
|
||||
// workspace
|
||||
workspace_test_runner.startTesting();
|
||||
workspace_test_runner.main();
|
||||
|
||||
// document
|
||||
document_option_actions_test.main();
|
||||
document_drag_block_test.main();
|
||||
document_delete_block_test.main();
|
||||
document_test_runner.main();
|
||||
|
||||
// sidebar
|
||||
sidebar_move_page_test.main();
|
||||
|
||||
@ -1,60 +0,0 @@
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document delete block: ', () {
|
||||
testWidgets('hover on the block and delete it', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
|
||||
// before delete
|
||||
final path = [1];
|
||||
final beforeDeletedBlock = tester.editor.getNodeAtPath(path);
|
||||
|
||||
// hover on the block and delete it
|
||||
final optionButton = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is DraggableOptionButton &&
|
||||
widget.blockComponentContext.node.path.equals(path),
|
||||
);
|
||||
|
||||
await tester.hoverOnWidget(
|
||||
optionButton,
|
||||
onHover: () async {
|
||||
// click the delete button
|
||||
await tester.tapButton(optionButton);
|
||||
},
|
||||
);
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// click the delete button
|
||||
final deleteButton =
|
||||
find.findTextInFlowyText(LocaleKeys.button_delete.tr());
|
||||
await tester.tapButton(deleteButton);
|
||||
|
||||
// wait for the deletion
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// check if the block is deleted
|
||||
final afterDeletedBlock = tester.editor.getNodeAtPath([1]);
|
||||
expect(afterDeletedBlock.id, isNot(equals(beforeDeletedBlock.id)));
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -1,101 +0,0 @@
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document option actions:', () {
|
||||
testWidgets('drag block to the top', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
|
||||
// before move
|
||||
final beforeMoveBlock = tester.editor.getNodeAtPath([1]);
|
||||
|
||||
// move the desktop guide to the top, above the getting started
|
||||
await tester.editor.dragBlock(
|
||||
[1],
|
||||
const Offset(20, -80),
|
||||
);
|
||||
|
||||
// wait for the move animation to complete
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// check if the block is moved to the top
|
||||
final afterMoveBlock = tester.editor.getNodeAtPath([0]);
|
||||
expect(afterMoveBlock.delta, beforeMoveBlock.delta);
|
||||
});
|
||||
|
||||
testWidgets('drag block to other block\'s child', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
|
||||
// before move
|
||||
final beforeMoveBlock = tester.editor.getNodeAtPath([10]);
|
||||
|
||||
// move the checkbox to the child of the block at path [9]
|
||||
await tester.editor.dragBlock(
|
||||
[10],
|
||||
const Offset(80, -30),
|
||||
);
|
||||
|
||||
// wait for the move animation to complete
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// check if the block is moved to the child of the block at path [9]
|
||||
final afterMoveBlock = tester.editor.getNodeAtPath([9, 0]);
|
||||
expect(afterMoveBlock.delta, beforeMoveBlock.delta);
|
||||
});
|
||||
|
||||
testWidgets('copy block link', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
|
||||
// hover and click on the option menu button beside the block component.
|
||||
await tester.editor.hoverAndClickOptionMenuButton([0]);
|
||||
|
||||
// click the copy link to block option
|
||||
await tester.tap(
|
||||
find.findTextInFlowyText(
|
||||
LocaleKeys.document_plugins_optionAction_copyLinkToBlock.tr(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// check the clipboard
|
||||
final content = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
expect(
|
||||
content?.text,
|
||||
matches(
|
||||
r'^https:\/\/appflowy\.com\/app\/[a-f0-9-]{36}\/[a-f0-9-]{36}\?blockId=[A-Za-z0-9_-]+$',
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@ -97,5 +99,48 @@ void main() {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('hover on the block and delete it', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open getting started page
|
||||
await tester.openPage(Constants.gettingStartedPageName);
|
||||
|
||||
// before delete
|
||||
final path = [1];
|
||||
final beforeDeletedBlock = tester.editor.getNodeAtPath(path);
|
||||
|
||||
// hover on the block and delete it
|
||||
final optionButton = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is DraggableOptionButton &&
|
||||
widget.blockComponentContext.node.path.equals(path),
|
||||
);
|
||||
|
||||
await tester.hoverOnWidget(
|
||||
optionButton,
|
||||
onHover: () async {
|
||||
// click the delete button
|
||||
await tester.tapButton(optionButton);
|
||||
},
|
||||
);
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// click the delete button
|
||||
final deleteButton =
|
||||
find.findTextInFlowyText(LocaleKeys.button_delete.tr());
|
||||
await tester.tapButton(deleteButton);
|
||||
|
||||
// wait for the deletion
|
||||
await tester.pumpAndSettle(Durations.short1);
|
||||
|
||||
// check if the block is deleted
|
||||
final afterDeletedBlock = tester.editor.getNodeAtPath([1]);
|
||||
expect(afterDeletedBlock.id, isNot(equals(beforeDeletedBlock.id)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'document_option_actions_test.dart' as document_option_actions_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
document_option_actions_test.main();
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
import 'anon_user_continue_test.dart' as anon_user_continue_test;
|
||||
import 'appflowy_cloud_auth_test.dart' as appflowy_cloud_auth_test;
|
||||
import 'empty_test.dart' as preset_af_cloud_env_test;
|
||||
import 'user_setting_sync_test.dart' as user_sync_test;
|
||||
|
||||
void main() async {
|
||||
preset_af_cloud_env_test.main();
|
||||
appflowy_cloud_auth_test.main();
|
||||
user_sync_test.main();
|
||||
anon_user_continue_test.main();
|
||||
}
|
||||
@ -5,7 +5,7 @@ import 'collaborative_workspace_test.dart' as collaborative_workspace_test;
|
||||
import 'share_menu_test.dart' as share_menu_test;
|
||||
import 'workspace_settings_test.dart' as workspace_settings_test;
|
||||
|
||||
void startTesting() {
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
workspace_settings_test.main();
|
||||
|
||||
@ -220,5 +220,33 @@ void main() {
|
||||
final newTitle = tester.editor.findDocumentTitle(_testDocumentName);
|
||||
expect(newTitle, findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('press arrow down key in title, check if the cursor flashes',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapAnonymousSignInButton();
|
||||
|
||||
await tester.createNewPageWithNameUnderParent();
|
||||
|
||||
final title = tester.editor.findDocumentTitle('');
|
||||
await tester.enterText(title, _testDocumentName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
|
||||
const inputText = 'Hello World';
|
||||
await tester.ime.insertText(inputText);
|
||||
|
||||
await tester.tapButton(
|
||||
tester.editor.findDocumentTitle(_testDocumentName),
|
||||
);
|
||||
await tester.simulateKeyEvent(LogicalKeyboardKey.arrowDown);
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
expect(
|
||||
editorState.selection,
|
||||
Selection.collapsed(
|
||||
Position(path: [0], offset: inputText.length),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart';
|
||||
import 'package:appflowy/shared/text_field/text_filed_with_metric_lines.dart';
|
||||
import 'package:appflowy/workspace/application/appearance_defaults.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
@ -52,6 +53,7 @@ class _InnerCoverTitleState extends State<_InnerCoverTitle> {
|
||||
late final editorContext = context.read<SharedEditorContext>();
|
||||
late final editorState = context.read<EditorState>();
|
||||
bool isTitleFocused = false;
|
||||
int lineCount = 1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -111,11 +113,11 @@ class _InnerCoverTitleState extends State<_InnerCoverTitle> {
|
||||
DefaultAppearanceSettings.getDefaultSelectionColor(context),
|
||||
),
|
||||
),
|
||||
child: TextField(
|
||||
child: TextFieldWithMetricLines(
|
||||
controller: titleTextController,
|
||||
focusNode: titleFocusNode,
|
||||
maxLines: null,
|
||||
style: fontStyle,
|
||||
onLineCountChange: (count) => lineCount = count,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
@ -175,6 +177,10 @@ class _InnerCoverTitleState extends State<_InnerCoverTitle> {
|
||||
}
|
||||
|
||||
KeyEventResult _onKeyEvent(FocusNode focusNode, KeyEvent event) {
|
||||
if (event is KeyUpEvent) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
if (event.logicalKey == LogicalKeyboardKey.enter) {
|
||||
// if enter is pressed, jump the first line of editor.
|
||||
_createNewLine();
|
||||
@ -218,7 +224,8 @@ class _InnerCoverTitleState extends State<_InnerCoverTitle> {
|
||||
final text = titleTextController.text;
|
||||
|
||||
// if the cursor is not at the end of the text, ignore the event
|
||||
if (!selection.isCollapsed || text.length != selection.extentOffset) {
|
||||
if (lineCount != 1 &&
|
||||
(!selection.isCollapsed || text.length != selection.extentOffset)) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class TextFieldWithMetricLines extends StatefulWidget {
|
||||
const TextFieldWithMetricLines({
|
||||
super.key,
|
||||
this.controller,
|
||||
this.focusNode,
|
||||
this.maxLines,
|
||||
this.style,
|
||||
this.decoration,
|
||||
this.onLineCountChange,
|
||||
});
|
||||
|
||||
final TextEditingController? controller;
|
||||
final FocusNode? focusNode;
|
||||
final int? maxLines;
|
||||
final TextStyle? style;
|
||||
final InputDecoration? decoration;
|
||||
final void Function(int count)? onLineCountChange;
|
||||
|
||||
@override
|
||||
State<TextFieldWithMetricLines> createState() =>
|
||||
_TextFieldWithMetricLinesState();
|
||||
}
|
||||
|
||||
class _TextFieldWithMetricLinesState extends State<TextFieldWithMetricLines> {
|
||||
final key = GlobalKey();
|
||||
late final controller = widget.controller ?? TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
updateDisplayedLineCount(context);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (widget.controller == null) {
|
||||
// dispose the controller if it was created by this widget
|
||||
controller.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
key: key,
|
||||
controller: widget.controller,
|
||||
focusNode: widget.focusNode,
|
||||
maxLines: widget.maxLines,
|
||||
style: widget.style,
|
||||
decoration: widget.decoration,
|
||||
onChanged: (_) => updateDisplayedLineCount(context),
|
||||
);
|
||||
}
|
||||
|
||||
// calculate the number of lines that would be displayed in the text field
|
||||
void updateDisplayedLineCount(BuildContext context) {
|
||||
if (widget.onLineCountChange == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final renderObject = key.currentContext?.findRenderObject();
|
||||
if (renderObject == null || renderObject is! RenderBox) {
|
||||
return;
|
||||
}
|
||||
|
||||
final size = renderObject.size;
|
||||
final text = controller.buildTextSpan(
|
||||
context: context,
|
||||
style: widget.style,
|
||||
withComposing: false,
|
||||
);
|
||||
final textPainter = TextPainter(
|
||||
text: text,
|
||||
textDirection: Directionality.of(context),
|
||||
);
|
||||
|
||||
textPainter.layout(minWidth: size.width, maxWidth: size.width);
|
||||
|
||||
final lines = textPainter.computeLineMetrics().length;
|
||||
widget.onLineCountChange?.call(lines);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user