mirror of
				https://github.com/AppFlowy-IO/AppFlowy.git
				synced 2025-11-04 03:54:44 +00:00 
			
		
		
		
	chore: add integration test
This commit is contained in:
		
							parent
							
								
									89647ae683
								
							
						
					
					
						commit
						72fe1d7c47
					
				@ -1,12 +1,19 @@
 | 
				
			|||||||
 | 
					import 'package:appflowy/ai/service/ai_entities.dart';
 | 
				
			||||||
import 'package:appflowy/env/cloud_env.dart';
 | 
					import 'package:appflowy/env/cloud_env.dart';
 | 
				
			||||||
import 'package:appflowy/generated/locale_keys.g.dart';
 | 
					import 'package:appflowy/generated/locale_keys.g.dart';
 | 
				
			||||||
 | 
					import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_entities.dart';
 | 
				
			||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
 | 
					import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
 | 
				
			||||||
 | 
					import 'package:appflowy_backend/protobuf/flowy-ai/entities.pbenum.dart';
 | 
				
			||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
 | 
					import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
 | 
				
			||||||
 | 
					import 'package:appflowy_editor/appflowy_editor.dart';
 | 
				
			||||||
import 'package:easy_localization/easy_localization.dart';
 | 
					import 'package:easy_localization/easy_localization.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/services.dart';
 | 
				
			||||||
import 'package:flutter_test/flutter_test.dart';
 | 
					import 'package:flutter_test/flutter_test.dart';
 | 
				
			||||||
import 'package:integration_test/integration_test.dart';
 | 
					import 'package:integration_test/integration_test.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import '../../../shared/ai_test_op.dart';
 | 
				
			||||||
import '../../../shared/constants.dart';
 | 
					import '../../../shared/constants.dart';
 | 
				
			||||||
 | 
					import '../../../shared/mock/mock_ai.dart';
 | 
				
			||||||
