Sfoglia il codice sorgente

test: add database event tests (#2744)

* chore: add tests

* test: add tests

* test: add tests
Nathan.fooo 1 anno fa
parent
commit
e9be37a961
26 ha cambiato i file con 834 aggiunte e 104 eliminazioni
  1. 1 1
      frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_data_persistence.dart
  2. 1 4
      frontend/appflowy_flutter/lib/plugins/database_view/application/group/group_service.dart
  3. 0 1
      frontend/appflowy_flutter/lib/plugins/database_view/application/setting/group_bloc.dart
  4. 2 2
      frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_bloc.dart
  5. 4 4
      frontend/appflowy_flutter/pubspec.lock
  6. 6 5
      frontend/appflowy_flutter/test/bloc_test/board_test/group_by_multi_select_field_test.dart
  7. 10 10
      frontend/appflowy_tauri/src-tauri/Cargo.lock
  8. 6 6
      frontend/appflowy_tauri/src-tauri/Cargo.toml
  9. 2 2
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/data_persistence.ts
  10. 10 11
      frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/group/group_controller.ts
  11. 10 10
      frontend/rust-lib/Cargo.lock
  12. 5 5
      frontend/rust-lib/Cargo.toml
  13. 1 10
      frontend/rust-lib/flowy-database2/src/entities/calendar_entities.rs
  14. 4 2
      frontend/rust-lib/flowy-database2/src/entities/database_entities.rs
  15. 3 11
      frontend/rust-lib/flowy-database2/src/entities/group_entities/group.rs
  16. 2 1
      frontend/rust-lib/flowy-database2/src/entities/row_entities.rs
  17. 6 5
      frontend/rust-lib/flowy-database2/src/entities/type_option_entities/checklist_entities.rs
  18. 1 1
      frontend/rust-lib/flowy-database2/src/entities/type_option_entities/date_entities.rs
  19. 4 2
      frontend/rust-lib/flowy-database2/src/event_handler.rs
  20. 11 2
      frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs
  21. 1 0
      frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs
  22. 22 4
      frontend/rust-lib/flowy-database2/src/services/group/configuration.rs
  23. 2 1
      frontend/rust-lib/flowy-database2/tests/database/group_test/script.rs
  24. 1 1
      frontend/rust-lib/flowy-database2/tests/database/group_test/test.rs
  25. 195 2
      frontend/rust-lib/flowy-test/src/lib.rs
  26. 524 1
      frontend/rust-lib/flowy-test/tests/database/test.rs

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/database_view/application/cell/cell_data_persistence.dart

@@ -47,7 +47,7 @@ class DateCellDataPersistence implements CellDataPersistence<DateCellData> {
   @override
   @override
   Future<Option<FlowyError>> save(DateCellData data) {
   Future<Option<FlowyError>> save(DateCellData data) {
     final payload = DateChangesetPB.create()
     final payload = DateChangesetPB.create()
-      ..cellPath = _makeCellPath(cellContext);
+      ..cellId = _makeCellPath(cellContext);
     if (data.dateTime != null) {
     if (data.dateTime != null) {
       final date = (data.dateTime!.millisecondsSinceEpoch ~/ 1000).toString();
       final date = (data.dateTime!.millisecondsSinceEpoch ~/ 1000).toString();
       payload.date = date;
       payload.date = date;

+ 1 - 4
frontend/appflowy_flutter/lib/plugins/database_view/application/group/group_service.dart

@@ -1,5 +1,4 @@
 import 'package:appflowy_backend/dispatch/dispatch.dart';
 import 'package:appflowy_backend/dispatch/dispatch.dart';
-import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/group.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
 import 'package:dartz/dartz.dart';
 import 'package:dartz/dartz.dart';
@@ -10,12 +9,10 @@ class GroupBackendService {
 
 
   Future<Either<Unit, FlowyError>> groupByField({
   Future<Either<Unit, FlowyError>> groupByField({
     required String fieldId,
     required String fieldId,
-    required FieldType fieldType,
   }) {
   }) {
     final payload = GroupByFieldPayloadPB.create()
     final payload = GroupByFieldPayloadPB.create()
       ..viewId = viewId
       ..viewId = viewId
-      ..fieldId = fieldId
-      ..fieldType = fieldType;
+      ..fieldId = fieldId;
 
 
     return DatabaseEventSetGroupByField(payload).send();
     return DatabaseEventSetGroupByField(payload).send();
   }
   }

+ 0 - 1
frontend/appflowy_flutter/lib/plugins/database_view/application/setting/group_bloc.dart

@@ -32,7 +32,6 @@ class DatabaseGroupBloc extends Bloc<DatabaseGroupEvent, DatabaseGroupState> {
           setGroupByField: (String fieldId, FieldType fieldType) async {
           setGroupByField: (String fieldId, FieldType fieldType) async {
             final result = await _groupBackendSvc.groupByField(
             final result = await _groupBackendSvc.groupByField(
               fieldId: fieldId,
               fieldId: fieldId,
-              fieldType: fieldType,
             );
             );
             result.fold((l) => null, (err) => Log.error(err));
             result.fold((l) => null, (err) => Log.error(err));
           },
           },

+ 2 - 2
frontend/appflowy_flutter/lib/plugins/database_view/board/application/board_bloc.dart

@@ -230,7 +230,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
         for (final group in updatedGroups) {
         for (final group in updatedGroups) {
           final columnController =
           final columnController =
               boardController.getGroupController(group.groupId);
               boardController.getGroupController(group.groupId);
-          columnController?.updateGroupName(group.desc);
+          columnController?.updateGroupName(group.groupName);
         }
         }
       },
       },
     );
     );
@@ -285,7 +285,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
   AppFlowyGroupData initializeGroupData(GroupPB group) {
   AppFlowyGroupData initializeGroupData(GroupPB group) {
     return AppFlowyGroupData(
     return AppFlowyGroupData(
       id: group.groupId,
       id: group.groupId,
-      name: group.desc,
+      name: group.groupName,
       items: _buildGroupItems(group),
       items: _buildGroupItems(group),
       customData: GroupData(
       customData: GroupData(
         group: group,
         group: group,

+ 4 - 4
frontend/appflowy_flutter/pubspec.lock

@@ -285,10 +285,10 @@ packages:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: csslib
       name: csslib
-      sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f"
+      sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb"
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
-    version: "0.17.3"
+    version: "1.0.0"
   dart_style:
   dart_style:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -648,10 +648,10 @@ packages:
     dependency: transitive
     dependency: transitive
     description:
     description:
       name: html
       name: html
-      sha256: "58e3491f7bf0b6a4ea5110c0c688877460d1a6366731155c4a4580e7ded773e8"
+      sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
-    version: "0.15.3"
+    version: "0.15.4"
   http:
   http:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:

+ 6 - 5
frontend/appflowy_flutter/test/bloc_test/board_test/group_by_multi_select_field_test.dart

@@ -48,8 +48,9 @@ void main() {
     );
     );
     final expectedGroupName = "No ${multiSelectField.name}";
     final expectedGroupName = "No ${multiSelectField.name}";
     assert(
     assert(
-      boardBloc.groupControllers.values.first.group.desc == expectedGroupName,
-      "Expected $expectedGroupName, but receive ${boardBloc.groupControllers.values.first.group.desc}",
+      boardBloc.groupControllers.values.first.group.groupName ==
+          expectedGroupName,
+      "Expected $expectedGroupName, but receive ${boardBloc.groupControllers.values.first.group.groupName}",
     );
     );
   });
   });
 
 
@@ -101,8 +102,8 @@ void main() {
 
 
     final groups =
     final groups =
         boardBloc.groupControllers.values.map((e) => e.group).toList();
         boardBloc.groupControllers.values.map((e) => e.group).toList();
-    assert(groups[0].desc == "No ${multiSelectField.name}");
-    assert(groups[1].desc == "B");
-    assert(groups[2].desc == "A");
+    assert(groups[0].groupName == "No ${multiSelectField.name}");
+    assert(groups[1].groupName == "B");
+    assert(groups[2].groupName == "A");
   });
   });
 }
 }

