Skip to content

Commit 5fbd07e

Browse files
authored
fix: potential ai_repository not init (#6388)
* fix: potential ai_repository not init * fix: assertion when deleting block * chore: remove the rule * test: add smart edit bloc test * test: add delete block test * chore: remove unused_import * fix: paste image integration test
1 parent 0a6bee6 commit 5fbd07e

File tree

11 files changed

+220
-82
lines changed

11 files changed

+220
-82
lines changed

frontend/appflowy_flutter/integration_test/cloud/appflowy_cloud_auth_test.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,21 @@ void main() {
8686
// Open the setting page and sign out
8787
await tester.openSettings();
8888
await tester.openSettingsPage(SettingsPage.cloud);
89+
await tester.pumpAndSettle();
8990

9091
// the switch should be on by default
9192
tester.assertAppFlowyCloudEnableSyncSwitchValue(true);
9293
await tester.toggleEnableSync(AppFlowyCloudEnableSync);
94+
// wait for the switch animation
95+
await tester.wait(250);
9396

9497
// the switch should be off
9598
tester.assertAppFlowyCloudEnableSyncSwitchValue(false);
9699

97100
// the switch should be on after toggling
98101
await tester.toggleEnableSync(AppFlowyCloudEnableSync);
99102
tester.assertAppFlowyCloudEnableSyncSwitchValue(true);
103+
await tester.wait(250);
100104
});
101105
});
102106
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import 'package:appflowy/env/cloud_env.dart';
2+
import 'package:appflowy/generated/locale_keys.g.dart';
3+
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart';
4+
import 'package:appflowy_editor/appflowy_editor.dart';
5+
import 'package:easy_localization/easy_localization.dart';
6+
import 'package:flutter/material.dart';
7+
import 'package:flutter_test/flutter_test.dart';
8+
import 'package:integration_test/integration_test.dart';
9+
10+
import '../../shared/constants.dart';
11+
import '../../shared/util.dart';
12+
13+
void main() {
14+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
15+
16+
group('document delete block: ', () {
17+
testWidgets('hover on the block and delete it', (tester) async {
18+
await tester.initializeAppFlowy(
19+
cloudType: AuthenticatorType.appflowyCloudSelfHost,
20+
);
21+
await tester.tapGoogleLoginInButton();
22+
await tester.expectToSeeHomePageWithGetStartedPage();
23+
24+
// open getting started page
25+
await tester.openPage(Constants.gettingStartedPageName);
26+
27+
// before delete
28+
final path = [1];
29+
final beforeDeletedBlock = tester.editor.getNodeAtPath(path);
30+
31+
// hover on the block and delete it
32+
final optionButton = find.byWidgetPredicate(
33+
(widget) =>
34+
widget is DraggableOptionButton &&
35+
widget.blockComponentContext.node.path.equals(path),
36+
);
37+
38+
await tester.hoverOnWidget(
39+
optionButton,
40+
onHover: () async {
41+
// click the delete button
42+
await tester.tapButton(optionButton);
43+
},
44+
);
45+
await tester.pumpAndSettle(Durations.short1);
46+
47+
// click the delete button
48+
final deleteButton =
49+
find.findTextInFlowyText(LocaleKeys.button_delete.tr());
50+
await tester.tapButton(deleteButton);
51+
52+
// wait for the deletion
53+
await tester.pumpAndSettle(Durations.short1);
54+
55+
// check if the block is deleted
56+
final afterDeletedBlock = tester.editor.getNodeAtPath([1]);
57+
expect(afterDeletedBlock.id, isNot(equals(beforeDeletedBlock.id)));
58+
});
59+
});
60+
}