import '../../../shared/util.dart';
 | 
					import '../../../shared/util.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void main() {
 | 
					void main() {
 | 
				
			||||||
@ -17,6 +24,7 @@ void main() {
 | 
				
			|||||||
        (tester) async {
 | 
					        (tester) async {
 | 
				
			||||||
      await tester.initializeAppFlowy(
 | 
					      await tester.initializeAppFlowy(
 | 
				
			||||||
        cloudType: AuthenticatorType.appflowyCloudSelfHost,
 | 
					        cloudType: AuthenticatorType.appflowyCloudSelfHost,
 | 
				
			||||||
 | 
					        aiRepositoryBuilder: () => MockAIRepository(),
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      await tester.tapGoogleLoginInButton();
 | 
					      await tester.tapGoogleLoginInButton();
 | 
				
			||||||
      await tester.expectToSeeHomePageWithGetStartedPage();
 | 
					      await tester.expectToSeeHomePageWithGetStartedPage();
 | 
				
			||||||
@ -43,5 +51,159 @@ void main() {
 | 
				
			|||||||
      // expect the ai writer block is not in the document
 | 
					      // expect the ai writer block is not in the document
 | 
				
			||||||
      expect(find.byType(AiWriterBlockComponent), findsNothing);
 | 
					      expect(find.byType(AiWriterBlockComponent), findsNothing);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    testWidgets('Improve writing', (tester) async {
 | 
				
			||||||
 | 
					      await tester.initializeAppFlowy(
 | 
				
			||||||
 | 
					        cloudType: AuthenticatorType.appflowyCloudSelfHost,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      await tester.tapGoogleLoginInButton();
 | 
				
			||||||
 | 
					      await tester.expectToSeeHomePageWithGetStartedPage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const pageName = 'Document';
 | 
				
			||||||
 | 
					      await tester.createNewPageInSpace(
 | 
				
			||||||
 | 
					        spaceName: Constants.generalSpaceName,
 | 
				
			||||||
 | 
					        layout: ViewLayoutPB.Document,
 | 
				
			||||||
 | 
					        pageName: pageName,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await tester.editor.tapLineOfEditorAt(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // insert a paragraph
 | 
				
			||||||
 | 
					      final text = 'I have an apple';
 | 
				
			||||||
 | 
					      await tester.editor.tapLineOfEditorAt(0);
 | 
				
			||||||
 | 
					      await tester.ime.insertText(text);
 | 
				
			||||||
 | 
					      await tester.editor.updateSelection(
 | 
				
			||||||
 | 
					        Selection(
 | 
				
			||||||
 | 
					          start: Position(path: [0]),
 | 
				
			||||||
 | 
					          end: Position(path: [0], offset: text.length),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await tester.pumpAndSettle();
 | 
				
			||||||
 | 
					      await tester.tapButton(find.byType(ImproveWritingButton));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      final editorState = tester.editor.getCurrentEditorState();
 | 
				
			||||||
 | 
					      final document = editorState.document;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(document.root.children.length, 3);
 | 
				
			||||||
 | 
					      expect(document.root.children[1].type, ParagraphBlockKeys.type);
 | 
				
			||||||
 | 
					      expect(
 | 
				
			||||||
 | 
					        document.root.children[1].delta!.toPlainText(),
 | 
				
			||||||
 | 
					        'I have an apple and a banana',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    testWidgets('fix grammar', (tester) async {
 | 
				
			||||||
 | 
					      await tester.initializeAppFlowy(
 | 
				
			||||||
 | 
					        cloudType: AuthenticatorType.appflowyCloudSelfHost,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      await tester.tapGoogleLoginInButton();
 | 
				
			||||||
 | 
					      await tester.expectToSeeHomePageWithGetStartedPage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const pageName = 'Document';
 | 
				
			||||||
 | 
					      await tester.createNewPageInSpace(
 | 
				
			||||||
 | 
					        spaceName: Constants.generalSpaceName,
 | 
				
			||||||
 | 
					        layout: ViewLayoutPB.Document,
 | 
				
			||||||
 | 
					        pageName: pageName,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await tester.editor.tapLineOfEditorAt(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // insert a paragraph
 | 
				
			||||||
 | 
					      final text = 'We didn’t had enough money';
 | 
				
			||||||
 | 
					      await tester.editor.tapLineOfEditorAt(0);
 | 
				
			||||||
 | 
					      await tester.ime.insertText(text);
 | 
				
			||||||
 | 
					      await tester.editor.updateSelection(
 | 
				
			||||||
 | 
					        Selection(
 | 
				
			||||||
 | 
					          start: Position(path: [0]),
 | 
				
			||||||
 | 
					          end: Position(path: [0], offset: text.length),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await tester.pumpAndSettle();
 | 
				
			||||||
 | 
					      await tester.tapButton(find.byType(AiWriterToolbarActionList));
 | 
				
			||||||
 | 
					      await tester.tapButton(
 | 
				
			||||||
 | 
					        find.text(AiWriterCommand.fixSpellingAndGrammar.i18n),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      await tester.pumpAndSettle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      final editorState = tester.editor.getCurrentEditorState();
 | 
				
			||||||
 | 
					      final document = editorState.document;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(document.root.children.length, 3);
 | 
				
			||||||
 | 
					      expect(document.root.children[1].type, ParagraphBlockKeys.type);
 | 
				
			||||||
 | 
					      expect(
 | 
				
			||||||
 | 
					        document.root.children[1].delta!.toPlainText(),
 | 
				
			||||||
 | 
					        'We didn’t have enough money',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    testWidgets('ask ai', (tester) async {
 | 
				
			||||||
 | 
					      await tester.initializeAppFlowy(
 | 
				
			||||||
 | 
					        cloudType: AuthenticatorType.appflowyCloudSelfHost,
 | 
				
			||||||
 | 
					        aiRepositoryBuilder: () => MockAIRepository(
 | 
				
			||||||
 | 
					          validator: _CompletionHistoryValidator(),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      await tester.tapGoogleLoginInButton();
 | 
				
			||||||
 | 
					      await tester.expectToSeeHomePageWithGetStartedPage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const pageName = 'Document';
 | 
				
			||||||
 | 
					      await tester.createNewPageInSpace(
 | 
				
			||||||
 | 
					        spaceName: Constants.generalSpaceName,
 | 
				
			||||||
 | 
					        layout: ViewLayoutPB.Document,
 | 
				
			||||||
 | 
					        pageName: pageName,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await tester.editor.tapLineOfEditorAt(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // insert a paragraph
 | 
				
			||||||
 | 
					      final text = 'What is TPU?';
 | 
				
			||||||
 | 
					      await tester.editor.tapLineOfEditorAt(0);
 | 
				
			||||||
 | 
					      await tester.ime.insertText(text);
 | 
				
			||||||
 | 
					      await tester.editor.updateSelection(
 | 
				
			||||||
 | 
					        Selection(
 | 
				
			||||||
 | 
					          start: Position(path: [0]),
 | 
				
			||||||
 | 
					          end: Position(path: [0], offset: text.length),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await tester.pumpAndSettle();
 | 
				
			||||||
 | 
					      await tester.tapButton(find.byType(AiWriterToolbarActionList));
 | 
				
			||||||
 | 
					      await tester.tapButton(
 | 
				
			||||||
 | 
					        find.text(AiWriterCommand.userQuestion.i18n),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      await tester.pumpAndSettle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await tester.enterTextInPromptTextField("Explain the concept of TPU");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // click enter button
 | 
				
			||||||
 | 
					      await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
 | 
				
			||||||
 | 
					      await tester.pumpAndSettle(Duration(seconds: 10));
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _CompletionHistoryValidator extends StreamCompletionValidator {
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  bool validate(
 | 
				
			||||||
 | 
					    String text,
 | 
				
			||||||
 | 
					    String? objectId,
 | 
				
			||||||
 | 
					    CompletionTypePB completionType,
 | 
				
			||||||
 | 
					    PredefinedFormat? format,
 | 
				
			||||||
 | 
					    List<String> sourceIds,
 | 
				
			||||||
 | 
					    List<AiWriterRecord> history,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    assert(completionType == CompletionTypePB.UserQuestion);
 | 
				
			||||||
 | 
					    assert(
 | 
				
			||||||
 | 
					      history.length == 1,
 | 
				
			||||||
 | 
					      "expect history length is 1, but got ${history.length}",
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    assert(
 | 
				
			||||||
 | 
					      history[0].content.trim() == "What is TPU?",
 | 
				
			||||||
 | 
					      "expect history[0].content is 'What is TPU?', but got '${history[0].content.trim()}'",
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import 'dart:io';
 | 
					import 'dart:io';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:appflowy/env/cloud_env.dart';
 | 
				
			||||||
import 'package:appflowy/generated/locale_keys.g.dart';
 | 
					import 'package:appflowy/generated/locale_keys.g.dart';
 | 
				
			||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/block_menu/block_menu_button.dart';
 | 
					import 'package:appflowy/plugins/document/presentation/editor_plugins/block_menu/block_menu_button.dart';
 | 
				
			||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
 | 
					import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
 | 
				
			||||||
@ -530,6 +531,7 @@ extension on WidgetTester {
 | 
				
			|||||||
    (String, Uint8List?)? image,
 | 
					    (String, Uint8List?)? image,
 | 
				
			||||||
  }) async {
 | 
					  }) async {
 | 
				
			||||||
    await initializeAppFlowy();
 | 
					    await initializeAppFlowy();
 | 
				
			||||||
 | 
					    await useAppFlowyCloudDevelop("http://localhost");
 | 
				
			||||||
    await tapAnonymousSignInButton();
 | 
					    await tapAnonymousSignInButton();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // create a new document
 | 
					    // create a new document
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					import 'package:appflowy/ai/ai.dart';
 | 
				
			||||||
 | 
					import 'package:flutter_test/flutter_test.dart';
 | 
				
			||||||
 | 
					import 'package:extended_text_field/extended_text_field.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extension AppFlowyAITest on WidgetTester {
 | 
				
			||||||
 | 
					  Future<void> enterTextInPromptTextField(String text) async {
 | 
				
			||||||
 | 
					    // Wait for the text field to be visible
 | 
				
			||||||
 | 
					    await pumpAndSettle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Find the ExtendedTextField widget
 | 
				
			||||||
 | 
					    final textField = find.descendant(
 | 
				
			||||||
 | 
					      of: find.byType(PromptInputTextField),
 | 
				
			||||||
 | 
					      matching: find.byType(ExtendedTextField),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    expect(textField, findsOneWidget, reason: 'ExtendedTextField not found');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final widget = element(textField).widget as ExtendedTextField;
 | 
				
			||||||
 | 
					    expect(widget.enabled, isTrue, reason: 'TextField is not enabled');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    testTextInput.enterText(text);
 | 
				
			||||||
 | 
					    await pumpAndSettle(const Duration(milliseconds: 300));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import 'dart:async';
 | 
					import 'dart:async';
 | 
				
			||||||
import 'dart:io';
 | 
					import 'dart:io';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:appflowy/ai/service/appflowy_ai_service.dart';
 | 
				
			||||||
import 'package:appflowy/env/cloud_env.dart';
 | 
					import 'package:appflowy/env/cloud_env.dart';
 | 
				
			||||||
import 'package:appflowy/env/cloud_env_test.dart';
 | 
					import 'package:appflowy/env/cloud_env_test.dart';
 | 
				
			||||||
import 'package:appflowy/startup/entry_point.dart';
 | 
					import 'package:appflowy/startup/entry_point.dart';
 | 
				
			||||||
@ -20,6 +21,8 @@ import 'package:path/path.dart' as p;
 | 
				
			|||||||
import 'package:path_provider/path_provider.dart';
 | 
					import 'package:path_provider/path_provider.dart';
 | 
				
			||||||
import 'package:universal_platform/universal_platform.dart';
 | 
					import 'package:universal_platform/universal_platform.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'mock/mock_ai.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FlowyTestContext {
 | 
					class FlowyTestContext {
 | 
				
			||||||
  FlowyTestContext({required this.applicationDataDirectory});
 | 
					  FlowyTestContext({required this.applicationDataDirectory});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -33,8 +36,9 @@ extension AppFlowyTestBase on WidgetTester {
 | 
				
			|||||||
    // use to specify the application data directory, if not specified, a temporary directory will be used.
 | 
					    // use to specify the application data directory, if not specified, a temporary directory will be used.
 | 
				
			||||||
    String? dataDirectory,
 | 
					    String? dataDirectory,
 | 
				
			||||||
    Size windowSize = const Size(1600, 1200),
 | 
					    Size windowSize = const Size(1600, 1200),
 | 
				
			||||||
    AuthenticatorType? cloudType,
 | 
					 | 
				
			||||||
    String? email,
 | 
					    String? email,
 | 
				
			||||||
 | 
					    AuthenticatorType? cloudType,
 | 
				
			||||||
 | 
					    AIRepository Function()? aiRepositoryBuilder,
 | 
				
			||||||
  }) async {
 | 
					  }) async {
 | 
				
			||||||
    if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
 | 
					    if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
 | 
				
			||||||
      // Set the window size
 | 
					      // Set the window size
 | 
				
			||||||
@ -60,6 +64,10 @@ extension AppFlowyTestBase on WidgetTester {
 | 
				
			|||||||
              rustEnvs["GOTRUE_ADMIN_EMAIL"] = "admin@example.com";
 | 
					              rustEnvs["GOTRUE_ADMIN_EMAIL"] = "admin@example.com";
 | 
				
			||||||
              rustEnvs["GOTRUE_ADMIN_PASSWORD"] = "password";
 | 
					              rustEnvs["GOTRUE_ADMIN_PASSWORD"] = "password";
 | 
				
			||||||
              break;
 | 
					              break;
 | 
				
			||||||
 | 
					            case AuthenticatorType.appflowyCloudDevelop:
 | 
				
			||||||
 | 
					              rustEnvs["GOTRUE_ADMIN_EMAIL"] = "admin@example.com";
 | 
				
			||||||
 | 
					              rustEnvs["GOTRUE_ADMIN_PASSWORD"] = "password";
 | 
				
			||||||
 | 
					              break;
 | 
				
			||||||
            default:
 | 
					            default:
 | 
				
			||||||
              throw Exception("not supported");
 | 
					              throw Exception("not supported");
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@ -75,11 +83,32 @@ extension AppFlowyTestBase on WidgetTester {
 | 
				
			|||||||
                  await useLocalServer();
 | 
					                  await useLocalServer();
 | 
				
			||||||
                  break;
 | 
					                  break;
 | 
				
			||||||
                case AuthenticatorType.appflowyCloudSelfHost:
 | 
					                case AuthenticatorType.appflowyCloudSelfHost:
 | 
				
			||||||
                  await useTestSelfHostedAppFlowyCloud();
 | 
					                  await useSelfHostedAppFlowyCloud(TestEnv.afCloudUrl);
 | 
				
			||||||
                  getIt.unregister<AuthService>();
 | 
					                  getIt.unregister<AuthService>();
 | 
				
			||||||
 | 
					                  getIt.unregister<AIRepository>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                  getIt.registerFactory<AuthService>(
 | 
					                  getIt.registerFactory<AuthService>(
 | 
				
			||||||
                    () => AppFlowyCloudMockAuthService(email: email),
 | 
					                    () => AppFlowyCloudMockAuthService(email: email),
 | 
				
			||||||
                  );
 | 
					                  );
 | 
				
			||||||
 | 
					                  getIt.registerFactory<AIRepository>(
 | 
				
			||||||
 | 
					                    aiRepositoryBuilder ?? () => MockAIRepository(),
 | 
				
			||||||
 | 
					                  );
 | 
				
			||||||
 | 
					                case AuthenticatorType.appflowyCloudDevelop:
 | 
				
			||||||
 | 
					                  if (integrationMode().isDevelop) {
 | 
				
			||||||
 | 
					                    await useAppFlowyCloudDevelop("http://localhost");
 | 
				
			||||||
 | 
					                  } else {
 | 
				
			||||||
 | 
					                    await useSelfHostedAppFlowyCloud(TestEnv.afCloudUrl);
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                  getIt.unregister<AuthService>();
 | 
				
			||||||
 | 
					                  getIt.unregister<AIRepository>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                  getIt.registerFactory<AuthService>(
 | 
				
			||||||
 | 
					                    () => AppFlowyCloudMockAuthService(email: email),
 | 
				
			||||||
 | 
					                  );
 | 
				
			||||||
 | 
					                  getIt.registerFactory<AIRepository>(
 | 
				
			||||||
 | 
					                    aiRepositoryBuilder ?? () => MockAIRepository(),
 | 
				
			||||||
 | 
					                  );
 | 
				
			||||||
 | 
					                  break;
 | 
				
			||||||
                default:
 | 
					                default:
 | 
				
			||||||
                  throw Exception("not supported");
 | 
					                  throw Exception("not supported");
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
@ -275,10 +304,6 @@ extension AppFlowyFinderTestBase on CommonFinders {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Future<void> useTestSelfHostedAppFlowyCloud() async {
 | 
					 | 
				
			||||||
  await useSelfHostedAppFlowyCloudWithURL(TestEnv.afCloudUrl);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Future<String> mockApplicationDataStorage({
 | 
					Future<String> mockApplicationDataStorage({
 | 
				
			||||||
  // use to append after the application data directory
 | 
					  // use to append after the application data directory
 | 
				
			||||||
  String? pathExtension,
 | 
					  String? pathExtension,
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,112 @@
 | 
				
			|||||||
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:appflowy/ai/service/ai_entities.dart';
 | 
				
			||||||
 | 
					import 'package:appflowy/ai/service/appflowy_ai_service.dart';
 | 
				
			||||||
 | 
					import 'package:appflowy/ai/service/error.dart';
 | 
				
			||||||
 | 
					import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_entities.dart';
 | 
				
			||||||
 | 
					import 'package:appflowy_backend/protobuf/flowy-ai/entities.pbenum.dart';
 | 
				
			||||||
 | 
					import 'package:mocktail/mocktail.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					final _mockAiMap = <CompletionTypePB, Map<String, List<String>>>{
 | 
				
			||||||
 | 
					  CompletionTypePB.ImproveWriting: {
 | 
				
			||||||
 | 
					    "I have an apple": [
 | 
				
			||||||
 | 
					      "I",
 | 
				
			||||||
 | 
					      "have",
 | 
				
			||||||
 | 
					      "an",
 | 
				
			||||||
 | 
					      "apple",
 | 
				
			||||||
 | 
					      "and",
 | 
				
			||||||
 | 
					      "a",
 | 
				
			||||||
 | 
					      "banana",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  CompletionTypePB.SpellingAndGrammar: {
 | 
				
			||||||
 | 
					    "We didn’t had enough money": [
 | 
				
			||||||
 | 
					      "We",
 | 
				
			||||||
 | 
					      "didn’t",
 | 
				
			||||||
 | 
					      "have",
 | 
				
			||||||
 | 
					      "enough",
 | 
				
			||||||
 | 
					      "money",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  CompletionTypePB.UserQuestion: {
 | 
				
			||||||
 | 
					    "Explain the concept of TPU": [
 | 
				
			||||||
 | 
					      "TPU",
 | 
				
			||||||
 | 
					      "is",
 | 
				
			||||||
 | 
					      "a",
 | 
				
			||||||
 | 
					      "tensor",
 | 
				
			||||||
 | 
					      "processing",
 | 
				
			||||||
 | 
					      "unit",
 | 
				
			||||||
 | 
					      "that",
 | 
				
			||||||
 | 
					      "is",
 | 
				
			||||||
 | 
					      "designed",
 | 
				
			||||||
 | 
					      "to",
 | 
				
			||||||
 | 
					      "accelerate",
 | 
				
			||||||
 | 
					      "machine",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					abstract class StreamCompletionValidator {
 | 
				
			||||||
 | 
					  bool validate(
 | 
				
			||||||
 | 
					    String text,
 | 
				
			||||||
 | 
					    String? objectId,
 | 
				
			||||||
 | 
					    CompletionTypePB completionType,
 | 
				
			||||||
 | 
					    PredefinedFormat? format,
 | 
				
			||||||
 | 
					    List<String> sourceIds,
 | 
				
			||||||
 | 
					    List<AiWriterRecord> history,
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MockCompletionStream extends Mock implements CompletionStream {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MockAIRepository extends Mock implements AppFlowyAIService {
 | 
				
			||||||
 | 
					  MockAIRepository({this.validator});
 | 
				
			||||||
 | 
					  StreamCompletionValidator? validator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Future<(String, CompletionStream)?> streamCompletion({
 | 
				
			||||||
 | 
					    String? objectId,
 | 
				
			||||||
 | 
					    required String text,
 | 
				
			||||||
 | 
					    PredefinedFormat? format,
 | 
				
			||||||
 | 
					    List<String> sourceIds = const [],
 | 
				
			||||||
 | 
					    List<AiWriterRecord> history = const [],
 | 
				
			||||||
 | 
					    required CompletionTypePB completionType,
 | 
				
			||||||
 | 
					    required Future<void> Function() onStart,
 | 
				
			||||||
 | 
					    required Future<void> Function(String text) processMessage,
 | 
				
			||||||
 | 
					    required Future<void> Function(String text) processAssistMessage,
 | 
				
			||||||
 | 
					    required Future<void> Function() onEnd,
 | 
				
			||||||
 | 
					    required void Function(AIError error) onError,
 | 
				
			||||||
 | 
					    required void Function(LocalAIStreamingState state)
 | 
				
			||||||
 | 
					        onLocalAIStreamingStateChange,
 | 
				
			||||||
 | 
					  }) async {
 | 
				
			||||||
 | 
					    if (validator != null) {
 | 
				
			||||||
 | 
					      if (!validator!.validate(
 | 
				
			||||||
 | 
					        text,
 | 
				
			||||||
 | 
					        objectId,
 | 
				
			||||||
 | 
					        completionType,
 | 
				
			||||||
 | 
					        format,
 | 
				
			||||||
 | 
					        sourceIds,
 | 
				
			||||||
 | 
					        history,
 | 
				
			||||||
 | 
					      )) {
 | 
				
			||||||
 | 
					        throw Exception('Invalid completion');
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    final stream = MockCompletionStream();
 | 
				
			||||||
 | 
					    unawaited(
 | 
				
			||||||
 | 
					      Future(() async {
 | 
				
			||||||
 | 
					        await onStart();
 | 
				
			||||||
 | 
					        final lines = _mockAiMap[completionType]?[text.trim()];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (lines == null) {
 | 
				
			||||||
 | 
					          throw Exception('No mock ai found for $text and $completionType');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (final line in lines) {
 | 
				
			||||||
 | 
					          await processMessage('$line ');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        await onEnd();
 | 
				
			||||||
 | 
					      }),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    return ('mock_id', stream);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -21,7 +21,7 @@ enum LocalAIStreamingState {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
abstract class AIRepository {
 | 
					abstract class AIRepository {
 | 
				
			||||||
  Future<void> streamCompletion({
 | 
					  Future<(String, CompletionStream)?> streamCompletion({
 | 
				
			||||||
    String? objectId,
 | 
					    String? objectId,
 | 
				
			||||||
    required String text,
 | 
					    required String text,
 | 
				
			||||||
    PredefinedFormat? format,
 | 
					    PredefinedFormat? format,
 | 
				
			||||||
 | 
				
			|||||||
@ -167,11 +167,16 @@ Future<void> useBaseWebDomain(String? url) async {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Future<void> useSelfHostedAppFlowyCloudWithURL(String url) async {
 | 
					Future<void> useSelfHostedAppFlowyCloud(String url) async {
 | 
				
			||||||
  await _setAuthenticatorType(AuthenticatorType.appflowyCloudSelfHost);
 | 
					  await _setAuthenticatorType(AuthenticatorType.appflowyCloudSelfHost);
 | 
				
			||||||
  await _setAppFlowyCloudUrl(url);
 | 
					  await _setAppFlowyCloudUrl(url);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Future<void> useAppFlowyCloudDevelop(String url) async {
 | 
				
			||||||
 | 
					  await _setAuthenticatorType(AuthenticatorType.appflowyCloudDevelop);
 | 
				
			||||||
 | 
					  await _setAppFlowyCloudUrl(url);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Future<void> useAppFlowyBetaCloudWithURL(
 | 
					Future<void> useAppFlowyBetaCloudWithURL(
 | 
				
			||||||
  String url,
 | 
					  String url,
 | 
				
			||||||
  AuthenticatorType authenticatorType,
 | 
					  AuthenticatorType authenticatorType,
 | 
				
			||||||
 | 
				
			|||||||
@ -87,7 +87,7 @@ class _SelfHostUrlBottomSheetState extends State<SelfHostUrlBottomSheet> {
 | 
				
			|||||||
              case SelfHostUrlBottomSheetType.shareDomain:
 | 
					              case SelfHostUrlBottomSheetType.shareDomain:
 | 
				
			||||||
                await useBaseWebDomain(url);
 | 
					                await useBaseWebDomain(url);
 | 
				
			||||||
              case SelfHostUrlBottomSheetType.cloudURL:
 | 
					              case SelfHostUrlBottomSheetType.cloudURL:
 | 
				
			||||||
                await useSelfHostedAppFlowyCloudWithURL(url);
 | 
					                await useSelfHostedAppFlowyCloud(url);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            await runAppFlowy();
 | 
					            await runAppFlowy();
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
import 'dart:async';
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:appflowy/ai/ai.dart';
 | 
					import 'package:appflowy/ai/ai.dart';
 | 
				
			||||||
 | 
					import 'package:appflowy/startup/startup.dart';
 | 
				
			||||||
import 'package:appflowy/workspace/application/view/view_service.dart';
 | 
					import 'package:appflowy/workspace/application/view/view_service.dart';
 | 
				
			||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
 | 
					import 'package:appflowy_backend/dispatch/dispatch.dart';
 | 
				
			||||||
import 'package:appflowy_backend/log.dart';
 | 
					import 'package:appflowy_backend/log.dart';
 | 
				
			||||||
@ -23,15 +24,14 @@ class AiWriterCubit extends Cubit<AiWriterState> {
 | 
				
			|||||||
    this.onCreateNode,
 | 
					    this.onCreateNode,
 | 
				
			||||||
    this.onRemoveNode,
 | 
					    this.onRemoveNode,
 | 
				
			||||||
    this.onAppendToDocument,
 | 
					    this.onAppendToDocument,
 | 
				
			||||||
    AppFlowyAIService? aiService,
 | 
					  })  : _aiService = getIt<AIRepository>(),
 | 
				
			||||||
  })  : _aiService = aiService ?? AppFlowyAIService(),
 | 
					 | 
				
			||||||
        _textRobot = MarkdownTextRobot(editorState: editorState),
 | 
					        _textRobot = MarkdownTextRobot(editorState: editorState),
 | 
				
			||||||
        selectedSourcesNotifier = ValueNotifier([documentId]),
 | 
					        selectedSourcesNotifier = ValueNotifier([documentId]),
 | 
				
			||||||
        super(IdleAiWriterState());
 | 
					        super(IdleAiWriterState());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  final String documentId;
 | 
					  final String documentId;
 | 
				
			||||||
  final EditorState editorState;
 | 
					  final EditorState editorState;
 | 
				
			||||||
  final AppFlowyAIService _aiService;
 | 
					  final AIRepository _aiService;
 | 
				
			||||||
  final MarkdownTextRobot _textRobot;
 | 
					  final MarkdownTextRobot _textRobot;
 | 
				
			||||||
  final void Function()? onCreateNode;
 | 
					  final void Function()? onCreateNode;
 | 
				
			||||||
  final void Function()? onRemoveNode;
 | 
					  final void Function()? onRemoveNode;
 | 
				
			||||||
@ -295,7 +295,6 @@ class AiWriterCubit extends Cubit<AiWriterState> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final selectionText = await editorState.getMarkdownInSelection(selection);
 | 
					    final selectionText = await editorState.getMarkdownInSelection(selection);
 | 
				
			||||||
    Log.warn('[AI writer] Selection is null');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (command == AiWriterCommand.userQuestion) {
 | 
					    if (command == AiWriterCommand.userQuestion) {
 | 
				
			||||||
      records.add(
 | 
					      records.add(
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:appflowy/ai/service/appflowy_ai_service.dart';
 | 
				
			||||||
import 'package:appflowy/core/config/kv.dart';
 | 
					import 'package:appflowy/core/config/kv.dart';
 | 
				
			||||||
import 'package:appflowy/core/network_monitor.dart';
 | 
					import 'package:appflowy/core/network_monitor.dart';
 | 
				
			||||||
import 'package:appflowy/env/cloud_env.dart';
 | 
					import 'package:appflowy/env/cloud_env.dart';
 | 
				
			||||||
@ -59,6 +60,7 @@ Future<void> _resolveCloudDeps(GetIt getIt) async {
 | 
				
			|||||||
  final env = await AppFlowyCloudSharedEnv.fromEnv();
 | 
					  final env = await AppFlowyCloudSharedEnv.fromEnv();
 | 
				
			||||||
  Log.info("cloud setting: $env");
 | 
					  Log.info("cloud setting: $env");
 | 
				
			||||||
  getIt.registerFactory<AppFlowyCloudSharedEnv>(() => env);
 | 
					  getIt.registerFactory<AppFlowyCloudSharedEnv>(() => env);
 | 
				
			||||||
 | 
					  getIt.registerFactory<AIRepository>(() => AppFlowyAIService());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (isAppFlowyCloudEnabled) {
 | 
					  if (isAppFlowyCloudEnabled) {
 | 
				
			||||||
    getIt.registerSingleton(
 | 
					    getIt.registerSingleton(
 | 
				
			||||||
 | 
				
			|||||||
@ -48,7 +48,7 @@ class AppFlowyCloudURLsBloc
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            await validateUrl(state.updatedServerUrl).fold(
 | 
					            await validateUrl(state.updatedServerUrl).fold(
 | 
				
			||||||
              (url) async {
 | 
					              (url) async {
 | 
				
			||||||
                await useSelfHostedAppFlowyCloudWithURL(url);
 | 
					                await useSelfHostedAppFlowyCloud(url);
 | 
				
			||||||
                isSuccess = true;
 | 
					                isSuccess = true;
 | 
				
			||||||
              },
 | 
					              },
 | 
				
			||||||
              (err) async => emit(state.copyWith(urlError: err)),
 | 
					              (err) async => emit(state.copyWith(urlError: err)),
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@ import 'package:appflowy/ai/ai.dart';
 | 
				
			|||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_cubit.dart';
 | 
					import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_cubit.dart';
 | 
				
			||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_entities.dart';
 | 
					import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_entities.dart';
 | 
				
			||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
 | 
					import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
 | 
				
			||||||
 | 
					import 'package:appflowy/startup/startup.dart';
 | 
				
			||||||
import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart';
 | 
					import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart';
 | 
				
			||||||
import 'package:appflowy_editor/appflowy_editor.dart';
 | 
					import 'package:appflowy_editor/appflowy_editor.dart';
 | 
				
			||||||
import 'package:bloc_test/bloc_test.dart';
 | 
					import 'package:bloc_test/bloc_test.dart';
 | 
				
			||||||
@ -145,6 +146,13 @@ class _MockErrorRepository extends Mock implements AppFlowyAIService {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void registerMockRepository(AppFlowyAIService mock) {
 | 
				
			||||||
 | 
					  if (getIt.isRegistered<AIRepository>()) {
 | 
				
			||||||
 | 
					    getIt.unregister<AIRepository>();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  getIt.registerFactory<AIRepository>(() => mock);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void main() {
 | 
					void main() {
 | 
				
			||||||
  group('AIWriterCubit:', () {
 | 
					  group('AIWriterCubit:', () {
 | 
				
			||||||
    const text1 = '1. Select text to style using the toolbar menu.';
 | 
					    const text1 = '1. Select text to style using the toolbar menu.';
 | 
				
			||||||
@ -174,10 +182,10 @@ void main() {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
        final editorState = EditorState(document: document)
 | 
					        final editorState = EditorState(document: document)
 | 
				
			||||||
          ..selection = selection;
 | 
					          ..selection = selection;
 | 
				
			||||||
 | 
					        registerMockRepository(_MockAIRepository());
 | 
				
			||||||
        return AiWriterCubit(
 | 
					        return AiWriterCubit(
 | 
				
			||||||
          documentId: '',
 | 
					          documentId: '',
 | 
				
			||||||
          editorState: editorState,
 | 
					          editorState: editorState,
 | 
				
			||||||
          aiService: _MockAIRepository(),
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      act: (bloc) => bloc.register(
 | 
					      act: (bloc) => bloc.register(
 | 
				
			||||||
@ -230,10 +238,10 @@ void main() {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
        final editorState = EditorState(document: document)
 | 
					        final editorState = EditorState(document: document)
 | 
				
			||||||
          ..selection = selection;
 | 
					          ..selection = selection;
 | 
				
			||||||
 | 
					        registerMockRepository(_MockErrorRepository());
 | 
				
			||||||
        return AiWriterCubit(
 | 
					        return AiWriterCubit(
 | 
				
			||||||
          documentId: '',
 | 
					          documentId: '',
 | 
				
			||||||
          editorState: editorState,
 | 
					          editorState: editorState,
 | 
				
			||||||
          aiService: _MockErrorRepository(),
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      act: (bloc) => bloc.register(
 | 
					      act: (bloc) => bloc.register(
 | 
				
			||||||
@ -279,10 +287,10 @@ void main() {
 | 
				
			|||||||
      final editorState = EditorState(document: document)
 | 
					      final editorState = EditorState(document: document)
 | 
				
			||||||
        ..selection = selection;
 | 
					        ..selection = selection;
 | 
				
			||||||
      final aiNode = editorState.getNodeAtPath([3])!;
 | 
					      final aiNode = editorState.getNodeAtPath([3])!;
 | 
				
			||||||
 | 
					      registerMockRepository(_MockAIRepository());
 | 
				
			||||||
      final bloc = AiWriterCubit(
 | 
					      final bloc = AiWriterCubit(
 | 
				
			||||||
        documentId: '',
 | 
					        documentId: '',
 | 
				
			||||||
        editorState: editorState,
 | 
					        editorState: editorState,
 | 
				
			||||||
        aiService: _MockAIRepository(),
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      bloc.register(aiNode);
 | 
					      bloc.register(aiNode);
 | 
				
			||||||
      await blocResponseFuture();
 | 
					      await blocResponseFuture();
 | 
				
			||||||
@ -327,10 +335,10 @@ void main() {
 | 
				
			|||||||
      final editorState = EditorState(document: document)
 | 
					      final editorState = EditorState(document: document)
 | 
				
			||||||
        ..selection = selection;
 | 
					        ..selection = selection;
 | 
				
			||||||
      final aiNode = editorState.getNodeAtPath([3])!;
 | 
					      final aiNode = editorState.getNodeAtPath([3])!;
 | 
				
			||||||
 | 
					      registerMockRepository(_MockAIRepository());
 | 
				
			||||||
      final bloc = AiWriterCubit(
 | 
					      final bloc = AiWriterCubit(
 | 
				
			||||||
        documentId: '',
 | 
					        documentId: '',
 | 
				
			||||||
        editorState: editorState,
 | 
					        editorState: editorState,
 | 
				
			||||||
        aiService: _MockAIRepository(),
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      bloc.register(aiNode);
 | 
					      bloc.register(aiNode);
 | 
				
			||||||
      await blocResponseFuture();
 | 
					      await blocResponseFuture();
 | 
				
			||||||
@ -366,10 +374,10 @@ void main() {
 | 
				
			|||||||
      final editorState = EditorState(document: document)
 | 
					      final editorState = EditorState(document: document)
 | 
				
			||||||
        ..selection = selection;
 | 
					        ..selection = selection;
 | 
				
			||||||
      final aiNode = editorState.getNodeAtPath([3])!;
 | 
					      final aiNode = editorState.getNodeAtPath([3])!;
 | 
				
			||||||
 | 
					      registerMockRepository(_MockAIRepositoryLess());
 | 
				
			||||||
      final bloc = AiWriterCubit(
 | 
					      final bloc = AiWriterCubit(
 | 
				
			||||||
        documentId: '',
 | 
					        documentId: '',
 | 
				
			||||||
        editorState: editorState,
 | 
					        editorState: editorState,
 | 
				
			||||||
        aiService: _MockAIRepositoryLess(),
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      bloc.register(aiNode);
 | 
					      bloc.register(aiNode);
 | 
				
			||||||
      await blocResponseFuture();
 | 
					      await blocResponseFuture();
 | 
				
			||||||
@ -403,10 +411,10 @@ void main() {
 | 
				
			|||||||
      final editorState = EditorState(document: document)
 | 
					      final editorState = EditorState(document: document)
 | 
				
			||||||
        ..selection = selection;
 | 
					        ..selection = selection;
 | 
				
			||||||
      final aiNode = editorState.getNodeAtPath([3])!;
 | 
					      final aiNode = editorState.getNodeAtPath([3])!;
 | 
				
			||||||
 | 
					      registerMockRepository(_MockAIRepositoryMore());
 | 
				
			||||||
      final bloc = AiWriterCubit(
 | 
					      final bloc = AiWriterCubit(
 | 
				
			||||||
        documentId: '',
 | 
					        documentId: '',
 | 
				
			||||||
        editorState: editorState,
 | 
					        editorState: editorState,
 | 
				
			||||||
        aiService: _MockAIRepositoryMore(),
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      bloc.register(aiNode);
 | 
					      bloc.register(aiNode);
 | 
				
			||||||
      await blocResponseFuture();
 | 
					      await blocResponseFuture();
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user