+ 10 - 10
frontend/appflowy_tauri/src-tauri/Cargo.lock

@@ -99,7 +99,7 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
 [[package]]
 [[package]]
 name = "appflowy-integrate"
 name = "appflowy-integrate"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "collab",
  "collab",
@@ -1024,7 +1024,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab"
 name = "collab"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "bytes",
  "bytes",
@@ -1042,7 +1042,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-client-ws"
 name = "collab-client-ws"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "bytes",
  "bytes",
  "collab-sync",
  "collab-sync",
@@ -1060,7 +1060,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-database"
 name = "collab-database"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "async-trait",
  "async-trait",
@@ -1086,7 +1086,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-derive"
 name = "collab-derive"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "proc-macro2",
  "proc-macro2",
  "quote",
  "quote",
@@ -1098,7 +1098,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-document"
 name = "collab-document"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "collab",
  "collab",
@@ -1115,7 +1115,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-folder"
 name = "collab-folder"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "chrono",
  "chrono",
@@ -1135,7 +1135,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-persistence"
 name = "collab-persistence"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "bincode",
  "bincode",
  "chrono",
  "chrono",
@@ -1155,7 +1155,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-plugins"
 name = "collab-plugins"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "async-trait",
  "async-trait",
@@ -1186,7 +1186,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-sync"
 name = "collab-sync"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "bytes",
  "bytes",
  "collab",
  "collab",

+ 6 - 6
frontend/appflowy_tauri/src-tauri/Cargo.toml

@@ -34,12 +34,12 @@ default = ["custom-protocol"]
 custom-protocol = ["tauri/custom-protocol"]
 custom-protocol = ["tauri/custom-protocol"]
 
 
 [patch.crates-io]
 [patch.crates-io]
-collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "87f534" }
-collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "87f534" }
-collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "87f534" }
-collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "87f534" }
-collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "87f534" }
-appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "87f534" }
+collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c382b1" }
+collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c382b1" }
+collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c382b1" }
+collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c382b1" }
+collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c382b1" }
+appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c382b1" }
 
 
 #collab = { path = "../../AppFlowy-Collab/collab" }
 #collab = { path = "../../AppFlowy-Collab/collab" }
 #collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }
 #collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }

+ 2 - 2
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/cell/data_persistence.ts

@@ -24,7 +24,7 @@ export class DateCellDataPersistence extends CellDataPersistence<CalendarData> {
   }
   }
 
 
   save(data: CalendarData): Promise<Result<void, FlowyError>> {
   save(data: CalendarData): Promise<Result<void, FlowyError>> {
-    const payload = DateChangesetPB.fromObject({ cell_path: _makeCellPath(this.cellIdentifier) });
+    const payload = DateChangesetPB.fromObject({ cell_id: _makeCellId(this.cellIdentifier) });
     payload.date = ((data.date.getTime() / 1000) | 0).toString();
     payload.date = ((data.date.getTime() / 1000) | 0).toString();
     if (data.time !== undefined) {
     if (data.time !== undefined) {
       payload.time = data.time;
       payload.time = data.time;
@@ -34,7 +34,7 @@ export class DateCellDataPersistence extends CellDataPersistence<CalendarData> {
   }
   }
 }
 }
 
 
-function _makeCellPath(cellIdentifier: CellIdentifier): CellIdPB {
+function _makeCellId(cellIdentifier: CellIdentifier): CellIdPB {
   return CellIdPB.fromObject({
   return CellIdPB.fromObject({
     view_id: cellIdentifier.viewId,
     view_id: cellIdentifier.viewId,
     field_id: cellIdentifier.fieldId,
     field_id: cellIdentifier.fieldId,

+ 10 - 11
frontend/appflowy_tauri/src/appflowy_app/stores/effects/database/group/group_controller.ts

@@ -1,9 +1,9 @@
-import { DatabaseNotification, FlowyError, GroupPB, GroupRowsNotificationPB, RowPB } from "@/services/backend";
-import { ChangeNotifier } from "$app/utils/change_notifier";
-import { None, Ok, Option, Result, Some } from "ts-results";
-import { DatabaseNotificationObserver } from "../notifications/observer";
-import { Log } from "$app/utils/log";
-import { DatabaseBackendService } from "../database_bd_svc";
+import { DatabaseNotification, FlowyError, GroupPB, GroupRowsNotificationPB, RowPB } from '@/services/backend';
+import { ChangeNotifier } from '$app/utils/change_notifier';
+import { None, Ok, Option, Result, Some } from 'ts-results';
+import { DatabaseNotificationObserver } from '../notifications/observer';
+import { Log } from '$app/utils/log';
+import { DatabaseBackendService } from '../database_bd_svc';
 
 
 export type GroupDataCallbacks = {
 export type GroupDataCallbacks = {
   onRemoveRow: (groupId: string, rowId: string) => void;
   onRemoveRow: (groupId: string, rowId: string) => void;
@@ -30,7 +30,7 @@ export class DatabaseGroupController {
   }
   }
 
 
   get name() {
   get name() {
-    return this.group.desc;
+    return this.group.group_name;
   }
   }
 
 
   updateGroup = (group: GroupPB) => {
   updateGroup = (group: GroupPB) => {
@@ -83,7 +83,7 @@ export class DatabaseGroupController {
         } else {
         } else {
           Log.error(result.val);
           Log.error(result.val);
         }
         }
-      }
+      },
     });
     });
   };
   };
 
 
@@ -111,8 +111,7 @@ class GroupDataObserver {
   private notifier?: ChangeNotifier<Result<GroupRowsNotificationPB, FlowyError>>;
   private notifier?: ChangeNotifier<Result<GroupRowsNotificationPB, FlowyError>>;
   private listener?: DatabaseNotificationObserver;
   private listener?: DatabaseNotificationObserver;
 
 
-  constructor(public readonly groupId: string) {
-  }
+  constructor(public readonly groupId: string) {}
 
 
   subscribe = async (callbacks: { onRowsChanged: GroupRowsSubscribeCallback }) => {
   subscribe = async (callbacks: { onRowsChanged: GroupRowsSubscribeCallback }) => {
     this.notifier = new ChangeNotifier();
     this.notifier = new ChangeNotifier();
@@ -132,7 +131,7 @@ class GroupDataObserver {
           default:
           default:
             break;
             break;
         }
         }
-      }
+      },
     });
     });
     await this.listener.start();
     await this.listener.start();
   };
   };

+ 10 - 10
frontend/rust-lib/Cargo.lock

@@ -85,7 +85,7 @@ checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
 [[package]]
 [[package]]
 name = "appflowy-integrate"
 name = "appflowy-integrate"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "collab",
  "collab",
@@ -887,7 +887,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab"
 name = "collab"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "bytes",
  "bytes",
@@ -905,7 +905,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-client-ws"
 name = "collab-client-ws"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "bytes",
  "bytes",
  "collab-sync",
  "collab-sync",
@@ -923,7 +923,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-database"
 name = "collab-database"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "async-trait",
  "async-trait",
@@ -949,7 +949,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-derive"
 name = "collab-derive"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "proc-macro2",
  "proc-macro2",
  "quote",
  "quote",
@@ -961,7 +961,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-document"
 name = "collab-document"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "collab",
  "collab",
@@ -978,7 +978,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-folder"
 name = "collab-folder"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "chrono",
  "chrono",
@@ -998,7 +998,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-persistence"
 name = "collab-persistence"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "bincode",
  "bincode",
  "chrono",
  "chrono",
@@ -1018,7 +1018,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-plugins"
 name = "collab-plugins"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "anyhow",
  "anyhow",
  "async-trait",
  "async-trait",
