mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: undo redo for the transforming block will raise an error (#2869)
* fix: undo redo for the transforming block will raise an error * test: add golden for editing document * test: add undo redo test
This commit is contained in:
parent
d302f6b3fb
commit
9bd629aaef
2
frontend/appflowy_flutter/.gitignore
vendored
2
frontend/appflowy_flutter/.gitignore
vendored
@ -72,3 +72,5 @@ windows/flutter/dart_ffi/
|
||||
*.env
|
||||
|
||||
coverage/
|
||||
|
||||
**/failures/*.png
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'util/util.dart';
|
||||
import '../util/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
@ -4,7 +4,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'util/util.dart';
|
||||
import '../util/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
@ -7,7 +7,7 @@ import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'util/util.dart';
|
||||
import '../util/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
@ -0,0 +1,141 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../util/ime.dart';
|
||||
import '../util/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('edit document', () {
|
||||
const location = 'appflowy';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('redo & undo', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
// create a new document called Sample
|
||||
const pageName = 'Sample';
|
||||
await tester.createNewPageWithName(ViewLayoutPB.Document, pageName);
|
||||
|
||||
// focus on the editor
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
|
||||
// insert 1. to trigger it to be a numbered list
|
||||
await tester.ime.insertText('1. ');
|
||||
expect(find.text('1.', findRichText: true), findsOneWidget);
|
||||
expect(
|
||||
tester.editor.getCurrentEditorState().getNodeAtPath([0])!.type,
|
||||
NumberedListBlockKeys.type,
|
||||
);
|
||||
|
||||
// undo
|
||||
// numbered list will be reverted to paragraph
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
expect(
|
||||
tester.editor.getCurrentEditorState().getNodeAtPath([0])!.type,
|
||||
ParagraphBlockKeys.type,
|
||||
);
|
||||
|
||||
// redo
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyZ,
|
||||
isControlPressed: Platform.isWindows || Platform.isLinux,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
expect(
|
||||
tester.editor.getCurrentEditorState().getNodeAtPath([0])!.type,
|
||||
NumberedListBlockKeys.type,
|
||||
);
|
||||
|
||||
// switch to other page and switch back
|
||||
await tester.openPage(readme);
|
||||
await tester.openPage(pageName);
|
||||
|
||||
// the numbered list should be kept
|
||||
expect(
|
||||
tester.editor.getCurrentEditorState().getNodeAtPath([0])!.type,
|
||||
NumberedListBlockKeys.type,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('write a readme document', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
// create a new document called Sample
|
||||
const pageName = 'Sample';
|
||||
await tester.createNewPageWithName(ViewLayoutPB.Document, pageName);
|
||||
|
||||
// focus on the editor
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
|
||||
// mock inputting the sample
|
||||
final lines = _sample.split('\n');
|
||||
for (final line in lines) {
|
||||
await tester.ime.insertText(line);
|
||||
await tester.ime.insertCharacter('\n');
|
||||
}
|
||||
|
||||
// switch to other page and switch back
|
||||
await tester.openPage(readme);
|
||||
await tester.openPage(pageName);
|
||||
|
||||
// this screenshots are different on different platform, so comment it out temporarily.
|
||||
// check the document
|
||||
// await expectLater(
|
||||
// find.byType(AppFlowyEditor),
|
||||
// matchesGoldenFile('document/edit_document_test.png'),
|
||||
// );
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// TODO(Lucas.Xu): there're no shorctcuts for underline, format code yet.
|
||||
const _sample = r'''
|
||||
# Heading 1
|
||||
## Heading 2
|
||||
### Heading 3
|
||||
---
|
||||
[] Highlight any text, and use the editing menu to _style_ **your** writing `however` you ~~like.~~
|
||||
|
||||
[] Type / followed by /bullet or /num to create a list.
|
||||
|
||||
[x] Click `+ New Page` button at the bottom of your sidebar to add a new page.
|
||||
|
||||
[] Click `+` next to any page title in the sidebar to quickly add a new subpage, `Document`, `Grid`, or `Kanban Board`.
|
||||
---
|
||||
* bulleted list 1
|
||||
|
||||
* bulleted list 2
|
||||
|
||||
* bulleted list 3
|
||||
bulleted list 4
|
||||
---
|
||||
1. numbered list 1
|
||||
|
||||
2. numbered list 2
|
||||
|
||||
3. numbered list 3
|
||||
numbered list 4
|
||||
---
|
||||
" quote''';
|
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
@ -1,11 +1,13 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'switch_folder_test.dart' as switch_folder_test;
|
||||
import 'document_test.dart' as document_test;
|
||||
import 'cover_image_test.dart' as cover_image_test;
|
||||
import 'document/document_test.dart' as document_test;
|
||||
import 'document/cover_image_test.dart' as cover_image_test;
|
||||
import 'share_markdown_test.dart' as share_markdown_test;
|
||||
import 'import_files_test.dart' as import_files_test;
|
||||
import 'document_with_database_test.dart' as document_with_database_test;
|
||||
import 'document/document_with_database_test.dart'
|
||||
as document_with_database_test;
|
||||
import 'document/edit_document_test.dart' as edit_document_test;
|
||||
import 'database_cell_test.dart' as database_cell_test;
|
||||
import 'database_field_test.dart' as database_field_test;
|
||||
import 'database_share_test.dart' as database_share_test;
|
||||
@ -27,11 +29,14 @@ import 'database_sort_test.dart' as database_sort_test;
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
switch_folder_test.main();
|
||||
cover_image_test.main();
|
||||
document_test.main();
|
||||
share_markdown_test.main();
|
||||
import_files_test.main();
|
||||
|
||||
// Document integration tests
|
||||
cover_image_test.main();
|
||||
document_test.main();
|
||||
document_with_database_test.main();
|
||||
edit_document_test.main();
|
||||
|
||||
// Database integration tests
|
||||
database_cell_test.main();
|
||||
|
@ -11,6 +11,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'util.dart';
|
||||
@ -244,6 +245,42 @@ extension CommonOperations on WidgetTester {
|
||||
);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> simulateKeyEvent(
|
||||
LogicalKeyboardKey key, {
|
||||
bool isControlPressed = false,
|
||||
bool isShiftPressed = false,
|
||||
bool isAltPressed = false,
|
||||
bool isMetaPressed = false,
|
||||
}) async {
|
||||
if (isControlPressed) {
|
||||
await simulateKeyDownEvent(LogicalKeyboardKey.control);
|
||||
}
|
||||
if (isShiftPressed) {
|
||||
await simulateKeyDownEvent(LogicalKeyboardKey.shift);
|
||||
}
|
||||
if (isAltPressed) {
|
||||
await simulateKeyDownEvent(LogicalKeyboardKey.alt);
|
||||
}
|
||||
if (isMetaPressed) {
|
||||
await simulateKeyDownEvent(LogicalKeyboardKey.meta);
|
||||
}
|
||||
await simulateKeyDownEvent(key);
|
||||
await simulateKeyUpEvent(key);
|
||||
if (isControlPressed) {
|
||||
await simulateKeyUpEvent(LogicalKeyboardKey.control);
|
||||
}
|
||||
if (isShiftPressed) {
|
||||
await simulateKeyUpEvent(LogicalKeyboardKey.shift);
|
||||
}
|
||||
if (isAltPressed) {
|
||||
await simulateKeyUpEvent(LogicalKeyboardKey.alt);
|
||||
}
|
||||
if (isMetaPressed) {
|
||||
await simulateKeyUpEvent(LogicalKeyboardKey.meta);
|
||||
}
|
||||
await pumpAndSettle();
|
||||
}
|
||||
}
|
||||
|
||||
extension ViewLayoutPBTest on ViewLayoutPB {
|
||||
|
@ -13,6 +13,12 @@ class EditorOperations {
|
||||
|
||||
final WidgetTester tester;
|
||||
|
||||
EditorState getCurrentEditorState() {
|
||||
return tester
|
||||
.widget<AppFlowyEditor>(find.byType(AppFlowyEditor))
|
||||
.editorState;
|
||||
}
|
||||
|
||||
/// Tap the line of editor at [index]
|
||||
Future<void> tapLineOfEditorAt(int index) async {
|
||||
final textBlocks = find.byType(TextBlockComponentWidget);
|
||||
|
@ -9,7 +9,7 @@ import 'package:appflowy/plugins/document/application/doc_service.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart'
|
||||
show EditorState, LogLevel;
|
||||
show EditorState, LogLevel, TransactionTime;
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@ -149,8 +149,12 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
this.editorState = editorState;
|
||||
|
||||
// subscribe to the document change from the editor
|
||||
_subscription = editorState.transactionStream.listen((transaction) async {
|
||||
await _transactionAdapter.apply(transaction, editorState);
|
||||
_subscription = editorState.transactionStream.listen((event) async {
|
||||
final time = event.$1;
|
||||
if (time != TransactionTime.before) {
|
||||
return;
|
||||
}
|
||||
await _transactionAdapter.apply(event.$2, editorState);
|
||||
});
|
||||
|
||||
// output the log from the editor when debug mode
|
||||
|
@ -117,12 +117,13 @@ extension NodeToBlock on Node {
|
||||
BlockPB toBlock({
|
||||
String? parentId,
|
||||
String? childrenId,
|
||||
Attributes? attributes,
|
||||
}) {
|
||||
assert(id.isNotEmpty);
|
||||
final block = BlockPB.create()
|
||||
..id = id
|
||||
..ty = type
|
||||
..data = _dataAdapter(type, attributes);
|
||||
..data = _dataAdapter(type, attributes ?? this.attributes);
|
||||
if (childrenId != null && childrenId.isNotEmpty) {
|
||||
block.childrenId = childrenId;
|
||||
}
|
||||
|
@ -10,7 +10,8 @@ import 'package:appflowy_editor/appflowy_editor.dart'
|
||||
UpdateOperation,
|
||||
DeleteOperation,
|
||||
PathExtensions,
|
||||
Node;
|
||||
Node,
|
||||
composeAttributes;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'dart:async';
|
||||
|
||||
@ -34,7 +35,8 @@ class TransactionAdapter {
|
||||
final actions = transaction.operations
|
||||
.map((op) => op.toBlockAction(editorState))
|
||||
.whereNotNull()
|
||||
.expand((element) => element);
|
||||
.expand((element) => element)
|
||||
.toList(growable: false); // avoid lazy evaluation
|
||||
// Log.debug('actions => $actions');
|
||||
await documentService.applyAction(
|
||||
documentId: documentId,
|
||||
@ -114,7 +116,10 @@ extension on UpdateOperation {
|
||||
node.parent?.id ?? editorState.getNodeAtPath(path.parent)?.id ?? '';
|
||||
assert(parentId.isNotEmpty);
|
||||
final payload = BlockActionPayloadPB()
|
||||
..block = node.toBlock()
|
||||
..block = node.toBlock(
|
||||
parentId: parentId,
|
||||
attributes: composeAttributes(oldAttributes, attributes),
|
||||
)
|
||||
..parentId = parentId;
|
||||
actions.add(
|
||||
BlockActionPB()
|
||||
@ -132,7 +137,9 @@ extension on DeleteOperation {
|
||||
final parentId =
|
||||
node.parent?.id ?? editorState.getNodeAtPath(path.parent)?.id ?? '';
|
||||
final payload = BlockActionPayloadPB()
|
||||
..block = node.toBlock()
|
||||
..block = node.toBlock(
|
||||
parentId: parentId,
|
||||
)
|
||||
..parentId = parentId;
|
||||
assert(parentId.isNotEmpty);
|
||||
actions.add(
|
||||
|
@ -50,7 +50,6 @@ class AppFlowyPopover extends StatelessWidget {
|
||||
offset: offset,
|
||||
popupBuilder: (context) {
|
||||
final child = popupBuilder(context);
|
||||
debugPrint("Show popover: $child");
|
||||
return _PopoverContainer(
|
||||
constraints: constraints,
|
||||
margin: margin,
|
||||
|
@ -52,10 +52,11 @@ packages:
|
||||
appflowy_editor:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: appflowy_editor
|
||||
sha256: "3ab567d8993ca06ea114c35bc38c07d2f0d7a5b00857f52d71fbe6a7f9d2ba7b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: "."
|
||||
ref: "4f83b6f"
|
||||
resolved-ref: "4f83b6feb92963f104f3f1f77a473a06aa4870f5"
|
||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||
source: git
|
||||
version: "1.0.4"
|
||||
appflowy_popover:
|
||||
dependency: "direct main"
|
||||
|
@ -42,11 +42,11 @@ dependencies:
|
||||
git:
|
||||
url: https://github.com/AppFlowy-IO/appflowy-board.git
|
||||
ref: a183c57
|
||||
appflowy_editor: ^1.0.4
|
||||
# appflowy_editor:
|
||||
# git:
|
||||
# url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||
# ref: d2460c9
|
||||
# appflowy_editor: ^1.0.4
|
||||
appflowy_editor:
|
||||
git:
|
||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||
ref: 4f83b6f
|
||||
appflowy_popover:
|
||||
path: packages/appflowy_popover
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user