Kaynağa Gözat

fix: reorder view when sibling view was deleted (#2724)

* fix: reorder view when sibling view was deleted

* ci: fix test

* ci: rust fmt
Nathan.fooo 1 yıl önce
ebeveyn
işleme
381d2e6c71

+ 1 - 0
frontend/appflowy_flutter/lib/workspace/application/app/app_bloc.dart

@@ -105,6 +105,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
   }
 
   Future<void> _createView(CreateView value, Emitter<AppState> emit) async {
+    // create a child view for the current view
     final result = await ViewBackendService.createView(
       parentViewId: state.view.id,
       name: value.name,

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

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

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

@@ -85,7 +85,7 @@ checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
 [[package]]
 name = "appflowy-integrate"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5c519e#5c519e988e93f6fbc7d98ce4373c5ff30a547fb1"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
 dependencies = [
  "anyhow",
  "collab",
@@ -887,7 +887,7 @@ dependencies = [
 [[package]]
 name = "collab"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5c519e#5c519e988e93f6fbc7d98ce4373c5ff30a547fb1"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
 dependencies = [
  "anyhow",
  "bytes",
@@ -905,7 +905,7 @@ dependencies = [
 [[package]]
 name = "collab-client-ws"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5c519e#5c519e988e93f6fbc7d98ce4373c5ff30a547fb1"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
 dependencies = [
  "bytes",
  "collab-sync",
@@ -923,7 +923,7 @@ dependencies = [
 [[package]]
 name = "collab-database"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5c519e#5c519e988e93f6fbc7d98ce4373c5ff30a547fb1"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -949,7 +949,7 @@ dependencies = [
 [[package]]
 name = "collab-derive"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5c519e#5c519e988e93f6fbc7d98ce4373c5ff30a547fb1"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -961,7 +961,7 @@ dependencies = [
 [[package]]
 name = "collab-document"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5c519e#5c519e988e93f6fbc7d98ce4373c5ff30a547fb1"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
 dependencies = [
  "anyhow",
  "collab",
@@ -978,7 +978,7 @@ dependencies = [
 [[package]]
 name = "collab-folder"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5c519e#5c519e988e93f6fbc7d98ce4373c5ff30a547fb1"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
 dependencies = [
  "anyhow",
  "collab",
@@ -997,7 +997,7 @@ dependencies = [
 [[package]]
 name = "collab-persistence"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5c519e#5c519e988e93f6fbc7d98ce4373c5ff30a547fb1"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
 dependencies = [
  "bincode",
  "chrono",
@@ -1017,7 +1017,7 @@ dependencies = [
 [[package]]
 name = "collab-plugins"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5c519e#5c519e988e93f6fbc7d98ce4373c5ff30a547fb1"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
 dependencies = [
  "anyhow",
  "async-trait",
@@ -1048,7 +1048,7 @@ dependencies = [
 [[package]]
 name = "collab-sync"
 version = "0.1.0"
-source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=5c519e#5c519e988e93f6fbc7d98ce4373c5ff30a547fb1"
+source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=a647d9#a647d922ef432510d6be0abb5f968d9a75dc7011"
 dependencies = [
  "bytes",
  "collab",

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

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

+ 9 - 1
frontend/rust-lib/flowy-core/src/deps_resolve/collab_deps.rs

@@ -25,7 +25,7 @@ impl SnapshotDB for SnapshotDBImpl {
       .and_then(|pool| Ok(pool.get()?))
       .and_then(|conn| {
         CollabSnapshotTableSql::get_all_snapshots(object_id, &conn)
-          .and_then(|rows| Ok(rows.into_iter().map(|row| row.into()).collect()))
+          .map(|rows| rows.into_iter().map(|row| row.into()).collect())
       })
       .unwrap_or_else(|_| vec![])
   }
@@ -34,6 +34,8 @@ impl SnapshotDB for SnapshotDBImpl {
     &self,
     uid: i64,
     object_id: &str,
+    title: String,
+    collab_type: String,
     snapshot: Snapshot,
     collab: Arc<MutexCollab>,
   ) -> Result<(), PersistenceError> {
@@ -74,7 +76,9 @@ impl SnapshotDB for SnapshotDBImpl {
             CollabSnapshotRow {
               id: uuid::Uuid::new_v4().to_string(),
               object_id: object_id.clone(),
+              title,
               desc,
+              collab_type,
               timestamp: timestamp(),
               data: new_snapshot_data,
             },
@@ -97,7 +101,9 @@ impl SnapshotDB for SnapshotDBImpl {
 struct CollabSnapshotRow {
   id: String,
   object_id: String,
+  title: String,
   desc: String,
+  collab_type: String,
   timestamp: i64,
   data: Vec<u8>,
 }
@@ -118,7 +124,9 @@ impl CollabSnapshotTableSql {
     let values = (
       dsl::id.eq(row.id),
       dsl::object_id.eq(row.object_id),
+      dsl::title.eq(row.title),
       dsl::desc.eq(row.desc),
+      dsl::collab_type.eq(row.collab_type),
       dsl::data.eq(row.data),
       dsl::timestamp.eq(row.timestamp),
     );

+ 0 - 1
frontend/rust-lib/flowy-database2/src/manager.rs

@@ -84,7 +84,6 @@ impl DatabaseManager2 {
     self.get_database(&database_id).await
   }
 
-  #[tracing::instrument(level = "debug", skip(self), err)]
   pub async fn get_database_id_with_view_id(&self, view_id: &str) -> FlowyResult<String> {
     let database_id = self.with_user_database(Err(FlowyError::internal()), |database| {
       database

+ 10 - 7
frontend/rust-lib/flowy-folder2/src/entities/view.rs

@@ -1,12 +1,15 @@
-use crate::entities::parser::view::{ViewDesc, ViewIdentify, ViewName, ViewThumbnail};
-use crate::view_operation::gen_view_id;
-use collab_folder::core::{View, ViewLayout};
-use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
-use flowy_error::ErrorCode;
 use std::collections::HashMap;
 use std::convert::TryInto;
 use std::ops::{Deref, DerefMut};
 
+use collab_folder::core::{View, ViewLayout};
+
+use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
+use flowy_error::ErrorCode;
+
+use crate::entities::parser::view::{ViewDesc, ViewIdentify, ViewName, ViewThumbnail};
+use crate::view_operation::gen_view_id;
+
 #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
 pub struct ViewPB {
   #[pb(index = 1)]
@@ -284,7 +287,7 @@ pub struct MoveViewPayloadPB {
 }
 
 pub struct MoveViewParams {
-  pub item_id: String,
+  pub view_id: String,
   pub from: usize,
   pub to: usize,
 }
@@ -295,7 +298,7 @@ impl TryInto<MoveViewParams> for MoveViewPayloadPB {
   fn try_into(self) -> Result<MoveViewParams, Self::Error> {
     let view_id = ViewIdentify::parse(self.view_id)?.0;
     Ok(MoveViewParams {
-      item_id: view_id,
+      view_id,
       from: self.from as usize,
       to: self.to as usize,
     })

+ 1 - 3
frontend/rust-lib/flowy-folder2/src/event_handler.rs

@@ -72,8 +72,6 @@ pub async fn read_cur_workspace_setting_handler(
   folder: AFPluginState<Arc<Folder2Manager>>,
 ) -> DataResult<WorkspaceSettingPB, FlowyError> {
   let workspace = folder.get_current_workspace().await?;
-  let views = folder.get_workspace_views(&workspace.id).await?;
-  let workspace: WorkspacePB = (workspace, views).into();
   let latest_view: Option<ViewPB> = folder.get_current_view().await;
   data_result_ok(WorkspaceSettingPB {
     workspace,
@@ -149,7 +147,7 @@ pub(crate) async fn move_view_handler(
 ) -> Result<(), FlowyError> {
   let params: MoveViewParams = data.into_inner().try_into()?;
   folder
-    .move_view(&params.item_id, params.from, params.to)
+    .move_view(&params.view_id, params.from, params.to)
     .await?;
   Ok(())
 }

+ 87 - 16
frontend/rust-lib/flowy-folder2/src/manager.rs

@@ -19,7 +19,7 @@ use lib_infra::util::timestamp;
 use crate::deps::{FolderCloudService, FolderUser};
 use crate::entities::{
   view_pb_with_child_views, CreateViewParams, CreateWorkspaceParams, DeletedViewPB,
-  RepeatedTrashPB, RepeatedViewPB, RepeatedWorkspacePB, UpdateViewParams, ViewPB,
+  RepeatedTrashPB, RepeatedViewPB, RepeatedWorkspacePB, UpdateViewParams, ViewPB, WorkspacePB,
 };
 use crate::notification::{
   send_notification, send_workspace_notification, send_workspace_setting_notification,
@@ -61,11 +61,17 @@ impl Folder2Manager {
     Ok(manager)
   }
 
-  pub async fn get_current_workspace(&self) -> FlowyResult<Workspace> {
-    match self.with_folder(None, |folder| folder.get_current_workspace()) {
-      None => Err(FlowyError::record_not_found().context("Can not find the workspace")),
-      Some(workspace) => Ok(workspace),
-    }
+  pub async fn get_current_workspace(&self) -> FlowyResult<WorkspacePB> {
+    self.with_folder(Err(FlowyError::internal()), |folder| {
+      match folder.get_current_workspace() {
+        None => Err(FlowyError::record_not_found().context("Can not find the workspace")),
+        Some(workspace) => {
+          let views = get_workspace_view_pbs(&workspace.id, folder);
+          let workspace: WorkspacePB = (workspace, views).into();
+          Ok(workspace)
+        },
+      }
+    })
   }
 
   /// Return a list of views of the current workspace.
@@ -307,18 +313,48 @@ impl Folder2Manager {
     Ok(())
   }
 
-  /// Move the view from one position to another position.
-  #[tracing::instrument(level = "debug", skip(self), err)]
+  /// Move the view with given id from one position to another position.
+  /// The view will be moved to the new position in the same parent view.
+  /// The passed in index is the index of the view that displayed in the UI.
+  /// We need to convert the index to the real index of the view in the parent view.
+  #[tracing::instrument(level = "trace", skip(self), err)]
   pub async fn move_view(&self, view_id: &str, from: usize, to: usize) -> FlowyResult<()> {
-    let view = self.with_folder(None, |folder| {
-      folder.move_view(view_id, from as u32, to as u32)
-    });
+    if let Some((is_workspace, parent_view_id, child_views)) = self.get_view_relation(view_id).await
+    {
+      // The display parent view is the view that is displayed in the UI
+      let display_views = if is_workspace {
+        self
+          .get_current_workspace()
+          .await?
+          .views
+          .into_iter()
+          .map(|view| view.id)
+          .collect::<Vec<_>>()
+      } else {
+        self
+          .get_view(&parent_view_id)
+          .await?
+          .child_views
+          .into_iter()
+          .map(|view| view.id)
+          .collect::<Vec<_>>()
+      };
 
-    match view {
-      None => tracing::error!("Couldn't find the view. It should not be empty"),
-      Some(view) => {
-        notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.parent_view_id]);
-      },
+      if display_views.len() > to {
+        let to_view_id = display_views[to].clone();
+
+        // Find the actual index of the view in the parent view
+        let actual_from_index = child_views.iter().position(|id| id == view_id);
+        let actual_to_index = child_views.iter().position(|id| id == &to_view_id);
+        if let (Some(actual_from_index), Some(actual_to_index)) =
+          (actual_from_index, actual_to_index)
+        {
+          self.with_folder((), |folder| {
+            folder.move_view(view_id, actual_from_index as u32, actual_to_index as u32);
+          });
+          notify_parent_view_did_change(self.mutex_folder.clone(), vec![parent_view_id]);
+        }
+      }
     }
     Ok(())
   }
@@ -516,6 +552,40 @@ impl Folder2Manager {
       Some(processor) => Ok(processor.clone()),
     }
   }
+
+  /// Returns the relation of the view. The relation is a tuple of (is_workspace, parent_view_id,
+  /// child_view_ids). If the view is a workspace, then the parent_view_id is the workspace id.
+  /// Otherwise, the parent_view_id is the parent view id of the view. The child_view_ids is the
+  /// child view ids of the view.
+  async fn get_view_relation(&self, view_id: &str) -> Option<(bool, String, Vec<String>)> {
+    self.with_folder(None, |folder| {
+      let view = folder.views.get_view(view_id)?;
+      match folder.views.get_view(&view.parent_view_id) {
+        None => folder.get_current_workspace().map(|workspace| {
+          (
+            true,
+            workspace.id,
+            workspace
+              .child_views
+              .items
+              .into_iter()
+              .map(|view| view.id)
+              .collect::<Vec<String>>(),
+          )
+        }),
+        Some(parent_view) => Some((
+          false,
+          parent_view.id,
+          parent_view
+            .children
+            .items
+            .into_iter()
+            .map(|view| view.id)
+            .collect::<Vec<String>>(),
+        )),
+      }
+    })
+  }
 }
 
 /// Listen on the [ViewChange] after create/delete/update events happened
@@ -594,6 +664,7 @@ fn listen_on_trash_change(mut rx: TrashChangeReceiver, weak_mutex_folder: &Weak<
   });
 }
 
+/// Return the views that belong to the workspace. The views are filtered by the trash.
 fn get_workspace_view_pbs(workspace_id: &str, folder: &Folder) -> Vec<ViewPB> {
   let trash_ids = folder
     .trash

+ 2 - 0
frontend/rust-lib/flowy-sqlite/migrations/2023-06-05-135652_collab_snapshot/up.sql

@@ -2,7 +2,9 @@
 CREATE TABLE collab_snapshot (
     id TEXT NOT NULL PRIMARY KEY DEFAULT '',
     object_id TEXT NOT NULL DEFAULT '',
+    title TEXT NOT NULL DEFAULT '',
     desc TEXT NOT NULL DEFAULT '',
+    collab_type TEXT NOT NULL DEFAULT '',
     timestamp BIGINT NOT NULL DEFAULT 0,
     data BLOB NOT NULL DEFAULT (x'')
 );

+ 2 - 0
frontend/rust-lib/flowy-sqlite/src/schema.rs

@@ -4,7 +4,9 @@ diesel::table! {
     collab_snapshot (id) {
         id -> Text,
         object_id -> Text,
+        title -> Text,
         desc -> Text,
+        collab_type -> Text,
         timestamp -> BigInt,
         data -> Binary,
     }

+ 28 - 4
frontend/rust-lib/flowy-test/src/lib.rs

@@ -1,14 +1,18 @@
-use nanoid::nanoid;
-use parking_lot::RwLock;
 use std::env::temp_dir;
 use std::sync::Arc;
 
-use crate::event_builder::EventBuilder;
+use nanoid::nanoid;
+use parking_lot::RwLock;
+
 use flowy_core::{AppFlowyCore, AppFlowyCoreConfig};
-use flowy_folder2::entities::{CreateViewPayloadPB, RepeatedViewIdPB, ViewPB, WorkspaceSettingPB};
+use flowy_folder2::entities::{
+  CreateViewPayloadPB, RepeatedViewIdPB, ViewIdPB, ViewPB, WorkspaceSettingPB,
+};
 use flowy_user::entities::{AuthTypePB, UserProfilePB};
 
+use crate::event_builder::EventBuilder;
 use crate::user_event::{async_sign_up, init_user_setting, SignUpContext};
+
 pub mod event_builder;
 pub mod folder_event;
 pub mod user_event;
@@ -68,6 +72,15 @@ impl FlowyCoreTest {
       .parse::<flowy_folder2::entities::WorkspaceSettingPB>()
   }
 
+  pub async fn get_all_workspace_views(&self) -> Vec<ViewPB> {
+    EventBuilder::new(self.clone())
+      .event(flowy_folder2::event_map::FolderEvent::ReadWorkspaceViews)
+      .async_send()
+      .await
+      .parse::<flowy_folder2::entities::RepeatedViewPB>()
+      .items
+  }
+
   pub async fn delete_view(&self, view_id: &str) {
     let payload = RepeatedViewIdPB {
       items: vec![view_id.to_string()],
@@ -99,6 +112,17 @@ impl FlowyCoreTest {
       .await
       .parse::<flowy_folder2::entities::ViewPB>()
   }
+
+  pub async fn get_view(&self, view_id: &str) -> ViewPB {
+    EventBuilder::new(self.clone())
+      .event(flowy_folder2::event_map::FolderEvent::ReadView)
+      .payload(ViewIdPB {
+        value: view_id.to_string(),
+      })
+      .async_send()
+      .await
+      .parse::<flowy_folder2::entities::ViewPB>()
+  }
 }
 
 impl std::ops::Deref for FlowyCoreTest {

+ 87 - 30
frontend/rust-lib/flowy-test/tests/folder/test.rs

@@ -240,13 +240,7 @@ async fn multiple_hierarchy_view_test() {
     }
   }
 
-  let mut views = EventBuilder::new(test.clone())
-    .event(flowy_folder2::event_map::FolderEvent::ReadWorkspaceViews)
-    .async_send()
-    .await
-    .parse::<flowy_folder2::entities::RepeatedViewPB>()
-    .items;
-
+  let mut views = test.get_all_workspace_views().await;
   // There will be one default view when AppFlowy is initialized. So there will be 4 views in total
   assert_eq!(views.len(), 4);
   views.remove(0);
@@ -294,15 +288,7 @@ async fn multiple_hierarchy_view_test() {
 
       for (k, _child_view) in child_view.child_views.into_iter().enumerate() {
         // Get the last level view
-        let sub_child = EventBuilder::new(test.clone())
-          .event(flowy_folder2::event_map::FolderEvent::ReadView)
-          .payload(ViewIdPB {
-            value: child.id.clone(),
-          })
-          .async_send()
-          .await
-          .parse::<flowy_folder2::entities::ViewPB>();
-
+        let sub_child = test.get_view(&child.id).await;
         assert_eq!(child.name, format!("My {}-{}-{} view", i + 1, j + 1, k + 1));
         assert!(sub_child.child_views.is_empty());
       }
@@ -324,13 +310,7 @@ async fn move_view_event_test() {
         .await;
     }
   }
-  let views = EventBuilder::new(test.clone())
-    .event(flowy_folder2::event_map::FolderEvent::ReadWorkspaceViews)
-    .async_send()
-    .await
-    .parse::<flowy_folder2::entities::RepeatedViewPB>()
-    .items;
-
+  let views = test.get_all_workspace_views().await;
   // There will be one default view when AppFlowy is initialized. So there will be 4 views in total
   assert_eq!(views.len(), 4);
   assert_eq!(views[1].name, "My 1 view");
@@ -348,18 +328,95 @@ async fn move_view_event_test() {
     .async_send()
     .await;
 
-  let views = EventBuilder::new(test.clone())
-    .event(flowy_folder2::event_map::FolderEvent::ReadWorkspaceViews)
-    .async_send()
-    .await
-    .parse::<flowy_folder2::entities::RepeatedViewPB>()
-    .items;
-
+  let views = test.get_all_workspace_views().await;
   assert_eq!(views[1].name, "My 2 view");
   assert_eq!(views[2].name, "My 1 view");
   assert_eq!(views[3].name, "My 3 view");
 }
 
+#[tokio::test]
+async fn move_view_event_after_delete_view_test() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  for i in 1..6 {
+    let _ = test
+      .create_view(&current_workspace.id, format!("My {} view", i))
+      .await;
+  }
+  let views = test.get_all_workspace_views().await;
+  assert_eq!(views[1].name, "My 1 view");
+  assert_eq!(views[2].name, "My 2 view");
+  assert_eq!(views[3].name, "My 3 view");
+  assert_eq!(views[4].name, "My 4 view");
+  assert_eq!(views[5].name, "My 5 view");
+  test.delete_view(&views[3].id).await;
+
+  // There will be one default view when AppFlowy is initialized. So there will be 4 views in total
+  let views = test.get_all_workspace_views().await;
+  assert_eq!(views[1].name, "My 1 view");
+  assert_eq!(views[2].name, "My 2 view");
+  assert_eq!(views[3].name, "My 4 view");
+  assert_eq!(views[4].name, "My 5 view");
+
+  let payload = MoveViewPayloadPB {
+    view_id: views[1].id.clone(),
+    from: 1,
+    to: 3,
+  };
+  let _ = EventBuilder::new(test.clone())
+    .event(flowy_folder2::event_map::FolderEvent::MoveView)
+    .payload(payload)
+    .async_send()
+    .await;
+
+  let views = test.get_all_workspace_views().await;
+  assert_eq!(views[1].name, "My 2 view");
+  assert_eq!(views[2].name, "My 4 view");
+  assert_eq!(views[3].name, "My 1 view");
+  assert_eq!(views[4].name, "My 5 view");
+}
+
+#[tokio::test]
+async fn move_view_event_after_delete_view_test2() {
+  let test = FlowyCoreTest::new_with_user().await;
+  let current_workspace = test.get_current_workspace().await.workspace;
+  let parent = test
+    .create_view(&current_workspace.id, "My view".to_string())
+    .await;
+
+  for j in 1..6 {
+    let _ = test
+      .create_view(&parent.id, format!("My 1-{} view", j))
+      .await;
+  }
+
+  let views = test.get_view(&parent.id).await.child_views;
+  assert_eq!(views.len(), 5);
+  assert_eq!(views[0].name, "My 1-1 view");
+  assert_eq!(views[1].name, "My 1-2 view");
+  assert_eq!(views[2].name, "My 1-3 view");
+  assert_eq!(views[3].name, "My 1-4 view");
+  assert_eq!(views[4].name, "My 1-5 view");
+  test.delete_view(&views[2].id).await;
+
+  let payload = MoveViewPayloadPB {
+    view_id: views[0].id.clone(),
+    from: 0,
+    to: 2,
+  };
+  let _ = EventBuilder::new(test.clone())
+    .event(flowy_folder2::event_map::FolderEvent::MoveView)
+    .payload(payload)
+    .async_send()
+    .await;
+
+  let views = test.get_view(&parent.id).await.child_views;
+  assert_eq!(views[0].name, "My 1-2 view");
+  assert_eq!(views[1].name, "My 1-4 view");
+  assert_eq!(views[2].name, "My 1-1 view");
+  assert_eq!(views[3].name, "My 1-5 view");
+}
+
 #[tokio::test]
 async fn create_parent_view_with_invalid_name() {
   for (name, code) in invalid_workspace_name_test_case() {