frontend/appflowy_flutter/integration_test/cloud/document/document_drag_block_test.dart

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,9 @@
1-
// ignore_for_file: unused_import
2-
3-
import 'dart:io';
4-
51
import 'package:appflowy/env/cloud_env.dart';
6-
import 'package:appflowy/generated/locale_keys.g.dart';
7-
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
8-
import 'package:appflowy/shared/feature_flags.dart';
9-
import 'package:appflowy/startup/startup.dart';
10-
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
11-
import 'package:appflowy/user/application/auth/auth_service.dart';
12-
import 'package:appflowy/workspace/application/settings/prelude.dart';
13-
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
14-
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
15-
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
16-
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
17-
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
18-
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
19-
import 'package:easy_localization/easy_localization.dart';
20-
import 'package:flowy_infra/uuid.dart';
212
import 'package:flutter/material.dart';
223
import 'package:flutter_test/flutter_test.dart';
234
import 'package:integration_test/integration_test.dart';
24-
import 'package:path/path.dart' as p;
255

266
import '../../shared/constants.dart';
27-
import '../../shared/database_test_op.dart';
28-
import '../../shared/dir.dart';
29-
import '../../shared/emoji.dart';
30-
import '../../shared/mock/mock_file_picker.dart';
317
import '../../shared/util.dart';
328