@@ -1049,7 +1049,7 @@ dependencies = [
 [[package]]
 [[package]]
 name = "collab-sync"
 name = "collab-sync"
 version = "0.1.0"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=87f534#87f53452241a65275f5b2878ba57dff2a0e2b838"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=c382b1#c382b1ceb44e5615175e3b01f2bf0d49f6c669e3"
 dependencies = [
 dependencies = [
  "bytes",
  "bytes",
  "collab",
  "collab",

+ 5 - 5
frontend/rust-lib/Cargo.toml

@@ -33,11 +33,11 @@ opt-level = 3
 incremental = false
 incremental = false
 
 
 [patch.crates-io]
 [patch.crates-io]
-collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "87f534" }
-collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "87f534" }
-collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "87f534" }
-collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "87f534" }
-appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "87f534" }
+collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c382b1" }
+collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c382b1" }
+collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c382b1" }
+collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c382b1" }
+appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "c382b1" }
 
 
 #collab = { path = "../AppFlowy-Collab/collab" }
 #collab = { path = "../AppFlowy-Collab/collab" }
 #collab-folder = { path = "../AppFlowy-Collab/collab-folder" }
 #collab-folder = { path = "../AppFlowy-Collab/collab-folder" }

+ 1 - 10
frontend/rust-lib/flowy-database2/src/entities/calendar_entities.rs

