fix: unable insert a reference database (#2798)

* fix: unable insert a reference database

* test: add reference database tests

* feat: set min height for document inside database
This commit is contained in:
Lucas.Xu
2023-06-15 16:33:44 +08:00
committed by GitHub
parent 95f8b2e9a4
commit d5884ad2b5
16 changed files with 271 additions and 49 deletions

View File

@ -28,7 +28,7 @@ void main() {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();
await tester.hoverOnCoverPluginAddButton(); await tester.editor.hoverOnCoverPluginAddButton();
tester.expectToSeePluginAddCoverAndIconButton(); tester.expectToSeePluginAddCoverAndIconButton();
}); });

View File

@ -0,0 +1,110 @@
import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('database view in document', () {
const location = 'database_view';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('insert a referenced grid', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await insertReferenceDatabase(tester, ViewLayoutPB.Grid);
// validate the referenced grid is inserted
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.byType(GridPage),
),
findsOneWidget,
);
});
testWidgets('insert a referenced board', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await insertReferenceDatabase(tester, ViewLayoutPB.Board);
// validate the referenced board is inserted
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.byType(BoardPage),
),
findsOneWidget,
);
});
// testWidgets('insert a referenced calendar', (tester) async {
// await tester.initializeAppFlowy();
// await tester.tapGoButton();
// await insertReferenceDatabase(tester, ViewLayoutPB.Calendar);
// // validate the referenced grid is inserted
// expect(
// find.descendant(
// of: find.byType(AppFlowyEditor),
// matching: find.byType(CalendarPage),
// ),
// findsOneWidget,
// );
// });
});
}
/// Insert a referenced database of [layout] into the document
Future<void> insertReferenceDatabase(
WidgetTester tester,
ViewLayoutPB layout,
) async {
// create a new grid
final id = uuid();
final name = '${layout.name}_$id';
await tester.createNewPageWithName(
layout,
name,
);
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
'insert_a_reference_${layout.name}',
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a referenced grid
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
layout.referencedMenuName,
);
final linkToPageMenu = find.byType(LinkToPageMenu);
expect(linkToPageMenu, findsOneWidget);
final referencedDatabase = find.descendant(
of: linkToPageMenu,
matching: find.findTextInFlowyText(name),
);
expect(referencedDatabase, findsOneWidget);
await tester.tapButton(referencedDatabase);
}

View File

@ -5,6 +5,7 @@ import 'document_test.dart' as document_test;
import 'cover_image_test.dart' as cover_image_test; import 'cover_image_test.dart' as cover_image_test;
import 'share_markdown_test.dart' as share_markdown_test; import 'share_markdown_test.dart' as share_markdown_test;
import 'import_files_test.dart' as import_files_test; import 'import_files_test.dart' as import_files_test;
import 'document_with_database_test.dart' as document_with_database_test;
/// The main task runner for all integration tests in AppFlowy. /// The main task runner for all integration tests in AppFlowy.
/// ///
@ -20,6 +21,7 @@ void main() {
document_test.main(); document_test.main();
share_markdown_test.main(); share_markdown_test.main();
import_files_test.main(); import_files_test.main();
document_with_database_test.main();
// board_test.main(); // board_test.main();
// empty_document_test.main(); // empty_document_test.main();
// smart_menu_test.main(); // smart_menu_test.main();

View File

@ -126,3 +126,11 @@ extension AppFlowyTestBase on WidgetTester {
return; return;
} }
} }
extension AppFlowyFinderTestBase on CommonFinders {
Finder findTextInFlowyText(String text) {
return find.byWidgetPredicate(
(widget) => widget is FlowyText && widget.title == text,
);
}
}

View File

