Browse Source

test: add app bloc test (#1370)

Nathan.fooo 2 years ago
parent
commit
2eea57aefa
21 changed files with 226 additions and 77 deletions
  1. 3 3
      frontend/app_flowy/lib/plugins/board/application/board_bloc.dart
  2. 2 2
      frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart
  3. 16 14
      frontend/app_flowy/lib/plugins/doc/application/doc_bloc.dart
  4. 1 1
      frontend/app_flowy/lib/plugins/doc/document_page.dart
  5. 3 3
      frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart
  6. 2 2
      frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart
  7. 1 1
      frontend/app_flowy/lib/plugins/grid/application/grid_service.dart
  8. 1 6
      frontend/app_flowy/lib/startup/deps_resolver.dart
  9. 21 11
      frontend/app_flowy/lib/user/application/user_listener.dart
  10. 2 2
      frontend/app_flowy/lib/user/presentation/router.dart
  11. 2 2
      frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart
  12. 5 3
      frontend/app_flowy/lib/user/presentation/splash_screen.dart
  13. 8 7
      frontend/app_flowy/lib/workspace/application/home/home_bloc.dart
  14. 1 1
      frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart
  15. 1 1
      frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart
  16. 1 1
      frontend/app_flowy/test/bloc_test/grid_test/util.dart
  17. 147 8
      frontend/app_flowy/test/bloc_test/menu_test/app_bloc_test.dart
  18. 1 1
      frontend/rust-lib/flowy-folder/src/entities/workspace.rs
  19. 3 3
      frontend/rust-lib/flowy-folder/src/event_map.rs
  20. 2 2
      frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs
  21. 3 3
      frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs

+ 3 - 3
frontend/app_flowy/lib/plugins/board/application/board_bloc.dart

@@ -70,7 +70,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
         await event.when(
           initial: () async {
             _startListening();
-            await _loadGrid(emit);
+            await _openGrid(emit);
           },
           createBottomRow: (groupId) async {
             final startRowId = groupControllers[groupId]?.lastRow()?.id;
@@ -285,8 +285,8 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
     return <AppFlowyGroupItem>[...items];
   }
 
-  Future<void> _loadGrid(Emitter<BoardState> emit) async {
-    final result = await _gridDataController.loadData();
+  Future<void> _openGrid(Emitter<BoardState> emit) async {
+    final result = await _gridDataController.openGrid();
     result.fold(
       (grid) => emit(
         state.copyWith(loadingState: GridLoadingState.finish(left(unit))),

+ 2 - 2
frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart

@@ -107,8 +107,8 @@ class BoardDataController {
     );
   }
 
-  Future<Either<Unit, FlowyError>> loadData() async {
-    final result = await _gridFFIService.loadGrid();
+  Future<Either<Unit, FlowyError>> openGrid() async {
+    final result = await _gridFFIService.openGrid();
     return Future(
       () => result.fold(
         (grid) async {

+ 16 - 14
frontend/app_flowy/lib/plugins/doc/application/doc_bloc.dart

@@ -17,19 +17,19 @@ part 'doc_bloc.freezed.dart';
 
 class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
   final ViewPB view;
-  final DocumentService service;
+  final DocumentService _documentService;
 
-  final ViewListener listener;
-  final TrashService trashService;
+  final ViewListener _listener;
+  final TrashService _trashService;
   late EditorState editorState;
   StreamSubscription? _subscription;
 
   DocumentBloc({
     required this.view,
-    required this.service,
-    required this.listener,
-    required this.trashService,
-  }) : super(DocumentState.initial()) {
+  })  : _documentService = DocumentService(),
+        _listener = ViewListener(view: view),
+        _trashService = TrashService(),
+        super(DocumentState.initial()) {
     on<DocumentEvent>((event, emit) async {
       await event.map(
         initial: (Initial value) async {
@@ -43,7 +43,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
           emit(state.copyWith(isDeleted: false));
         },
         deletePermanently: (DeletePermanently value) async {
-          final result = await trashService
+          final result = await _trashService
               .deleteViews([Tuple2(view.id, TrashType.TrashView)]);
 
           final newState = result.fold(
@@ -51,7 +51,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
           emit(newState);
         },
         restorePage: (RestorePage value) async {
-          final result = await trashService.putback(view.id);
+          final result = await _trashService.putback(view.id);
           final newState = result.fold(
               (l) => state.copyWith(isDeleted: false), (r) => state);
           emit(newState);
@@ -62,18 +62,18 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
 
   @override
   Future<void> close() async {
-    await listener.stop();
+    await _listener.stop();
 
     if (_subscription != null) {
       await _subscription?.cancel();
     }
 
-    await service.closeDocument(docId: view.id);
+    await _documentService.closeDocument(docId: view.id);
     return super.close();
   }
 
   Future<void> _initial(Initial value, Emitter<DocumentState> emit) async {
-    final result = await service.openDocument(view: view);
+    final result = await _documentService.openDocument(view: view);
     result.fold(
       (block) {
         final document = Document.fromJson(jsonDecode(block.snapshot));
@@ -96,7 +96,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
   }
 
   void _listenOnViewChange() {
-    listener.start(
+    _listener.start(
       onViewDeleted: (result) {
         result.fold(
           (view) => add(const DocumentEvent.deleted()),
@@ -115,7 +115,9 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
   void _listenOnDocumentChange() {
     _subscription = editorState.transactionStream.listen((transaction) {
       final json = jsonEncode(TransactionAdaptor(transaction).toJson());
-      service.applyEdit(docId: view.id, operations: json).then((result) {
+      _documentService
+          .applyEdit(docId: view.id, operations: json)
+          .then((result) {
         result.fold(
           (l) => null,
           (err) => Log.error(err),

+ 1 - 1
frontend/app_flowy/lib/plugins/doc/document_page.dart

@@ -30,7 +30,7 @@ class _DocumentPageState extends State<DocumentPage> {
 
   @override
   void initState() {
-    // The appflowy editor use Intl as locatization, set the default language as fallback.
+    // The appflowy editor use Intl as localization, set the default language as fallback.
     Intl.defaultLocale = 'en_US';
     documentBloc = getIt<DocumentBloc>(param1: super.widget.view)
       ..add(const DocumentEvent.initial());

+ 3 - 3
frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart

@@ -27,7 +27,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
         await event.when(
           initial: () async {
             _startListening();
-            await _loadGrid(emit);
+            await _openGrid(emit);
           },
           createRow: () {
             state.loadingState.when(
@@ -95,8 +95,8 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     );
   }
 
-  Future<void> _loadGrid(Emitter<GridState> emit) async {
-    final result = await dataController.loadData();
+  Future<void> _openGrid(Emitter<GridState> emit) async {
+    final result = await dataController.openGrid();
     result.fold(
       (grid) {
         if (_createRowOperation != null) {

+ 2 - 2
frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart

@@ -65,8 +65,8 @@ class GridDataController {
   }
 
   // Loads the rows from each block
-  Future<Either<Unit, FlowyError>> loadData() async {
-    final result = await _gridFFIService.loadGrid();
+  Future<Either<Unit, FlowyError>> openGrid() async {
+    final result = await _gridFFIService.openGrid();
     return Future(
       () => result.fold(
         (grid) async {

+ 1 - 1
frontend/app_flowy/lib/plugins/grid/application/grid_service.dart

@@ -14,7 +14,7 @@ class GridFFIService {
     required this.gridId,
   });
 
-  Future<Either<GridPB, FlowyError>> loadGrid() async {
+  Future<Either<GridPB, FlowyError>> openGrid() async {
     await FolderEventSetLatestView(ViewIdPB(value: gridId)).send();
 
     final payload = GridIdPB(value: gridId);

+ 1 - 6
frontend/app_flowy/lib/startup/deps_resolver.dart

@@ -122,12 +122,7 @@ void _resolveFolderDeps(GetIt getIt) {
 void _resolveDocDeps(GetIt getIt) {
 // Doc
   getIt.registerFactoryParam<DocumentBloc, ViewPB, void>(
-    (view, _) => DocumentBloc(
-      view: view,
-      service: DocumentService(),
-      listener: getIt<ViewListener>(param1: view),
-      trashService: getIt<TrashService>(),
-    ),
+    (view, _) => DocumentBloc(view: view),
   );
 }
 

+ 21 - 11
frontend/app_flowy/lib/user/application/user_listener.dart

@@ -10,7 +10,8 @@ import 'package:flowy_infra/notifier.dart';
 import 'package:flowy_sdk/protobuf/dart-notify/protobuf.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/dart_notification.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
-import 'package:flowy_sdk/protobuf/flowy-user/dart_notification.pb.dart' as user;
+import 'package:flowy_sdk/protobuf/flowy-user/dart_notification.pb.dart'
+    as user;
 import 'package:flowy_sdk/rust_stream.dart';
 
 typedef UserProfileNotifyValue = Either<UserProfilePB, FlowyError>;
@@ -39,7 +40,8 @@ class UserListener {
       _authNotifier?.addPublishListener(onAuthChanged);
     }
 
-    _userParser = UserNotificationParser(id: _userProfile.token, callback: _userNotificationCallback);
+    _userParser = UserNotificationParser(
+        id: _userProfile.token, callback: _userNotificationCallback);
     _subscription = RustStreamReceiver.listen((observable) {
       _userParser?.parse(observable);
     });
@@ -55,7 +57,8 @@ class UserListener {
     _authNotifier = null;
   }
 
-  void _userNotificationCallback(user.UserNotification ty, Either<Uint8List, FlowyError> result) {
+  void _userNotificationCallback(
+      user.UserNotification ty, Either<Uint8List, FlowyError> result) {
     switch (ty) {
       case user.UserNotification.UserUnauthorized:
         result.fold(
@@ -65,7 +68,8 @@ class UserListener {
         break;
       case user.UserNotification.UserProfileUpdated:
         result.fold(
-          (payload) => _profileNotifier?.value = left(UserProfilePB.fromBuffer(payload)),
+          (payload) =>
+              _profileNotifier?.value = left(UserProfilePB.fromBuffer(payload)),
           (error) => _profileNotifier?.value = right(error),
         );
         break;
@@ -76,12 +80,14 @@ class UserListener {
 }
 
 typedef WorkspaceListNotifyValue = Either<List<WorkspacePB>, FlowyError>;
-typedef WorkspaceSettingNotifyValue = Either<CurrentWorkspaceSettingPB, FlowyError>;
+typedef WorkspaceSettingNotifyValue = Either<WorkspaceSettingPB, FlowyError>;
 
 class UserWorkspaceListener {
   PublishNotifier<AuthNotifyValue>? _authNotifier = PublishNotifier();
-  PublishNotifier<WorkspaceListNotifyValue>? _workspacesChangedNotifier = PublishNotifier();
-  PublishNotifier<WorkspaceSettingNotifyValue>? _settingChangedNotifier = PublishNotifier();
+  PublishNotifier<WorkspaceListNotifyValue>? _workspacesChangedNotifier =
+      PublishNotifier();
+  PublishNotifier<WorkspaceSettingNotifyValue>? _settingChangedNotifier =
+      PublishNotifier();
 
   FolderNotificationListener? _listener;
   final UserProfilePB _userProfile;
@@ -113,26 +119,30 @@ class UserWorkspaceListener {
     );
   }
 
-  void _handleObservableType(FolderNotification ty, Either<Uint8List, FlowyError> result) {
+  void _handleObservableType(
+      FolderNotification ty, Either<Uint8List, FlowyError> result) {
     switch (ty) {
       case FolderNotification.UserCreateWorkspace:
       case FolderNotification.UserDeleteWorkspace:
       case FolderNotification.WorkspaceListUpdated:
         result.fold(
-          (payload) => _workspacesChangedNotifier?.value = left(RepeatedWorkspacePB.fromBuffer(payload).items),
+          (payload) => _workspacesChangedNotifier?.value =
+              left(RepeatedWorkspacePB.fromBuffer(payload).items),
           (error) => _workspacesChangedNotifier?.value = right(error),
         );
         break;
       case FolderNotification.WorkspaceSetting:
         result.fold(
-          (payload) => _settingChangedNotifier?.value = left(CurrentWorkspaceSettingPB.fromBuffer(payload)),
+          (payload) => _settingChangedNotifier?.value =
+              left(WorkspaceSettingPB.fromBuffer(payload)),
           (error) => _settingChangedNotifier?.value = right(error),
         );
         break;
       case FolderNotification.UserUnauthorized:
         result.fold(
           (_) {},
-          (error) => _authNotifier?.value = right(FlowyError.create()..code = ErrorCode.UserUnauthorized.value),
+          (error) => _authNotifier?.value = right(
+              FlowyError.create()..code = ErrorCode.UserUnauthorized.value),
         );
         break;
       default:

+ 2 - 2
frontend/app_flowy/lib/user/presentation/router.dart

@@ -29,7 +29,7 @@ class AuthRouter {
   }
 
   void pushHomeScreen(BuildContext context, UserProfilePB profile,
-      CurrentWorkspaceSettingPB workspaceSetting) {
+      WorkspaceSettingPB workspaceSetting) {
     Navigator.push(
       context,
       PageRoutes.fade(() => HomeScreen(profile, workspaceSetting),
@@ -54,7 +54,7 @@ class SplashRoute {
   }
 
   void pushHomeScreen(BuildContext context, UserProfilePB userProfile,
-      CurrentWorkspaceSettingPB workspaceSetting) {
+      WorkspaceSettingPB workspaceSetting) {
     Navigator.push(
       context,
       PageRoutes.fade(() => HomeScreen(userProfile, workspaceSetting),

+ 2 - 2
frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart

@@ -106,7 +106,7 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
     );
     result.fold(
       (user) {
-        FolderEventReadCurWorkspace().send().then((result) {
+        FolderEventReadCurrentWorkspace().send().then((result) {
           _openCurrentWorkspace(context, user, result);
         });
       },
@@ -119,7 +119,7 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
   void _openCurrentWorkspace(
     BuildContext context,
     UserProfilePB user,
-    dartz.Either<CurrentWorkspaceSettingPB, FlowyError> workspacesOrError,
+    dartz.Either<WorkspaceSettingPB, FlowyError> workspacesOrError,
   ) {
     workspacesOrError.fold(
       (workspaceSetting) {

+ 5 - 3
frontend/app_flowy/lib/user/presentation/splash_screen.dart

@@ -44,10 +44,11 @@ class SplashScreen extends StatelessWidget {
 
   void _handleAuthenticated(BuildContext context, Authenticated result) {
     final userProfile = result.userProfile;
-    FolderEventReadCurWorkspace().send().then(
+    FolderEventReadCurrentWorkspace().send().then(
       (result) {
         return result.fold(
-          (workspaceSetting) => getIt<SplashRoute>().pushHomeScreen(context, userProfile, workspaceSetting),
+          (workspaceSetting) => getIt<SplashRoute>()
+              .pushHomeScreen(context, userProfile, workspaceSetting),
           (error) async {
             Log.error(error);
             assert(error.code == ErrorCode.RecordNotFound.value);
@@ -80,7 +81,8 @@ class Body extends StatelessWidget {
                 fit: BoxFit.cover,
                 width: size.width,
                 height: size.height,
-                image: const AssetImage('assets/images/appflowy_launch_splash.jpg')),
+                image: const AssetImage(
+                    'assets/images/appflowy_launch_splash.jpg')),
             const CircularProgressIndicator.adaptive(),
           ],
         ),

+ 8 - 7
frontend/app_flowy/lib/workspace/application/home/home_bloc.dart

@@ -5,7 +5,7 @@ import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/workspace.pb.dart'
-    show CurrentWorkspaceSettingPB;
+    show WorkspaceSettingPB;
 import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -15,8 +15,10 @@ part 'home_bloc.freezed.dart';
 class HomeBloc extends Bloc<HomeEvent, HomeState> {
   final UserWorkspaceListener _listener;
 
-  HomeBloc(UserProfilePB user, CurrentWorkspaceSettingPB workspaceSetting)
-      : _listener = UserWorkspaceListener(userProfile: user),
+  HomeBloc(
+    UserProfilePB user,
+    WorkspaceSettingPB workspaceSetting,
+  )   : _listener = UserWorkspaceListener(userProfile: user),
         super(HomeState.initial(workspaceSetting)) {
     on<HomeEvent>(
       (event, emit) async {
@@ -115,7 +117,7 @@ class HomeEvent with _$HomeEvent {
       _ShowEditPanel;
   const factory HomeEvent.dismissEditPanel() = _DismissEditPanel;
   const factory HomeEvent.didReceiveWorkspaceSetting(
-      CurrentWorkspaceSettingPB setting) = _DidReceiveWorkspaceSetting;
+      WorkspaceSettingPB setting) = _DidReceiveWorkspaceSetting;
   const factory HomeEvent.unauthorized(String msg) = _Unauthorized;
   const factory HomeEvent.collapseMenu() = _CollapseMenu;
   const factory HomeEvent.editPanelResized(double offset) = _EditPanelResized;
@@ -129,7 +131,7 @@ class HomeState with _$HomeState {
     required bool isLoading,
     required bool forceCollapse,
     required Option<EditPanelContext> panelContext,
-    required CurrentWorkspaceSettingPB workspaceSetting,
+    required WorkspaceSettingPB workspaceSetting,
     required bool unauthorized,
     required bool isMenuCollapsed,
     required double resizeOffset,
@@ -137,8 +139,7 @@ class HomeState with _$HomeState {
     required MenuResizeType resizeType,
   }) = _HomeState;
 
-  factory HomeState.initial(CurrentWorkspaceSettingPB workspaceSetting) =>
-      HomeState(
+  factory HomeState.initial(WorkspaceSettingPB workspaceSetting) => HomeState(
         isLoading: false,
         forceCollapse: false,
         panelContext: none(),

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart

@@ -25,7 +25,7 @@ import 'menu/menu.dart';
 
 class HomeScreen extends StatefulWidget {
   final UserProfilePB user;
-  final CurrentWorkspaceSettingPB workspaceSetting;
+  final WorkspaceSettingPB workspaceSetting;
   const HomeScreen(this.user, this.workspaceSetting, {Key? key})
       : super(key: key);
 

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart

@@ -34,7 +34,7 @@ import 'menu_user.dart';
 class HomeMenu extends StatelessWidget {
   final PublishNotifier<bool> _collapsedNotifier;
   final UserProfilePB user;
-  final CurrentWorkspaceSettingPB workspaceSetting;
+  final WorkspaceSettingPB workspaceSetting;
 
   const HomeMenu({
     Key? key,

+ 1 - 1
frontend/app_flowy/test/bloc_test/grid_test/util.dart

@@ -66,7 +66,7 @@ class AppFlowyGridTest {
       (view) async {
         gridView = view;
         _dataController = GridDataController(view: view);
-        final result = await _dataController.loadData();
+        final result = await _dataController.openGrid();
         result.fold((l) => null, (r) => throw Exception(r));
       },
       (error) {},

+ 147 - 8
frontend/app_flowy/test/bloc_test/menu_test/app_bloc_test.dart

@@ -1,8 +1,12 @@
+import 'package:app_flowy/plugins/board/application/board_bloc.dart';
 import 'package:app_flowy/plugins/board/board.dart';
+import 'package:app_flowy/plugins/doc/application/doc_bloc.dart';
 import 'package:app_flowy/plugins/doc/document.dart';
+import 'package:app_flowy/plugins/grid/application/grid_bloc.dart';
 import 'package:app_flowy/plugins/grid/grid.dart';
 import 'package:app_flowy/workspace/application/app/app_bloc.dart';
 import 'package:app_flowy/workspace/application/menu/menu_view_section_bloc.dart';
+import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
 import 'package:flutter_test/flutter_test.dart';
@@ -10,9 +14,9 @@ import 'package:bloc_test/bloc_test.dart';
 import '../../util.dart';
 
 void main() {
-  late AppFlowyUnitTest test;
+  late AppFlowyUnitTest testContext;
   setUpAll(() async {
-    test = await AppFlowyUnitTest.ensureInitialized();
+    testContext = await AppFlowyUnitTest.ensureInitialized();
   });
 
   group(
@@ -20,7 +24,7 @@ void main() {
     () {
       late AppPB app;
       setUp(() async {
-        app = await test.createTestApp();
+        app = await testContext.createTestApp();
       });
 
       blocTest<AppBloc, AppState>(
@@ -71,7 +75,7 @@ void main() {
   group('$AppBloc', () {
     late AppPB app;
     setUpAll(() async {
-      app = await test.createTestApp();
+      app = await testContext.createTestApp();
     });
 
     blocTest<AppBloc, AppState>(
@@ -90,7 +94,7 @@ void main() {
       wait: blocResponseDuration(),
       act: (bloc) => bloc.add(const AppEvent.delete()),
       verify: (bloc) async {
-        final apps = await test.loadApps();
+        final apps = await testContext.loadApps();
         assert(apps.where((element) => element.id == app.id).isEmpty);
       },
     );
@@ -100,7 +104,7 @@ void main() {
     late ViewPB view;
     late AppPB app;
     setUpAll(() async {
-      app = await test.createTestApp();
+      app = await testContext.createTestApp();
     });
 
     blocTest<AppBloc, AppState>(
@@ -130,7 +134,7 @@ void main() {
   group('$AppBloc', () {
     late AppPB app;
     setUpAll(() async {
-      app = await test.createTestApp();
+      app = await testContext.createTestApp();
     });
     blocTest<AppBloc, AppState>(
       "create documents' order test",
@@ -155,7 +159,7 @@ void main() {
   group('$AppBloc', () {
     late AppPB app;
     setUpAll(() async {
-      app = await test.createTestApp();
+      app = await testContext.createTestApp();
     });
     blocTest<AppBloc, AppState>(
       "reorder documents",
@@ -186,4 +190,139 @@ void main() {
       },
     );
   });
+
+  group('$AppBloc', () {
+    late AppPB app;
+    setUpAll(() async {
+      app = await testContext.createTestApp();
+    });
+    blocTest<AppBloc, AppState>(
+      "assert initial latest create view is null after initialize",
+      build: () => AppBloc(app: app)..add(const AppEvent.initial()),
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        assert(bloc.state.latestCreatedView == null);
+      },
+    );
+    blocTest<AppBloc, AppState>(
+      "create a view and assert the latest create view is this view",
+      build: () => AppBloc(app: app)..add(const AppEvent.initial()),
+      act: (bloc) async {
+        bloc.add(AppEvent.createView("1", DocumentPluginBuilder()));
+      },
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        assert(bloc.state.latestCreatedView!.id == bloc.state.views.last.id);
+      },
+    );
+
+    blocTest<AppBloc, AppState>(
+      "create a view and assert the latest create view is this view",
+      build: () => AppBloc(app: app)..add(const AppEvent.initial()),
+      act: (bloc) async {
+        bloc.add(AppEvent.createView("2", DocumentPluginBuilder()));
+      },
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        assert(bloc.state.views[0].name == "1");
+        assert(bloc.state.latestCreatedView!.id == bloc.state.views.last.id);
+      },
+    );
+    blocTest<AppBloc, AppState>(
+      "check latest create view is null after reinitialize",
+      build: () => AppBloc(app: app)..add(const AppEvent.initial()),
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        assert(bloc.state.latestCreatedView == null);
+      },
+    );
+  });
+
+  group('$AppBloc', () {
+    late AppPB app;
+    late ViewPB latestCreatedView;
+    setUpAll(() async {
+      app = await testContext.createTestApp();
+    });
+
+// Document
+    blocTest<AppBloc, AppState>(
+      "create a document view",
+      build: () => AppBloc(app: app)..add(const AppEvent.initial()),
+      act: (bloc) async {
+        bloc.add(AppEvent.createView("New document", DocumentPluginBuilder()));
+      },
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        latestCreatedView = bloc.state.views.last;
+      },
+    );
+
+    blocTest<DocumentBloc, DocumentState>(
+      "open the document",
+      build: () => DocumentBloc(view: latestCreatedView)
+        ..add(const DocumentEvent.initial()),
+      wait: blocResponseDuration(),
+    );
+
+    test('check latest opened view is this document', () async {
+      final workspaceSetting = await FolderEventReadCurrentWorkspace()
+          .send()
+          .then((result) => result.fold((l) => l, (r) => throw Exception()));
+      workspaceSetting.latestView.id == latestCreatedView.id;
+    });
+
+// Grid
+    blocTest<AppBloc, AppState>(
+      "create a grid view",
+      build: () => AppBloc(app: app)..add(const AppEvent.initial()),
+      act: (bloc) async {
+        bloc.add(AppEvent.createView("New grid", GridPluginBuilder()));
+      },
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        latestCreatedView = bloc.state.views.last;
+      },
+    );
+    blocTest<GridBloc, GridState>(
+      "open the grid",
+      build: () =>
+          GridBloc(view: latestCreatedView)..add(const GridEvent.initial()),
+      wait: blocResponseDuration(),
+    );
+
+    test('check latest opened view is this grid', () async {
+      final workspaceSetting = await FolderEventReadCurrentWorkspace()
+          .send()
+          .then((result) => result.fold((l) => l, (r) => throw Exception()));
+      workspaceSetting.latestView.id == latestCreatedView.id;
+    });
+
+// Board
+    blocTest<AppBloc, AppState>(
+      "create a board view",
+      build: () => AppBloc(app: app)..add(const AppEvent.initial()),
+      act: (bloc) async {
+        bloc.add(AppEvent.createView("New board", BoardPluginBuilder()));
+      },
+      wait: blocResponseDuration(),
+      verify: (bloc) {
+        latestCreatedView = bloc.state.views.last;
+      },
+    );
+
+    blocTest<BoardBloc, BoardState>(
+      "open the board",
+      build: () =>
+          BoardBloc(view: latestCreatedView)..add(const BoardEvent.initial()),
+      wait: blocResponseDuration(),
+    );
+
+    test('check latest opened view is this board', () async {
+      final workspaceSetting = await FolderEventReadCurrentWorkspace()
+          .send()
+          .then((result) => result.fold((l) => l, (r) => throw Exception()));
+      workspaceSetting.latestView.id == latestCreatedView.id;
+    });
+  });
 }

+ 1 - 1
frontend/rust-lib/flowy-folder/src/entities/workspace.rs

@@ -92,7 +92,7 @@ impl WorkspaceIdPB {
 }
 
 #[derive(Default, ProtoBuf, Clone)]
-pub struct CurrentWorkspaceSettingPB {
+pub struct WorkspaceSettingPB {
     #[pb(index = 1)]
     pub workspace: WorkspacePB,
 

+ 3 - 3
frontend/rust-lib/flowy-folder/src/event_map.rs

@@ -46,7 +46,7 @@ pub fn create(folder: Arc<FolderManager>) -> Module {
     // Workspace
     module = module
         .event(FolderEvent::CreateWorkspace, create_workspace_handler)
-        .event(FolderEvent::ReadCurWorkspace, read_cur_workspace_handler)
+        .event(FolderEvent::ReadCurrentWorkspace, read_cur_workspace_handler)
         .event(FolderEvent::ReadWorkspaces, read_workspaces_handler)
         .event(FolderEvent::OpenWorkspace, open_workspace_handler)
         .event(FolderEvent::ReadWorkspaceApps, read_workspace_apps_handler);
@@ -87,8 +87,8 @@ pub enum FolderEvent {
     #[event(input = "CreateWorkspacePayloadPB", output = "WorkspacePB")]
     CreateWorkspace = 0,
 
-    #[event(output = "CurrentWorkspaceSettingPB")]
-    ReadCurWorkspace = 1,
+    #[event(output = "WorkspaceSettingPB")]
+    ReadCurrentWorkspace = 1,
 
     #[event(input = "WorkspaceIdPB", output = "RepeatedWorkspacePB")]
     ReadWorkspaces = 2,

+ 2 - 2
frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs

@@ -221,11 +221,11 @@ pub async fn notify_workspace_setting_did_change(
             )?;
 
             let setting = match transaction.read_view(view_id) {
-                Ok(latest_view) => CurrentWorkspaceSettingPB {
+                Ok(latest_view) => WorkspaceSettingPB {
                     workspace,
                     latest_view: Some(latest_view.into()),
                 },
-                Err(_) => CurrentWorkspaceSettingPB {
+                Err(_) => WorkspaceSettingPB {
                     workspace,
                     latest_view: None,
                 },

+ 3 - 3
frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs

@@ -1,7 +1,7 @@
 use crate::entities::{
     app::RepeatedAppPB,
     view::ViewPB,
-    workspace::{CurrentWorkspaceSettingPB, RepeatedWorkspacePB, WorkspaceIdPB, *},
+    workspace::{RepeatedWorkspacePB, WorkspaceIdPB, WorkspaceSettingPB, *},
 };
 use crate::{
     dart_notification::{send_dart_notification, FolderNotification},
@@ -79,7 +79,7 @@ pub(crate) async fn read_workspaces_handler(
 #[tracing::instrument(level = "debug", skip(folder), err)]
 pub async fn read_cur_workspace_handler(
     folder: AppData<Arc<FolderManager>>,
-) -> DataResult<CurrentWorkspaceSettingPB, FlowyError> {
+) -> DataResult<WorkspaceSettingPB, FlowyError> {
     let workspace_id = get_current_workspace()?;
     let user_id = folder.user.user_id()?;
     let params = WorkspaceIdPB {
@@ -101,7 +101,7 @@ pub async fn read_cur_workspace_handler(
         .await
         .unwrap_or(None)
         .map(|view_rev| view_rev.into());
-    let setting = CurrentWorkspaceSettingPB { workspace, latest_view };
+    let setting = WorkspaceSettingPB { workspace, latest_view };
     let _ = read_workspaces_on_server(folder, user_id, params);
     data_result(setting)
 }