@@ -80,17 +80,11 @@ impl std::convert::From<CalendarLayout> for CalendarLayoutPB {
 pub struct CalendarEventRequestPB {
 pub struct CalendarEventRequestPB {
   #[pb(index = 1)]
   #[pb(index = 1)]
   pub view_id: String,
   pub view_id: String,
-
-  // Currently, requesting the events within the specified month
-  // is not supported
-  #[pb(index = 2)]
-  pub month: String,
 }
 }
 
 
 #[derive(Debug, Clone, Default)]
 #[derive(Debug, Clone, Default)]
 pub struct CalendarEventRequestParams {
 pub struct CalendarEventRequestParams {
   pub view_id: String,
   pub view_id: String,
-  pub month: String,
 }
 }
 
 
 impl TryInto<CalendarEventRequestParams> for CalendarEventRequestPB {
 impl TryInto<CalendarEventRequestParams> for CalendarEventRequestPB {
@@ -98,10 +92,7 @@ impl TryInto<CalendarEventRequestParams> for CalendarEventRequestPB {
 
 
   fn try_into(self) -> Result<CalendarEventRequestParams, Self::Error> {
   fn try_into(self) -> Result<CalendarEventRequestParams, Self::Error> {
     let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
     let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::ViewIdIsInvalid)?;
-    Ok(CalendarEventRequestParams {
-      view_id: view_id.0,
-      month: self.month,
-    })
+    Ok(CalendarEventRequestParams { view_id: view_id.0 })
   }
   }
 }
 }
 
 

+ 4 - 2
frontend/rust-lib/flowy-database2/src/entities/database_entities.rs

@@ -135,11 +135,13 @@ impl TryInto<MoveRowParams> for MoveRowPayloadPB {
 
 
   fn try_into(self) -> Result<MoveRowParams, Self::Error> {
   fn try_into(self) -> Result<MoveRowParams, Self::Error> {
     let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
     let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)?;
+    let from_row_id = NotEmptyStr::parse(self.from_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
+    let to_row_id = NotEmptyStr::parse(self.to_row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
 
 
     Ok(MoveRowParams {
     Ok(MoveRowParams {
       view_id: view_id.0,
       view_id: view_id.0,
-      from_row_id: RowId::from(self.from_row_id),
-      to_row_id: RowId::from(self.to_row_id),
+      from_row_id: RowId::from(from_row_id.0),
+      to_row_id: RowId::from(to_row_id.0),
     })
     })
   }
   }
 }
 }

+ 3 - 11
frontend/rust-lib/flowy-database2/src/entities/group_entities/group.rs

@@ -76,7 +76,7 @@ pub struct GroupPB {
   pub group_id: String,
   pub group_id: String,
 
 
   #[pb(index = 3)]
   #[pb(index = 3)]
-  pub desc: String,
+  pub group_name: String,
 
 
   #[pb(index = 4)]
   #[pb(index = 4)]
   pub rows: Vec<RowPB>,
   pub rows: Vec<RowPB>,
@@ -93,7 +93,7 @@ impl std::convert::From<GroupData> for GroupPB {
     Self {
     Self {
       field_id: group_data.field_id,
       field_id: group_data.field_id,
       group_id: group_data.id,
       group_id: group_data.id,
-      desc: group_data.name,
+      group_name: group_data.name,
       rows: group_data.rows.into_iter().map(RowPB::from).collect(),
       rows: group_data.rows.into_iter().map(RowPB::from).collect(),
       is_default: group_data.is_default,
       is_default: group_data.is_default,
       is_visible: group_data.is_visible,
       is_visible: group_data.is_visible,
@@ -108,9 +108,6 @@ pub struct GroupByFieldPayloadPB {
 
 
   #[pb(index = 2)]
   #[pb(index = 2)]
   pub view_id: String,
   pub view_id: String,
-
-  #[pb(index = 3)]
-  pub field_type: FieldType,
 }
 }
 
 
 impl TryInto<GroupByFieldParams> for GroupByFieldPayloadPB {
 impl TryInto<GroupByFieldParams> for GroupByFieldPayloadPB {
@@ -124,18 +121,13 @@ impl TryInto<GroupByFieldParams> for GroupByFieldPayloadPB {
       .map_err(|_| ErrorCode::ViewIdIsInvalid)?
       .map_err(|_| ErrorCode::ViewIdIsInvalid)?
       .0;
       .0;
 
 
-    Ok(GroupByFieldParams {
-      field_id,
-      view_id,
-      field_type: self.field_type,
-    })
+    Ok(GroupByFieldParams { field_id, view_id })
   }
   }
 }
 }
 
 
 pub struct GroupByFieldParams {
 pub struct GroupByFieldParams {
   pub field_id: String,
   pub field_id: String,
   pub view_id: String,
   pub view_id: String,
-  pub field_type: FieldType,
 }
 }
 
 
 pub struct DeleteGroupParams {
 pub struct DeleteGroupParams {

+ 2 - 1
frontend/rust-lib/flowy-database2/src/entities/row_entities.rs

@@ -158,6 +158,7 @@ impl TryInto<RowIdParams> for RowIdPB {
 
 
   fn try_into(self) -> Result<RowIdParams, Self::Error> {
   fn try_into(self) -> Result<RowIdParams, Self::Error> {
     let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
     let view_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
+    let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?;
     let group_id = match self.group_id {
     let group_id = match self.group_id {
       Some(group_id) => Some(
       Some(group_id) => Some(
         NotEmptyStr::parse(group_id)
         NotEmptyStr::parse(group_id)
@@ -169,7 +170,7 @@ impl TryInto<RowIdParams> for RowIdPB {
 
 
     Ok(RowIdParams {
     Ok(RowIdParams {
       view_id: view_id.0,
       view_id: view_id.0,
-      row_id: RowId::from(self.row_id),
+      row_id: RowId::from(row_id.0),
       group_id,
       group_id,
     })
     })
   }
   }

+ 6 - 5
frontend/rust-lib/flowy-database2/src/entities/type_option_entities/checklist_entities.rs

@@ -1,11 +1,12 @@
+use collab_database::rows::RowId;
+
+use flowy_derive::ProtoBuf;
+use flowy_error::{ErrorCode, FlowyError};
+
 use crate::entities::parser::NotEmptyStr;
 use crate::entities::parser::NotEmptyStr;
 use crate::entities::SelectOptionPB;
 use crate::entities::SelectOptionPB;
 use crate::services::field::checklist_type_option::ChecklistCellData;
 use crate::services::field::checklist_type_option::ChecklistCellData;
-
 use crate::services::field::SelectOption;
 use crate::services::field::SelectOption;
-use collab_database::rows::RowId;
-use flowy_derive::ProtoBuf;
-use flowy_error::{ErrorCode, FlowyError};
 
 
 #[derive(Debug, Clone, Default, ProtoBuf)]
 #[derive(Debug, Clone, Default, ProtoBuf)]
 pub struct ChecklistCellDataPB {
 pub struct ChecklistCellDataPB {
@@ -16,7 +17,7 @@ pub struct ChecklistCellDataPB {
   pub selected_options: Vec<SelectOptionPB>,
   pub selected_options: Vec<SelectOptionPB>,
 
 
   #[pb(index = 3)]
   #[pb(index = 3)]
-  pub(crate) percentage: f64,
+  pub percentage: f64,
 }
 }
 
 
 impl From<ChecklistCellData> for ChecklistCellDataPB {
 impl From<ChecklistCellData> for ChecklistCellDataPB {

+ 1 - 1
frontend/rust-lib/flowy-database2/src/entities/type_option_entities/date_entities.rs

@@ -25,7 +25,7 @@ pub struct DateCellDataPB {
 #[derive(Clone, Debug, Default, ProtoBuf)]
 #[derive(Clone, Debug, Default, ProtoBuf)]
 pub struct DateChangesetPB {
 pub struct DateChangesetPB {
   #[pb(index = 1)]
   #[pb(index = 1)]
-  pub cell_path: CellIdPB,
+  pub cell_id: CellIdPB,
 
 
   #[pb(index = 2, one_of)]
   #[pb(index = 2, one_of)]
   pub date: Option<String>,
   pub date: Option<String>,

+ 4 - 2
frontend/rust-lib/flowy-database2/src/event_handler.rs

@@ -294,7 +294,9 @@ pub(crate) async fn get_row_handler(
 ) -> DataResult<OptionalRowPB, FlowyError> {
 ) -> DataResult<OptionalRowPB, FlowyError> {
   let params: RowIdParams = data.into_inner().try_into()?;
   let params: RowIdParams = data.into_inner().try_into()?;
   let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
   let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
-  let row = database_editor.get_row(&params.row_id).map(RowPB::from);
+  let row = database_editor
+    .get_row(&params.view_id, &params.row_id)
+    .map(RowPB::from);
   data_result_ok(OptionalRowPB { row })
   data_result_ok(OptionalRowPB { row })
 }
 }
 
 
@@ -525,7 +527,7 @@ pub(crate) async fn update_date_cell_handler(
   manager: AFPluginState<Arc<DatabaseManager2>>,
   manager: AFPluginState<Arc<DatabaseManager2>>,
 ) -> Result<(), FlowyError> {
 ) -> Result<(), FlowyError> {
   let data = data.into_inner();
   let data = data.into_inner();
-  let cell_id: CellIdParams = data.cell_path.try_into()?;
+  let cell_id: CellIdParams = data.cell_id.try_into()?;
   let cell_changeset = DateCellChangeset {
   let cell_changeset = DateCellChangeset {
     date: data.date,
     date: data.date,
     time: data.time,
     time: data.time,

+ 11 - 2
frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs

@@ -496,8 +496,12 @@ impl DatabaseEditor {
     Ok(view_editor.v_get_rows().await)
     Ok(view_editor.v_get_rows().await)
   }
   }
 
 
-  pub fn get_row(&self, row_id: &RowId) -> Option<Row> {
-    self.database.lock().get_row(row_id)
+  pub fn get_row(&self, view_id: &str, row_id: &RowId) -> Option<Row> {
+    if self.database.lock().views.is_row_exist(view_id, row_id) {
+      return None;
+    } else {
+      self.database.lock().get_row(row_id)
+    }
   }
   }
 
 
   pub async fn delete_row(&self, row_id: &RowId) {
   pub async fn delete_row(&self, row_id: &RowId) {
@@ -832,6 +836,11 @@ impl DatabaseEditor {
     from_group: &str,
     from_group: &str,
     to_group: &str,
     to_group: &str,
   ) -> FlowyResult<()> {
   ) -> FlowyResult<()> {
+    // Do nothing if the group is the same
+    if from_group == to_group {
+      return Ok(());
+    }
+
     let view = self.database_views.get_view_editor(view_id).await?;
     let view = self.database_views.get_view_editor(view_id).await?;
     view.v_move_group(from_group, to_group).await?;
     view.v_move_group(from_group, to_group).await?;
     Ok(())
     Ok(())

+ 1 - 0
frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs

@@ -375,6 +375,7 @@ impl DatabaseViewEditor {
       .as_ref()?
       .as_ref()?
       .groups()
       .groups()
       .into_iter()
       .into_iter()
+      .filter(|group| group.is_visible)
       .map(|group_data| GroupPB::from(group_data.clone()))
       .map(|group_data| GroupPB::from(group_data.clone()))
       .collect::<Vec<_>>();
       .collect::<Vec<_>>();
     tracing::trace!("Number of groups: {}", groups.len());
     tracing::trace!("Number of groups: {}", groups.len());

+ 22 - 4
frontend/rust-lib/flowy-database2/src/services/group/configuration.rs

@@ -351,15 +351,21 @@ where
   }
   }
 
 
   pub(crate) fn update_group(&mut self, group_changeset: GroupChangeset) -> FlowyResult<()> {
   pub(crate) fn update_group(&mut self, group_changeset: GroupChangeset) -> FlowyResult<()> {
-    self.mut_group(&group_changeset.group_id, |group| {
+    let update_group = self.mut_group(&group_changeset.group_id, |group| {
       if let Some(visible) = group_changeset.visible {
       if let Some(visible) = group_changeset.visible {
         group.visible = visible;
         group.visible = visible;
       }
       }
-
       if let Some(name) = &group_changeset.name {
       if let Some(name) = &group_changeset.name {
         group.name = name.clone();
         group.name = name.clone();
       }
       }
     })?;
     })?;
+
+    if let Some(group) = update_group {
+      self.group_by_id.get_mut(&group.id).map(|group_data| {
+        group_data.name = group.name.clone();
+        group_data.is_visible = group.visible;
+      });
+    }
     Ok(())
     Ok(())
   }
   }
 
 
@@ -370,6 +376,11 @@ where
       .await
       .await
   }
   }
 
 
+  /// # Arguments
+  ///
+  /// * `mut_configuration_fn`: mutate the [GroupSetting] and return whether the [GroupSetting] is
+  /// changed. If the [GroupSetting] is changed, the [GroupSetting] will be saved to the storage.
+  ///
   fn mut_configuration(
   fn mut_configuration(
     &mut self,
     &mut self,
     mut_configuration_fn: impl FnOnce(&mut GroupSetting) -> bool,
     mut_configuration_fn: impl FnOnce(&mut GroupSetting) -> bool,
@@ -392,7 +403,12 @@ where
     Ok(())
     Ok(())
   }
   }
 
 
-  fn mut_group(&mut self, group_id: &str, mut_groups_fn: impl Fn(&mut Group)) -> FlowyResult<()> {
+  fn mut_group(
+    &mut self,
+    group_id: &str,
+    mut_groups_fn: impl Fn(&mut Group),
+  ) -> FlowyResult<Option<Group>> {
+    let mut updated_group = None;
     self.mut_configuration(|configuration| {
     self.mut_configuration(|configuration| {
       match configuration
       match configuration
         .groups
         .groups
@@ -402,10 +418,12 @@ where
         None => false,
         None => false,
         Some(group) => {
         Some(group) => {
           mut_groups_fn(group);
           mut_groups_fn(group);
+          updated_group = Some(group.clone());
           true
           true
         },
         },
       }
       }
-    })
+    })?;
+    Ok(updated_group)
   }
   }
 }
 }
 
 

+ 2 - 1
frontend/rust-lib/flowy-database2/tests/database/group_test/script.rs

@@ -1,6 +1,7 @@
 use collab_database::database::gen_row_id;
 use collab_database::database::gen_row_id;
 use collab_database::fields::Field;
 use collab_database::fields::Field;
 use collab_database::rows::{CreateRowParams, RowId};
 use collab_database::rows::{CreateRowParams, RowId};
+
 use flowy_database2::entities::{FieldType, GroupPB, RowPB};
 use flowy_database2::entities::{FieldType, GroupPB, RowPB};
 use flowy_database2::services::cell::{
 use flowy_database2::services::cell::{
   delete_select_option_cell, insert_select_option_cell, insert_url_cell,
   delete_select_option_cell, insert_select_option_cell, insert_url_cell,
@@ -233,7 +234,7 @@ impl DatabaseGroupTest {
       } => {
       } => {
         let group = self.group_at_index(group_index).await;
         let group = self.group_at_index(group_index).await;
         assert_eq!(group.group_id, group_pb.group_id);
         assert_eq!(group.group_id, group_pb.group_id);
-        assert_eq!(group.desc, group_pb.desc);
+        assert_eq!(group.group_name, group_pb.group_name);
       },
       },
       GroupScript::UpdateSingleSelectSelectOption { inserted_options } => {
       GroupScript::UpdateSingleSelectSelectOption { inserted_options } => {
         self
         self

+ 1 - 1
frontend/rust-lib/flowy-database2/tests/database/group_test/test.rs

@@ -463,7 +463,7 @@ async fn group_insert_single_select_option_test() {
   ];
   ];
   test.run_scripts(scripts).await;
   test.run_scripts(scripts).await;
   let new_group = test.group_at_index(1).await;
   let new_group = test.group_at_index(1).await;
-  assert_eq!(new_group.desc, new_option_name);
+  assert_eq!(new_group.group_name, new_option_name);
 }
 }
 
 
 #[tokio::test]
 #[tokio::test]

+ 195 - 2
frontend/rust-lib/flowy-test/src/lib.rs

@@ -1,3 +1,5 @@
+use bytes::Bytes;
+use std::convert::TryFrom;
 use std::env::temp_dir;
 use std::env::temp_dir;
 use std::sync::Arc;
 use std::sync::Arc;
 
 
@@ -132,6 +134,49 @@ impl FlowyCoreTest {
       .parse::<flowy_folder2::entities::ViewPB>()
       .parse::<flowy_folder2::entities::ViewPB>()
   }
   }
 
 
+  pub async fn create_board(&self, parent_id: &str, name: String, initial_data: Vec<u8>) -> ViewPB {
+    let payload = CreateViewPayloadPB {
+      parent_view_id: parent_id.to_string(),
+      name,
+      desc: "".to_string(),
+      thumbnail: None,
+      layout: ViewLayoutPB::Board,
+      initial_data,
+      meta: Default::default(),
+      set_as_current: true,
+    };
+    EventBuilder::new(self.clone())
+      .event(flowy_folder2::event_map::FolderEvent::CreateView)
+      .payload(payload)
+      .async_send()
+      .await
+      .parse::<flowy_folder2::entities::ViewPB>()
+  }
+
+  pub async fn create_calendar(
+    &self,
+    parent_id: &str,
+    name: String,
+    initial_data: Vec<u8>,
+  ) -> ViewPB {
+    let payload = CreateViewPayloadPB {
+      parent_view_id: parent_id.to_string(),
+      name,
+      desc: "".to_string(),
+      thumbnail: None,
+      layout: ViewLayoutPB::Calendar,
+      initial_data,
+      meta: Default::default(),
+      set_as_current: true,
+    };
+    EventBuilder::new(self.clone())
+      .event(flowy_folder2::event_map::FolderEvent::CreateView)
+      .payload(payload)
+      .async_send()
+      .await
+      .parse::<flowy_folder2::entities::ViewPB>()
+  }
+
   pub async fn get_database(&self, view_id: &str) -> DatabasePB {
   pub async fn get_database(&self, view_id: &str) -> DatabasePB {
     EventBuilder::new(self.clone())
     EventBuilder::new(self.clone())
       .event(flowy_database2::event_map::DatabaseEvent::GetDatabase)
       .event(flowy_database2::event_map::DatabaseEvent::GetDatabase)
@@ -238,7 +283,20 @@ impl FlowyCoreTest {
       .parse::<RowPB>()
       .parse::<RowPB>()
   }
   }
 
 
-  pub async fn get_row(&self, view_id: &str, row_id: &str) -> RowPB {
+  pub async fn delete_row(&self, view_id: &str, row_id: &str) -> Option<FlowyError> {
+    EventBuilder::new(self.clone())
+      .event(flowy_database2::event_map::DatabaseEvent::DeleteRow)
+      .payload(RowIdPB {
+        view_id: view_id.to_string(),
+        row_id: row_id.to_string(),
+        group_id: None,
+      })
+      .async_send()
+      .await
+      .error()
+  }
+
+  pub async fn get_row(&self, view_id: &str, row_id: &str) -> OptionalRowPB {
     EventBuilder::new(self.clone())
     EventBuilder::new(self.clone())
       .event(flowy_database2::event_map::DatabaseEvent::GetRow)
       .event(flowy_database2::event_map::DatabaseEvent::GetRow)
       .payload(RowIdPB {
       .payload(RowIdPB {
@@ -248,7 +306,7 @@ impl FlowyCoreTest {
       })
       })
       .async_send()
       .async_send()
       .await
       .await
-      .parse::<RowPB>()
+      .parse::<OptionalRowPB>()
   }
   }
 
 
   pub async fn duplicate_row(&self, view_id: &str, row_id: &str) -> Option<FlowyError> {
   pub async fn duplicate_row(&self, view_id: &str, row_id: &str) -> Option<FlowyError> {
@@ -264,6 +322,19 @@ impl FlowyCoreTest {
       .error()
       .error()
   }
   }
 
 
+  pub async fn move_row(&self, view_id: &str, row_id: &str, to_row_id: &str) -> Option<FlowyError> {
+    EventBuilder::new(self.clone())
+      .event(flowy_database2::event_map::DatabaseEvent::MoveRow)
+      .payload(MoveRowPayloadPB {
+        view_id: view_id.to_string(),
+        from_row_id: row_id.to_string(),
+        to_row_id: to_row_id.to_string(),
+      })
+      .async_send()
+      .await
+      .error()
+  }
+
   pub async fn update_cell(&self, changeset: CellChangesetPB) -> Option<FlowyError> {
   pub async fn update_cell(&self, changeset: CellChangesetPB) -> Option<FlowyError> {
     EventBuilder::new(self.clone())
     EventBuilder::new(self.clone())
       .event(flowy_database2::event_map::DatabaseEvent::UpdateCell)
       .event(flowy_database2::event_map::DatabaseEvent::UpdateCell)
@@ -273,6 +344,15 @@ impl FlowyCoreTest {
       .error()
       .error()
   }
   }
 
 
+  pub async fn update_date_cell(&self, changeset: DateChangesetPB) -> Option<FlowyError> {
+    EventBuilder::new(self.clone())
+      .event(flowy_database2::event_map::DatabaseEvent::UpdateDateCell)
+      .payload(changeset)
+      .async_send()
+      .await
+      .error()
+  }
+
   pub async fn get_cell(&self, view_id: &str, row_id: &str, field_id: &str) -> CellPB {
   pub async fn get_cell(&self, view_id: &str, row_id: &str, field_id: &str) -> CellPB {
     EventBuilder::new(self.clone())
     EventBuilder::new(self.clone())
       .event(flowy_database2::event_map::DatabaseEvent::GetCell)
       .event(flowy_database2::event_map::DatabaseEvent::GetCell)
@@ -286,6 +366,41 @@ impl FlowyCoreTest {
       .parse::<CellPB>()
       .parse::<CellPB>()
   }
   }
 
 
+  pub async fn get_date_cell(&self, view_id: &str, row_id: &str, field_id: &str) -> DateCellDataPB {
+    let cell = self.get_cell(view_id, row_id, field_id).await;
+    DateCellDataPB::try_from(Bytes::from(cell.data)).unwrap()
+  }
+
+  pub async fn get_checklist_cell(
+    &self,
+    view_id: &str,
+    field_id: &str,
+    row_id: &str,
+  ) -> ChecklistCellDataPB {
+    EventBuilder::new(self.clone())
+      .event(flowy_database2::event_map::DatabaseEvent::GetChecklistCellData)
+      .payload(CellIdPB {
+        view_id: view_id.to_string(),
+        row_id: row_id.to_string(),
+        field_id: field_id.to_string(),
+      })
+      .async_send()
+      .await
+      .parse::<ChecklistCellDataPB>()
+  }
+
+  pub async fn update_checklist_cell(
+    &self,
+    changeset: ChecklistCellDataChangesetPB,
+  ) -> Option<FlowyError> {
+    EventBuilder::new(self.clone())
+      .event(flowy_database2::event_map::DatabaseEvent::UpdateChecklistCell)
+      .payload(changeset)
+      .async_send()
+      .await
+      .error()
+  }
+
   pub async fn insert_option(
   pub async fn insert_option(
     &self,
     &self,
     view_id: &str,
     view_id: &str,
@@ -317,6 +432,84 @@ impl FlowyCoreTest {
       .error()
       .error()
   }
   }
 
 
+  pub async fn get_groups(&self, view_id: &str) -> Vec<GroupPB> {
+    EventBuilder::new(self.clone())
+      .event(flowy_database2::event_map::DatabaseEvent::GetGroups)
+      .payload(DatabaseViewIdPB {
+        value: view_id.to_string(),
+      })
+      .async_send()
+      .await
+      .parse::<RepeatedGroupPB>()
+      .items
+  }
+
+  pub async fn move_group(&self, view_id: &str, from_id: &str, to_id: &str) -> Option<FlowyError> {
+    EventBuilder::new(self.clone())
+      .event(flowy_database2::event_map::DatabaseEvent::MoveGroup)
+      .payload(MoveGroupPayloadPB {
+        view_id: view_id.to_string(),
+        from_group_id: from_id.to_string(),
+        to_group_id: to_id.to_string(),
+      })
+      .async_send()
+      .await
+      .error()
+  }
+
+  pub async fn set_group_by_field(&self, view_id: &str, field_id: &str) -> Option<FlowyError> {
+    EventBuilder::new(self.clone())
+      .event(flowy_database2::event_map::DatabaseEvent::SetGroupByField)
+      .payload(GroupByFieldPayloadPB {
+        field_id: field_id.to_string(),
+        view_id: view_id.to_string(),
+      })
+      .async_send()
+      .await
+      .error()
+  }
+
+  pub async fn update_group(
+    &self,
+    view_id: &str,
+    group_id: &str,
+    name: Option<String>,
+    visible: Option<bool>,
+  ) -> Option<FlowyError> {
+    EventBuilder::new(self.clone())
+      .event(flowy_database2::event_map::DatabaseEvent::UpdateGroup)
+      .payload(UpdateGroupPB {
+        view_id: view_id.to_string(),
+        group_id: group_id.to_string(),
+        name,
+        visible,
+      })
+      .async_send()
+      .await
+      .error()
+  }
+
+  pub async fn update_setting(&self, changeset: DatabaseSettingChangesetPB) -> Option<FlowyError> {
+    EventBuilder::new(self.clone())
+      .event(flowy_database2::event_map::DatabaseEvent::UpdateDatabaseSetting)
+      .payload(changeset)
+      .async_send()
+      .await
+      .error()
+  }
+
+  pub async fn get_all_calendar_events(&self, view_id: &str) -> Vec<CalendarEventPB> {
+    EventBuilder::new(self.clone())
+      .event(flowy_database2::event_map::DatabaseEvent::GetAllCalendarEvents)
+      .payload(CalendarEventRequestPB {
+        view_id: view_id.to_string(),
+      })
+      .async_send()
+      .await
+      .parse::<RepeatedCalendarEventPB>()
+      .items
+  }
+
   pub async fn get_view(&self, view_id: &str) -> ViewPB {
   pub async fn get_view(&self, view_id: &str) -> ViewPB {
     EventBuilder::new(self.clone())
     EventBuilder::new(self.clone())
       .event(flowy_folder2::event_map::FolderEvent::ReadView)
       .event(flowy_folder2::event_map::FolderEvent::ReadView)

+ 524 - 1
frontend/rust-lib/flowy-test/tests/database/test.rs

@@ -1,9 +1,11 @@
 use std::convert::TryFrom;
 use std::convert::TryFrom;
 
 
 use bytes::Bytes;
 use bytes::Bytes;
+use lib_infra::util::timestamp;
 
 
 use flowy_database2::entities::{
 use flowy_database2::entities::{
-  CellChangesetPB, DatabaseLayoutPB, DatabaseViewIdPB, FieldType, SelectOptionCellDataPB,
+  CellChangesetPB, CellIdPB, ChecklistCellDataChangesetPB, DatabaseLayoutPB,
+  DatabaseSettingChangesetPB, DatabaseViewIdPB, DateChangesetPB, FieldType, SelectOptionCellDataPB,
 };
 };
 use flowy_test::event_builder::EventBuilder;
 use flowy_test::event_builder::EventBuilder;
 use flowy_test::FlowyCoreTest;
 use flowy_test::FlowyCoreTest;
@@ -93,6 +95,7 @@ async fn delete_field_event_test() {
   assert_eq!(fields.len(), 2);
   assert_eq!(fields.len(), 2);
 }
 }
 
 
+// The primary field is not allowed to be deleted.
 #[tokio::test]
 #[tokio::test]
 async fn delete_primary_field_event_test() {
 async fn delete_primary_field_event_test() {
   let test = FlowyCoreTest::new_with_user().await;
   let test = FlowyCoreTest::new_with_user().await;
@@ -162,6 +165,7 @@ async fn duplicate_field_event_test() {
   assert_eq!(fields.len(), 4);
   assert_eq!(fields.len(), 4);
 }
 }
 
 
+// The primary field is not allowed to be duplicated. So this test should return an error.
 #[tokio::test]
 #[tokio::test]
 async fn duplicate_primary_field_test() {
 async fn duplicate_primary_field_test() {
   let test = FlowyCoreTest::new_with_user().await;
   let test = FlowyCoreTest::new_with_user().await;
@@ -189,6 +193,40 @@ async fn create_row_event_test() {
   assert_eq!(database.rows.len(), 4);
   assert_eq!(database.rows.len(), 4);
 }
 }
 
 
+#[tokio::test]
+async fn delete_row_event_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let grid_view = test
+    .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
+    .await;
+
+  // delete the row
+  let database = test.get_database(&grid_view.id).await;
+  let error = test.delete_row(&grid_view.id, &database.rows[0].id).await;
+  assert!(error.is_none());
+
+  let database = test.get_database(&grid_view.id).await;
+  assert_eq!(database.rows.len(), 2);
+
+  // get the row again and check if it is deleted.
+  let optional_row = test.get_row(&grid_view.id, &database.rows[0].id).await;
+  assert!(optional_row.row.is_none());
+}
+
+#[tokio::test]
+async fn delete_row_event_with_invalid_row_id_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let grid_view = test
+    .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
+    .await;
+
+  // delete the row with empty row_id. It should return an error.
+  let error = test.delete_row(&grid_view.id, "").await;
+  assert!(error.is_some());
+}
+
 #[tokio::test]
 #[tokio::test]
 async fn duplicate_row_event_test() {
 async fn duplicate_row_event_test() {
   let test = FlowyCoreTest::new_with_user().await;
   let test = FlowyCoreTest::new_with_user().await;
@@ -206,6 +244,90 @@ async fn duplicate_row_event_test() {
   assert_eq!(database.rows.len(), 4);
   assert_eq!(database.rows.len(), 4);
 }
 }
 
 
+#[tokio::test]
+async fn duplicate_row_event_with_invalid_row_id_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let grid_view = test
+    .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
+    .await;
+  let database = test.get_database(&grid_view.id).await;
+  assert_eq!(database.rows.len(), 3);
+
+  let error = test.duplicate_row(&grid_view.id, "").await;
+  assert!(error.is_some());
+
+  let database = test.get_database(&grid_view.id).await;
+  assert_eq!(database.rows.len(), 3);
+}
+
+#[tokio::test]
+async fn move_row_event_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let grid_view = test
+    .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
+    .await;
+  let database = test.get_database(&grid_view.id).await;
+  let row_1 = database.rows[0].id.clone();
+  let row_2 = database.rows[1].id.clone();
+  let row_3 = database.rows[2].id.clone();
+  let error = test.move_row(&grid_view.id, &row_1, &row_3).await;
+  assert!(error.is_none());
+
+  let database = test.get_database(&grid_view.id).await;
+  assert_eq!(database.rows[0].id, row_2);
+  assert_eq!(database.rows[1].id, row_3);
+  assert_eq!(database.rows[2].id, row_1);
+}
+
+#[tokio::test]
+async fn move_row_event_test2() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let grid_view = test
+    .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
+    .await;
+  let database = test.get_database(&grid_view.id).await;
+  let row_1 = database.rows[0].id.clone();
+  let row_2 = database.rows[1].id.clone();
+  let row_3 = database.rows[2].id.clone();
+  let error = test.move_row(&grid_view.id, &row_2, &row_1).await;
+  assert!(error.is_none());
+
+  let database = test.get_database(&grid_view.id).await;
+  assert_eq!(database.rows[0].id, row_2);
+  assert_eq!(database.rows[1].id, row_1);
+  assert_eq!(database.rows[2].id, row_3);
+}
+
+#[tokio::test]
+async fn move_row_event_with_invalid_row_id_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let grid_view = test
+    .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
+    .await;
+  let database = test.get_database(&grid_view.id).await;
+  let row_1 = database.rows[0].id.clone();
+  let row_2 = database.rows[1].id.clone();
+  let row_3 = database.rows[2].id.clone();
+
+  for i in 0..2 {
+    if i == 0 {
+      let error = test.move_row(&grid_view.id, &row_1, "").await;
+      assert!(error.is_some());
+    } else {
+      let error = test.move_row(&grid_view.id, "", &row_1).await;
+      assert!(error.is_some());
+    }
+    let database = test.get_database(&grid_view.id).await;
+    assert_eq!(database.rows[0].id, row_1);
+    assert_eq!(database.rows[1].id, row_2);
+    assert_eq!(database.rows[2].id, row_3);
+  }
+}
+
 #[tokio::test]
 #[tokio::test]
 async fn update_text_cell_event_test() {
 async fn update_text_cell_event_test() {
   let test = FlowyCoreTest::new_with_user().await;
   let test = FlowyCoreTest::new_with_user().await;
@@ -280,14 +402,415 @@ async fn update_single_select_cell_event_test() {
   let field_id = fields[1].id.clone();
   let field_id = fields[1].id.clone();
   assert_eq!(fields[1].field_type, FieldType::SingleSelect);
   assert_eq!(fields[1].field_type, FieldType::SingleSelect);
 
 
+  // Insert a new option. This should update the cell with the new option.
   let error = test
   let error = test
     .insert_option(&grid_view.id, &field_id, &row_id, "task 1")
     .insert_option(&grid_view.id, &field_id, &row_id, "task 1")
     .await;
     .await;
   assert!(error.is_none());
   assert!(error.is_none());
 
 
+  // Check that the cell data is updated.
   let cell = test.get_cell(&grid_view.id, &row_id, &field_id).await;
   let cell = test.get_cell(&grid_view.id, &row_id, &field_id).await;
   let select_option_cell = SelectOptionCellDataPB::try_from(Bytes::from(cell.data)).unwrap();
   let select_option_cell = SelectOptionCellDataPB::try_from(Bytes::from(cell.data)).unwrap();
 
 
   assert_eq!(select_option_cell.options.len(), 1);
   assert_eq!(select_option_cell.options.len(), 1);
   assert_eq!(select_option_cell.select_options.len(), 1);
   assert_eq!(select_option_cell.select_options.len(), 1);
 }
 }
+
+#[tokio::test]
+async fn update_date_cell_event_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let grid_view = test
+    .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
+    .await;
+  let database = test.get_database(&grid_view.id).await;
+
+  // Create a date field
+  let date_field = test.create_field(&grid_view.id, FieldType::DateTime).await;
+
+  let cell_path = CellIdPB {
+    view_id: grid_view.id.clone(),
+    field_id: date_field.id.clone(),
+    row_id: database.rows[0].id.clone(),
+  };
+
+  // Insert data into the date cell of the first row.
+  let timestamp = 1686300557;
+  let timestamp_str = 1686300557.to_string();
+  let error = test
+    .update_date_cell(DateChangesetPB {
+      cell_id: cell_path,
+      date: Some(timestamp_str.clone()),
+      time: None,
+      include_time: None,
+    })
+    .await;
+  assert!(error.is_none());
+
+  // Check that the cell data is updated.
+  let cell = test
+    .get_date_cell(&grid_view.id, &database.rows[0].id, &date_field.id)
+    .await;
+  assert_eq!(cell.date, "Jun 09, 2023");
+  assert_eq!(cell.timestamp, timestamp);
+}
+
+#[tokio::test]
+async fn update_date_cell_event_with_empty_time_str_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let grid_view = test
+    .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
+    .await;
+  let database = test.get_database(&grid_view.id).await;
+  let row_id = database.rows[0].id.clone();
+
+  // Create a date field
+  let date_field = test.create_field(&grid_view.id, FieldType::DateTime).await;
+  let cell_path = CellIdPB {
+    view_id: grid_view.id.clone(),
+    field_id: date_field.id.clone(),
+    row_id: row_id.clone(),
+  };
+
+  // Insert empty timestamp string
+  let error = test
+    .update_date_cell(DateChangesetPB {
+      cell_id: cell_path,
+      date: Some("".to_string()),
+      ..Default::default()
+    })
+    .await;
+  assert!(error.is_none());
+
+  // Check that the cell data is updated.
+  let cell = test
+    .get_date_cell(&grid_view.id, &row_id, &date_field.id)
+    .await;
+  assert_eq!(cell.date, "");
+  assert_eq!(cell.timestamp, 0);
+}
+
+#[tokio::test]
+async fn create_checklist_field_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let grid_view = test
+    .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
+    .await;
+
+  // create checklist field
+  let checklist_field = test.create_field(&grid_view.id, FieldType::Checklist).await;
+  let database = test.get_database(&grid_view.id).await;
+
+  // Get the checklist cell
+  let cell = test
+    .get_checklist_cell(&grid_view.id, &checklist_field.id, &database.rows[0].id)
+    .await;
+  assert_eq!(cell.options.len(), 0);
+  assert_eq!(cell.selected_options.len(), 0);
+  assert_eq!(cell.percentage, 0.0);
+}
+
+#[tokio::test]
+async fn update_checklist_cell_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let grid_view = test
+    .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
+    .await;
+
+  // create checklist field
+  let checklist_field = test.create_field(&grid_view.id, FieldType::Checklist).await;
+  let database = test.get_database(&grid_view.id).await;
+
+  // update the checklist cell
+  let changeset = ChecklistCellDataChangesetPB {
+    view_id: grid_view.id.clone(),
+    row_id: database.rows[0].id.clone(),
+    field_id: checklist_field.id.clone(),
+    insert_options: vec![
+      "task 1".to_string(),
+      "task 2".to_string(),
+      "task 3".to_string(),
+    ],
+    selected_option_ids: vec![],
+    delete_option_ids: vec![],
+    update_options: vec![],
+  };
+  test.update_checklist_cell(changeset).await;
+
+  // get the cell
+  let cell = test
+    .get_checklist_cell(&grid_view.id, &checklist_field.id, &database.rows[0].id)
+    .await;
+
+  assert_eq!(cell.options.len(), 3);
+  assert_eq!(cell.selected_options.len(), 0);
+
+  // select some options
+  let changeset = ChecklistCellDataChangesetPB {
+    view_id: grid_view.id.clone(),
+    row_id: database.rows[0].id.clone(),
+    field_id: checklist_field.id.clone(),
+    selected_option_ids: vec![cell.options[0].id.clone(), cell.options[1].id.clone()],
+    ..Default::default()
+  };
+  test.update_checklist_cell(changeset).await;
+
+  // get the cell
+  let cell = test
+    .get_checklist_cell(&grid_view.id, &checklist_field.id, &database.rows[0].id)
+    .await;
+
+  assert_eq!(cell.options.len(), 3);
+  assert_eq!(cell.selected_options.len(), 2);
+  assert_eq!(cell.percentage, 0.6666666666666666);
+}
+
+// The number of groups should be 0 if there is no group by field in grid
+#[tokio::test]
+async fn get_groups_event_with_grid_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let grid_view = test
+    .create_grid(&current_workspace.id, "my board view".to_owned(), vec![])
+    .await;
+
+  let groups = test.get_groups(&grid_view.id).await;
+  assert_eq!(groups.len(), 0);
+}
+
+#[tokio::test]
+async fn get_groups_event_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let board_view = test
+    .create_board(&current_workspace.id, "my board view".to_owned(), vec![])
+    .await;
+
+  let groups = test.get_groups(&board_view.id).await;
+  assert_eq!(groups.len(), 4);
+}
+
+#[tokio::test]
+async fn move_group_event_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let board_view = test
+    .create_board(&current_workspace.id, "my board view".to_owned(), vec![])
+    .await;
+
+  let groups = test.get_groups(&board_view.id).await;
+  assert_eq!(groups.len(), 4);
+  let group_1 = groups[0].group_id.clone();
+  let group_2 = groups[1].group_id.clone();
+  let group_3 = groups[2].group_id.clone();
+  let group_4 = groups[3].group_id.clone();
+
+  let error = test.move_group(&board_view.id, &group_2, &group_3).await;
+  assert!(error.is_none());
+
+  let groups = test.get_groups(&board_view.id).await;
+  assert_eq!(groups[0].group_id, group_1);
+  assert_eq!(groups[1].group_id, group_3);
+  assert_eq!(groups[2].group_id, group_2);
+  assert_eq!(groups[3].group_id, group_4);
+
+  let error = test.move_group(&board_view.id, &group_1, &group_4).await;
+  assert!(error.is_none());
+
+  let groups = test.get_groups(&board_view.id).await;
+  assert_eq!(groups[0].group_id, group_3);
+  assert_eq!(groups[1].group_id, group_2);
+  assert_eq!(groups[2].group_id, group_4);
+  assert_eq!(groups[3].group_id, group_1);
+}
+
+#[tokio::test]
+async fn move_group_event_with_invalid_id_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let board_view = test
+    .create_board(&current_workspace.id, "my board view".to_owned(), vec![])
+    .await;
+
+  // Empty to group id
+  let groups = test.get_groups(&board_view.id).await;
+  let error = test
+    .move_group(&board_view.id, &groups[0].group_id, "")
+    .await;
+  assert!(error.is_some());
+
+  // empty from group id
+  let error = test
+    .move_group(&board_view.id, "", &groups[1].group_id)
+    .await;
+  assert!(error.is_some());
+}
+
+#[tokio::test]
+async fn rename_group_event_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let board_view = test
+    .create_board(&current_workspace.id, "my board view".to_owned(), vec![])
+    .await;
+
+  // Empty to group id
+  let groups = test.get_groups(&board_view.id).await;
+  let error = test
+    .update_group(
+      &board_view.id,
+      &groups[0].group_id,
+      Some("new name".to_owned()),
+      None,
+    )
+    .await;
+  assert!(error.is_none());
+
+  let groups = test.get_groups(&board_view.id).await;
+  assert_eq!(groups[0].group_name, "new name".to_owned());
+}
+
+#[tokio::test]
+async fn hide_group_event_test2() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let board_view = test
+    .create_board(&current_workspace.id, "my board view".to_owned(), vec![])
+    .await;
+
+  // Empty to group id
+  let groups = test.get_groups(&board_view.id).await;
+  assert_eq!(groups.len(), 4);
+
+  let error = test
+    .update_group(&board_view.id, &groups[0].group_id, None, Some(false))
+    .await;
+  assert!(error.is_none());
+
+  let groups = test.get_groups(&board_view.id).await;
+  assert_eq!(groups.len(), 3);
+}
+
+// Update the database layout type from grid to board
+#[tokio::test]
+async fn update_database_layout_event_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let grid_view = test
+    .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
+    .await;
+
+  let error = test
+    .update_setting(DatabaseSettingChangesetPB {
+      view_id: grid_view.id.clone(),
+      layout_type: Some(DatabaseLayoutPB::Board),
+      ..Default::default()
+    })
+    .await;
+  assert!(error.is_none());
+
+  let database = test.get_database(&grid_view.id).await;
+  assert_eq!(database.layout_type, DatabaseLayoutPB::Board);
+}
+
+// Update the database layout type from grid to board. Set the checkbox field as the grouping field
+#[tokio::test]
+async fn update_database_layout_event_test2() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let grid_view = test
+    .create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
+    .await;
+  let fields = test.get_all_database_fields(&grid_view.id).await.items;
+
+  let checkbox_field = fields
+    .iter()
+    .find(|field| field.field_type == FieldType::Checkbox)
+    .unwrap();
+  test
+    .set_group_by_field(&grid_view.id, &checkbox_field.id)
+    .await;
+
+  let error = test
+    .update_setting(DatabaseSettingChangesetPB {
+      view_id: grid_view.id.clone(),
+      layout_type: Some(DatabaseLayoutPB::Board),
+      ..Default::default()
+    })
+    .await;
+  assert!(error.is_none());
+
+  // Empty to group id
+  let groups = test.get_groups(&grid_view.id).await;
+  assert_eq!(groups.len(), 2);
+}
+
+// Create a checkbox field in the default board and then set it as the grouping field.
+#[tokio::test]
+async fn set_group_by_checkbox_field_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let board_view = test
+    .create_board(&current_workspace.id, "my board view".to_owned(), vec![])
+    .await;
+
+  let checkbox_field = test.create_field(&board_view.id, FieldType::Checkbox).await;
+  test
+    .set_group_by_field(&board_view.id, &checkbox_field.id)
+    .await;
+
+  let groups = test.get_groups(&board_view.id).await;
+  assert_eq!(groups.len(), 2);
+}
+
+#[tokio::test]
+async fn get_all_calendar_event_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let calendar_view = test
+    .create_calendar(&current_workspace.id, "my calendar view".to_owned(), vec![])
+    .await;
+
+  // By default, there should be no events
+  let events = test.get_all_calendar_events(&calendar_view.id).await;
+  assert!(events.is_empty());
+}
+
+#[tokio::test]
+async fn create_calendar_event_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let calendar_view = test
+    .create_calendar(&current_workspace.id, "my calendar view".to_owned(), vec![])
+    .await;
+  let fields = test.get_all_database_fields(&calendar_view.id).await.items;
+  let date_field = fields
+    .iter()
+    .find(|field| field.field_type == FieldType::DateTime)
+    .unwrap();
+
+  // create a new row
+  let row = test.create_row(&calendar_view.id, None, None).await;
+
+  // Insert data into the date cell of the first row.
+  let timestamp_str = timestamp().to_string();
+  let error = test
+    .update_date_cell(DateChangesetPB {
+      cell_id: CellIdPB {
+        view_id: calendar_view.id.clone(),
+        field_id: date_field.id.clone(),
+        row_id: row.id,
+      },
+      date: Some(timestamp_str.clone()),
+      time: None,
+      include_time: None,
+    })
+    .await;
+  assert!(error.is_none());
+
+  let events = test.get_all_calendar_events(&calendar_view.id).await;
+  assert_eq!(events.len(), 1);
+}