@ -7,7 +7,7 @@ import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
import 'package:appflowy/workspace/presentation/home/menu/app/header/add_button.dart'; import 'package:appflowy/workspace/presentation/home/menu/app/header/add_button.dart';
import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart'; import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart'; import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -112,20 +112,32 @@ extension CommonOperations on WidgetTester {
Future<void> hoverOnWidget( Future<void> hoverOnWidget(
Finder finder, { Finder finder, {
Offset? offset, Offset? offset,
Future<void> Function()? onHover,
}) async { }) async {
try { try {
final gesture = await createGesture(kind: PointerDeviceKind.mouse); final gesture = await createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(location: Offset.zero); await gesture.addPointer(location: Offset.zero);
addTearDown(gesture.removePointer);
await pump(); await pump();
await gesture.moveTo(offset ?? getCenter(finder)); await gesture.moveTo(offset ?? getCenter(finder));
await pumpAndSettle(); await pumpAndSettle();
} catch (_) {} await onHover?.call();
await gesture.removePointer();
} catch (err) {
Log.error('hoverOnWidget error: $err');
}
} }
/// Hover on the page name. /// Hover on the page name.
Future<void> hoverOnPageName(String name) async { Future<void> hoverOnPageName(
await hoverOnWidget(findPageName(name)); String name, {
Future<void> Function()? onHover,
bool useLast = true,
}) async {
if (useLast) {
await hoverOnWidget(findPageName(name).last, onHover: onHover);
} else {
await hoverOnWidget(findPageName(name).first, onHover: onHover);
}
} }
/// Tap the ... button beside the page name. /// Tap the ... button beside the page name.
@ -137,24 +149,18 @@ extension CommonOperations on WidgetTester {
} }
/// Tap the delete page button. /// Tap the delete page button.
///
/// Must call [tapPageOptionButton] first.
Future<void> tapDeletePageButton() async { Future<void> tapDeletePageButton() async {
await tapPageOptionButton(); await tapPageOptionButton();
await tapButtonWithName(ViewDisclosureAction.delete.name); await tapButtonWithName(ViewDisclosureAction.delete.name);
} }
/// Tap the rename page button. /// Tap the rename page button.
///
/// Must call [tapPageOptionButton] first.
Future<void> tapRenamePageButton() async { Future<void> tapRenamePageButton() async {
await tapPageOptionButton(); await tapPageOptionButton();
await tapButtonWithName(ViewDisclosureAction.rename.name); await tapButtonWithName(ViewDisclosureAction.rename.name);
} }
/// Rename the page. /// Rename the page.
///
/// Must call [tapPageOptionButton] first.
Future<void> renamePage(String name) async { Future<void> renamePage(String name) async {
await tapRenamePageButton(); await tapRenamePageButton();
await enterText(find.byType(TextFormField), name); await enterText(find.byType(TextFormField), name);
@ -208,14 +214,50 @@ extension CommonOperations on WidgetTester {
await tapButton(markdownButton); await tapButton(markdownButton);
} }
/// Hover on cover plugin button above the document Future<void> createNewPageWithName(ViewLayoutPB layout, String name) async {
Future<void> hoverOnCoverPluginAddButton() async { // create a new page
final editor = find.byWidgetPredicate( await tapAddButton();
(widget) => widget is AppFlowyEditor, await tapButtonWithName(layout.menuName);
); await pumpAndSettle();
await hoverOnWidget(
editor, // hover on it and change it's name
offset: getTopLeft(editor).translate(20, 20), await hoverOnPageName(
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
onHover: () async {
await renamePage(name);
await pumpAndSettle();
},
); );
await pumpAndSettle();
}
}
extension ViewLayoutPBTest on ViewLayoutPB {
String get menuName {
switch (this) {
case ViewLayoutPB.Grid:
return LocaleKeys.grid_menuName.tr();
case ViewLayoutPB.Board:
return LocaleKeys.board_menuName.tr();
case ViewLayoutPB.Document:
return LocaleKeys.document_menuName.tr();
case ViewLayoutPB.Calendar:
return LocaleKeys.calendar_menuName.tr();
default:
throw UnsupportedError('Unsupported layout: $this');
}
}
String get referencedMenuName {
switch (this) {
case ViewLayoutPB.Grid:
return LocaleKeys.document_plugins_referencedGrid.tr();
case ViewLayoutPB.Board:
return LocaleKeys.document_plugins_referencedBoard.tr();
case ViewLayoutPB.Calendar:
return LocaleKeys.document_plugins_referencedCalendar.tr();
default:
throw UnsupportedError('Unsupported layout: $this');
}
} }
} }