339
void main() {

frontend/appflowy_flutter/integration_test/desktop/document/document_copy_and_paste_test.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ void main() {
207207
final image = await rootBundle.load('assets/test/images/sample.png');
208208
final bytes = image.buffer.asUint8List();
209209
await tester.pasteContent(image: ('png', bytes), (editorState) {
210-
expect(editorState.document.root.children.length, 2);
210+
expect(editorState.document.root.children.length, 1);
211211
final node = editorState.getNodeAtPath([0])!;
212212
expect(node.type, ImageBlockKeys.type);
213213
expect(node.attributes[ImageBlockKeys.url], isNotNull);
@@ -218,7 +218,7 @@ void main() {
218218
final image = await rootBundle.load('assets/test/images/sample.jpeg');
219219
final bytes = image.buffer.asUint8List();
220220
await tester.pasteContent(image: ('jpeg', bytes), (editorState) {
221-
expect(editorState.document.root.children.length, 2);
221+
expect(editorState.document.root.children.length, 1);
222222
final node = editorState.getNodeAtPath([0])!;
223223
expect(node.type, ImageBlockKeys.type);
224224
expect(node.attributes[ImageBlockKeys.url], isNotNull);
@@ -280,7 +280,7 @@ void main() {
280280
html: html,
281281
image: ('png', bytes),
282282
(editorState) {
283-
expect(editorState.document.root.children.length, 2);
283+
expect(editorState.document.root.children.length, 1);
284284
final node = editorState.getNodeAtPath([0])!;
285285
expect(node.type, ImageBlockKeys.type);
286286
},
@@ -313,6 +313,7 @@ void main() {
313313
(tester) async {
314314
const url = 'https://appflowy.io';
315315
await tester.pasteContent(plainText: url, (editorState) {
316+
// the second one is the paragraph node
316317
expect(editorState.document.root.children.length, 2);
317318
final node = editorState.getNodeAtPath([0])!;
318319
expect(node.type, LinkPreviewBlockKeys.type);

frontend/appflowy_flutter/lib/plugins/document/application/document_bloc.dart

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,24 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
238238
return;
239239
}
240240

241+
if (enableDocumentInternalLog) {
242+
Log.debug(
243+
'[TransactionAdapter] 1. transaction before apply: ${transaction.hashCode}',
244+
);
245+
}
246+
241247
// apply transaction to backend
242248
await _transactionAdapter.apply(transaction, editorState);
243249

244250
// check if the document is empty.
245251
await _applyRules();
246252

253+
if (enableDocumentInternalLog) {
254+
Log.debug(
255+
'[TransactionAdapter] 4. transaction after apply: ${transaction.hashCode}',
256+
);
257+
}
258+
247259
if (!isClosed) {
248260
// ignore: invalid_use_of_visible_for_testing_member
249261
emit(state.copyWith(isDocumentEmpty: editorState.document.isEmpty));
@@ -270,25 +282,9 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
270282
Future<void> _applyRules() async {
271283
await Future.wait([
272284
_ensureAtLeastOneParagraphExists(),
273-
_ensureLastNodeIsEditable(),
274285
]);
275286
}
276287

277-
Future<void> _ensureLastNodeIsEditable() async {
278-
final editorState = state.editorState;
279-
if (editorState == null) {
280-
return;
281-
}
282-
final document = editorState.document;
283-
final lastNode = document.root.children.lastOrNull;
284-
if (lastNode == null || lastNode.delta == null) {
285-
final transaction = editorState.transaction;
286-
transaction.insertNode([document.root.children.length], paragraphNode());
287-
transaction.afterSelection = transaction.beforeSelection;
288-
await editorState.apply(transaction);
289-
}
290-
}
291-
292288
Future<void> _ensureAtLeastOneParagraphExists() async {
293289
final editorState = state.editorState;
294290
if (editorState == null) {

frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import 'package:appflowy_editor/appflowy_editor.dart'
2222
blockComponentDelta;
2323
import 'package:collection/collection.dart';
2424
import 'package:nanoid/nanoid.dart';
25-
import 'package:synchronized/synchronized.dart';
2625

2726
const _kExternalTextType = 'text';
2827

@@ -39,27 +38,20 @@ class TransactionAdapter {
3938
final DocumentService documentService;
4039
final String documentId;
4140

42-
// Controls the apply transaction flow
43-
//
44-
// Ensures transactions are applied in the correct order when dependencies exist.
45-
// Example: If transaction B depends on transaction A, A must be applied first.
46-
final Lock _applyLock = Lock();
47-
4841
Future<void> apply(Transaction transaction, EditorState editorState) async {
49-
await _applyLock.synchronized(
50-
() async {
51-
if (enableDocumentInternalLog) {
52-
Log.debug('apply transaction begin ${transaction.hashCode}');
53-
}
42+
if (enableDocumentInternalLog) {
43+
Log.debug(
44+
'[TransactionAdapter] 2. apply transaction begin ${transaction.hashCode} in $hashCode',
45+
);
46+
}
5447

55-
await _applyInternal(transaction, editorState);
48+
await _applyInternal(transaction, editorState);
5649

57-
if (enableDocumentInternalLog) {
58-
Log.debug('apply transaction end ${transaction.hashCode}');
59-
}
60-
},
61-
timeout: const Duration(seconds: 5),
62-
);
50+
if (enableDocumentInternalLog) {
51+
Log.debug(
52+
'[TransactionAdapter] 3. apply transaction end ${transaction.hashCode} in $hashCode',
53+
);
54+
}
6355
}
6456

6557
Future<void> _applyInternal(

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ class _BlockOptionButtonState extends State<BlockOptionButton> {
6868
},
6969
onClosed: () {
7070
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
71+
if (!mounted) {
72+
return;
73+
}
7174
widget.editorState.selectionType = null;
7275
widget.editorState.selection = null;
7376
widget.blockComponentState.alwaysShowActions = false;

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/image/cust
88
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_util.dart';
99
import 'package:appflowy/shared/patterns/file_type_patterns.dart';
1010
import 'package:appflowy/startup/startup.dart';
11+
import 'package:appflowy/util/default_extensions.dart';
1112
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
1213
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
1314
import 'package:appflowy_backend/log.dart';
@@ -20,13 +21,6 @@ import 'package:provider/provider.dart';
2021
import 'package:universal_platform/universal_platform.dart';
2122

2223
extension PasteFromImage on EditorState {
23-
static final supportedImageFormats = [
24-
'png',
25-
'jpeg',
26-
'gif',
27-
'webp',
28-
];
29-
3024
Future<void> dropImages(
3125
Node dropNode,
3226
List<XFile> files,
@@ -75,7 +69,7 @@ extension PasteFromImage on EditorState {
7569
return false;
7670
}
7771

78-
if (!supportedImageFormats.contains(format)) {
72+
if (!defaultImageExtensions.contains(format)) {
7973
Log.info('unsupported format: $format');
8074
if (UniversalPlatform.isMobile) {
8175
showToastNotification(
@@ -129,7 +123,6 @@ extension PasteFromImage on EditorState {
129123
await insertImageNode(path, selection: selection);
130124
}
131125

132-
await File(copyToPath).delete();
133126
return true;
134127
} catch (e) {
135128
Log.error('cannot copy image file', e);

0 commit comments

Comments
 (0)