View File

@ -0,0 +1,45 @@
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:flutter_test/flutter_test.dart';
import 'ime.dart';
import 'util.dart';
extension EditorWidgetTester on WidgetTester {
EditorOperations get editor => EditorOperations(this);
}
class EditorOperations {
const EditorOperations(this.tester);
final WidgetTester tester;
/// Tap the line of editor at [index]
Future<void> tapLineOfEditorAt(int index) async {
final textBlocks = find.byType(TextBlockComponentWidget);
await tester.tapAt(tester.getTopRight(textBlocks.at(index)));
}
/// Hover on cover plugin button above the document
Future<void> hoverOnCoverPluginAddButton() async {
final editor = find.byWidgetPredicate(
(widget) => widget is AppFlowyEditor,
);
await tester.hoverOnWidget(
editor,
offset: tester.getTopLeft(editor).translate(20, 20),
);
}
/// trigger the slash command (selection menu)
Future<void> showSlashMenu() async {
await tester.ime.insertCharacter('/');
}
/// Tap the slash menu item with [name]
///
/// Must call [showSlashMenu] first.
Future<void> tapSlashMenuItemWithName(String name) async {
final slashMenuItem = find.text(name, findRichText: true);
await tester.tapButton(slashMenuItem);
}
}

View File

@ -3,3 +3,4 @@ export 'common_operations.dart';
export 'settings.dart'; export 'settings.dart';
export 'data.dart'; export 'data.dart';
export 'expectation.dart'; export 'expectation.dart';
export 'editor_test_operations.dart';

View File

@ -1,6 +1,7 @@
import 'package:appflowy/plugins/database_view/grid/application/row/row_document_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/application/row/row_document_bloc.dart';
import 'package:appflowy/plugins/document/application/doc_bloc.dart'; import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart'; import 'package:appflowy/plugins/document/presentation/editor_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_infra_ui/widget/error_page.dart';
@ -102,11 +103,18 @@ class _RowEditorState extends State<RowEditor> {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return IntrinsicHeight( return IntrinsicHeight(
child: Container(
constraints: const BoxConstraints(minHeight: 300),
child: AppFlowyEditorPage( child: AppFlowyEditorPage(
shrinkWrap: true, shrinkWrap: true,
autoFocus: false, autoFocus: false,
editorState: editorState, editorState: editorState,
scrollController: widget.scrollController, scrollController: widget.scrollController,
styleCustomizer: EditorStyleCustomizer(
context: context,
padding: const EdgeInsets.symmetric(horizontal: 10),
),
),
), ),
); );
}, },

View File

@ -5,6 +5,7 @@ import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/presentation/banner.dart'; import 'package:appflowy/plugins/document/presentation/banner.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart'; import 'package:appflowy/plugins/document/presentation/editor_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/plugins/document/presentation/export_page_widget.dart'; import 'package:appflowy/plugins/document/presentation/export_page_widget.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/base64_string.dart'; import 'package:appflowy/util/base64_string.dart';
@ -90,6 +91,10 @@ class _DocumentPageState extends State<DocumentPage> {
Widget _buildEditorPage(BuildContext context, DocumentState state) { Widget _buildEditorPage(BuildContext context, DocumentState state) {
final appflowyEditorPage = AppFlowyEditorPage( final appflowyEditorPage = AppFlowyEditorPage(
editorState: editorState!, editorState: editorState!,
styleCustomizer: EditorStyleCustomizer(
context: context,
padding: const EdgeInsets.symmetric(horizontal: 50),
),
header: _buildCoverAndIcon(context), header: _buildCoverAndIcon(context),
); );
return Column( return Column(

View File

@ -17,6 +17,7 @@ class AppFlowyEditorPage extends StatefulWidget {
this.shrinkWrap = false, this.shrinkWrap = false,
this.scrollController, this.scrollController,
this.autoFocus, this.autoFocus,
required this.styleCustomizer,
}); });
final Widget? header; final Widget? header;
@ -24,6 +25,7 @@ class AppFlowyEditorPage extends StatefulWidget {
final ScrollController? scrollController; final ScrollController? scrollController;
final bool shrinkWrap; final bool shrinkWrap;
final bool? autoFocus; final bool? autoFocus;
final EditorStyleCustomizer styleCustomizer;
@override @override
State<AppFlowyEditorPage> createState() => _AppFlowyEditorPageState(); State<AppFlowyEditorPage> createState() => _AppFlowyEditorPageState();
@ -91,9 +93,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
style: styleCustomizer.selectionMenuStyleBuilder(), style: styleCustomizer.selectionMenuStyleBuilder(),
).handler; ).handler;
EditorStyleCustomizer get styleCustomizer => EditorStyleCustomizer( EditorStyleCustomizer get styleCustomizer => widget.styleCustomizer;
context: context,
);
DocumentBloc get documentBloc => context.read<DocumentBloc>(); DocumentBloc get documentBloc => context.read<DocumentBloc>();
@override @override

View File

@ -23,11 +23,13 @@ void showLinkToPageMenu(
final top = alignment == Alignment.bottomLeft ? offset.dy : null; final top = alignment == Alignment.bottomLeft ? offset.dy : null;
final bottom = alignment == Alignment.topLeft ? offset.dy : null; final bottom = alignment == Alignment.topLeft ? offset.dy : null;
keepEditorFocusNotifier.value += 1;
late OverlayEntry linkToPageMenuEntry; late OverlayEntry linkToPageMenuEntry;
linkToPageMenuEntry = FullScreenOverlayEntry( linkToPageMenuEntry = FullScreenOverlayEntry(
top: top, top: top,
bottom: bottom, bottom: bottom,
left: offset.dx, left: offset.dx,
dismissCallback: () => keepEditorFocusNotifier.value -= 1,
builder: (context) => Material( builder: (context) => Material(
color: Colors.transparent, color: Colors.transparent,
child: LinkToPageMenu( child: LinkToPageMenu(

View File

@ -4,7 +4,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/change_cover_popover.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/change_cover_popover.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/emoji_popover.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/emoji_popover.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/emoji_icon_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/emoji_icon_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart'; import 'package:appflowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg; import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
@ -164,8 +163,8 @@ class _AddCoverButtonState extends State<_AddCoverButton> {
height: widget.hasIcon ? 180 : 50.0, height: widget.hasIcon ? 180 : 50.0,
alignment: Alignment.bottomLeft, alignment: Alignment.bottomLeft,
width: double.infinity, width: double.infinity,
padding: EdgeInsets.only( padding: const EdgeInsets.only(
left: EditorStyleCustomizer.horizontalPadding + 30, left: 80,
top: 20, top: 20,
bottom: 5, bottom: 5,
), ),
@ -333,7 +332,7 @@ class _CoverImageState extends State<_CoverImage> {
), ),
hasIcon hasIcon
? Positioned( ? Positioned(
left: EditorStyleCustomizer.horizontalPadding + 30, left: 80,
bottom: !hasCover ? 30 : 40, bottom: !hasCover ? 30 : 40,
child: AppFlowyPopover( child: AppFlowyPopover(
offset: const Offset(100, 0), offset: const Offset(100, 0),
@ -416,7 +415,7 @@ class _CoverImageState extends State<_CoverImage> {
Widget _buildCoverOverlayButtons(BuildContext context) { Widget _buildCoverOverlayButtons(BuildContext context) {
return Positioned( return Positioned(
bottom: 20, bottom: 20,
right: EditorStyleCustomizer.horizontalPadding, right: 50,
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [

View File

@ -35,10 +35,12 @@ void showEmojiPickerMenu(
final top = alignment == Alignment.bottomLeft ? offset.dy : null; final top = alignment == Alignment.bottomLeft ? offset.dy : null;
final bottom = alignment == Alignment.topLeft ? offset.dy : null; final bottom = alignment == Alignment.topLeft ? offset.dy : null;
keepEditorFocusNotifier.value += 1;
final emojiPickerMenuEntry = FullScreenOverlayEntry( final emojiPickerMenuEntry = FullScreenOverlayEntry(
top: top, top: top,
bottom: bottom, bottom: bottom,
left: offset.dx, left: offset.dx,
dismissCallback: () => keepEditorFocusNotifier.value -= 1,
builder: (context) => Material( builder: (context) => Material(
child: Container( child: Container(
width: 300, width: 300,

View File

@ -8,12 +8,11 @@ import 'package:google_fonts/google_fonts.dart';
class EditorStyleCustomizer { class EditorStyleCustomizer {
EditorStyleCustomizer({ EditorStyleCustomizer({
required this.context, required this.context,
required this.padding,
}); });
static double get horizontalPadding =>
PlatformExtension.isDesktop ? 50.0 : 10.0;
final BuildContext context; final BuildContext context;
final EdgeInsets padding;
EditorStyle style() { EditorStyle style() {
if (PlatformExtension.isDesktopOrWeb) { if (PlatformExtension.isDesktopOrWeb) {
@ -28,7 +27,7 @@ class EditorStyleCustomizer {
final theme = Theme.of(context); final theme = Theme.of(context);
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize; final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
return EditorStyle.desktop( return EditorStyle.desktop(
padding: EdgeInsets.symmetric(horizontal: horizontalPadding), padding: padding,
backgroundColor: theme.colorScheme.surface, backgroundColor: theme.colorScheme.surface,
cursorColor: theme.colorScheme.primary, cursorColor: theme.colorScheme.primary,
textStyleConfiguration: TextStyleConfiguration( textStyleConfiguration: TextStyleConfiguration(
@ -65,7 +64,7 @@ class EditorStyleCustomizer {
final theme = Theme.of(context); final theme = Theme.of(context);
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize; final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
return EditorStyle.desktop( return EditorStyle.desktop(
padding: EdgeInsets.symmetric(horizontal: horizontalPadding), padding: padding,
backgroundColor: theme.colorScheme.surface, backgroundColor: theme.colorScheme.surface,
cursorColor: theme.colorScheme.primary, cursorColor: theme.colorScheme.primary,
textStyleConfiguration: TextStyleConfiguration( textStyleConfiguration: TextStyleConfiguration(

View File

@ -52,12 +52,11 @@ packages:
appflowy_editor: appflowy_editor:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." name: appflowy_editor
ref: "23bc6d2" sha256: "19c2567e23bbd8894243b2e57fa8436e3192c8dcb50c23499b6aea90a674a045"
resolved-ref: "23bc6d2f58ab7ab4ff21c507d53753de35094ec0" url: "https://pub.dev"
url: "https://github.com/AppFlowy-IO/appflowy-editor.git" source: hosted
source: git version: "1.0.3"
version: "1.0.2"
appflowy_popover: appflowy_popover:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -42,11 +42,11 @@ dependencies:
git: git:
url: https://github.com/AppFlowy-IO/appflowy-board.git url: https://github.com/AppFlowy-IO/appflowy-board.git
ref: a183c57 ref: a183c57
# appflowy_editor: ^1.0.2 appflowy_editor: ^1.0.3
appflowy_editor: # appflowy_editor:
git: # git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git # url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: 23bc6d2 # ref: d2460c9
appflowy_popover: appflowy_popover:
path: packages/appflowy_popover path: packages/appflowy_popover