mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: update client api (#5208)
* chore: update client api * chore: rename test target * chore: fix test
This commit is contained in:
@ -0,0 +1,333 @@
|
||||
use collab_folder::ViewLayout;
|
||||
|
||||
use flowy_folder::entities::icon::{ViewIconPB, ViewIconTypePB};
|
||||
|
||||
use crate::folder::local_test::script::FolderScript::*;
|
||||
use crate::folder::local_test::script::FolderTest;
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_parent_view_test() {
|
||||
let mut test = FolderTest::new().await;
|
||||
test
|
||||
.run_scripts(vec![CreateParentView {
|
||||
name: "App".to_string(),
|
||||
desc: "App description".to_string(),
|
||||
}])
|
||||
.await;
|
||||
|
||||
let app = test.parent_view.clone();
|
||||
test.run_scripts(vec![ReloadParentView(app.id)]).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn delete_parent_view_test() {
|
||||
let mut test = FolderTest::new().await;
|
||||
let parent_view = test.parent_view.clone();
|
||||
test
|
||||
.run_scripts(vec![DeleteParentView, ReloadParentView(parent_view.id)])
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_parent_view_then_restore() {
|
||||
let mut test = FolderTest::new().await;
|
||||
test
|
||||
.run_scripts(vec![ReloadParentView(test.parent_view.id.clone())])
|
||||
.await;
|
||||
|
||||
let parent_view = test.parent_view.clone();
|
||||
test
|
||||
.run_scripts(vec![
|
||||
DeleteParentView,
|
||||
RestoreAppFromTrash,
|
||||
ReloadParentView(parent_view.id.clone()),
|
||||
AssertParentView(parent_view),
|
||||
])
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_parent_view_test() {
|
||||
let mut test = FolderTest::new().await;
|
||||
let parent_view = test.parent_view.clone();
|
||||
let new_name = "😁 hell world".to_owned();
|
||||
assert_ne!(parent_view.name, new_name);
|
||||
|
||||
test
|
||||
.run_scripts(vec![
|
||||
UpdateParentView {
|
||||
name: Some(new_name.clone()),
|
||||
desc: None,
|
||||
is_favorite: None,
|
||||
},
|
||||
ReloadParentView(parent_view.id),
|
||||
])
|
||||
.await;
|
||||
assert_eq!(test.parent_view.name, new_name);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_sub_views_test() {
|
||||
let mut test = FolderTest::new().await;
|
||||
let mut app = test.parent_view.clone();
|
||||
test
|
||||
.run_scripts(vec![
|
||||
CreateView {
|
||||
name: "View A".to_owned(),
|
||||
desc: "View A description".to_owned(),
|
||||
layout: ViewLayout::Document,
|
||||
},
|
||||
CreateView {
|
||||
name: "Grid".to_owned(),
|
||||
desc: "Grid description".to_owned(),
|
||||
layout: ViewLayout::Grid,
|
||||
},
|
||||
ReloadParentView(app.id),
|
||||
])
|
||||
.await;
|
||||
|
||||
app = test.parent_view.clone();
|
||||
assert_eq!(app.child_views.len(), 3);
|
||||
assert_eq!(app.child_views[1].name, "View A");
|
||||
assert_eq!(app.child_views[2].name, "Grid")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn view_update() {
|
||||
let mut test = FolderTest::new().await;
|
||||
let view = test.child_view.clone();
|
||||
let new_name = "😁 123".to_owned();
|
||||
assert_ne!(view.name, new_name);
|
||||
|
||||
test
|
||||
.run_scripts(vec![
|
||||
UpdateView {
|
||||
name: Some(new_name.clone()),
|
||||
desc: None,
|
||||
is_favorite: None,
|
||||
},
|
||||
ReadView(view.id),
|
||||
])
|
||||
.await;
|
||||
assert_eq!(test.child_view.name, new_name);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn view_icon_update_test() {
|
||||
let mut test = FolderTest::new().await;
|
||||
let view = test.child_view.clone();
|
||||
let new_icon = ViewIconPB {
|
||||
ty: ViewIconTypePB::Emoji,
|
||||
value: "👍".to_owned(),
|
||||
};
|
||||
assert!(view.icon.is_none());
|
||||
test
|
||||
.run_scripts(vec![
|
||||
UpdateViewIcon {
|
||||
icon: Some(new_icon.clone()),
|
||||
},
|
||||
ReadView(view.id.clone()),
|
||||
])
|
||||
.await;
|
||||
|
||||
assert_eq!(test.child_view.icon, Some(new_icon));
|
||||
|
||||
test
|
||||
.run_scripts(vec![UpdateViewIcon { icon: None }, ReadView(view.id)])
|
||||
.await;
|
||||
assert_eq!(test.child_view.icon, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn view_delete() {
|
||||
let mut test = FolderTest::new().await;
|
||||
let view = test.child_view.clone();
|
||||
test.run_scripts(vec![DeleteView, ReadView(view.id)]).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn view_delete_then_restore() {
|
||||
let mut test = FolderTest::new().await;
|
||||
let view = test.child_view.clone();
|
||||
test
|
||||
.run_scripts(vec![
|
||||
DeleteView,
|
||||
RestoreViewFromTrash,
|
||||
ReadView(view.id.clone()),
|
||||
AssertView(view),
|
||||
])
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn view_delete_all() {
|
||||
let mut test = FolderTest::new().await;
|
||||
let parent_view = test.parent_view.clone();
|
||||
test
|
||||
.run_scripts(vec![
|
||||
CreateView {
|
||||
name: "View A".to_owned(),
|
||||
desc: "View A description".to_owned(),
|
||||
layout: ViewLayout::Document,
|
||||
},
|
||||
CreateView {
|
||||
name: "Grid".to_owned(),
|
||||
desc: "Grid description".to_owned(),
|
||||
layout: ViewLayout::Grid,
|
||||
},
|
||||
ReloadParentView(parent_view.id.clone()),
|
||||
])
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
test.parent_view.child_views.len(),
|
||||
3,
|
||||
"num of belongings should be 3"
|
||||
);
|
||||
let view_ids = test
|
||||
.parent_view
|
||||
.child_views
|
||||
.iter()
|
||||
.map(|view| view.id.clone())
|
||||
.collect::<Vec<String>>();
|
||||
test
|
||||
.run_scripts(vec![
|
||||
DeleteViews(view_ids),
|
||||
ReloadParentView(parent_view.id),
|
||||
ReadTrash,
|
||||
])
|
||||
.await;
|
||||
|
||||
assert_eq!(test.parent_view.child_views.len(), 0);
|
||||
assert_eq!(test.trash.len(), 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn view_delete_all_permanent() {
|
||||
let mut test = FolderTest::new().await;
|
||||
let parent_view = test.parent_view.clone();
|
||||
test
|
||||
.run_scripts(vec![
|
||||
CreateView {
|
||||
name: "View A".to_owned(),
|
||||
desc: "View A description".to_owned(),
|
||||
layout: ViewLayout::Document,
|
||||
},
|
||||
ReloadParentView(parent_view.id.clone()),
|
||||
])
|
||||
.await;
|
||||
|
||||
let view_ids = test
|
||||
.parent_view
|
||||
.child_views
|
||||
.iter()
|
||||
.map(|view| view.id.clone())
|
||||
.collect::<Vec<String>>();
|
||||
test
|
||||
.run_scripts(vec![
|
||||
DeleteViews(view_ids),
|
||||
ReloadParentView(parent_view.id),
|
||||
DeleteAllTrash,
|
||||
ReadTrash,
|
||||
])
|
||||
.await;
|
||||
|
||||
assert_eq!(test.parent_view.child_views.len(), 0);
|
||||
assert_eq!(test.trash.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn toggle_favorites() {
|
||||
let mut test = FolderTest::new().await;
|
||||
let view = test.child_view.clone();
|
||||
test
|
||||
.run_scripts(vec![
|
||||
ReadView(view.id.clone()),
|
||||
ToggleFavorite,
|
||||
ReadFavorites,
|
||||
ReadView(view.id.clone()),
|
||||
])
|
||||
.await;
|
||||
assert!(test.child_view.is_favorite);
|
||||
assert_ne!(test.favorites.len(), 0);
|
||||
assert_eq!(test.favorites[0].id, view.id);
|
||||
|
||||
let view = test.child_view.clone();
|
||||
test
|
||||
.run_scripts(vec![
|
||||
ReadView(view.id.clone()),
|
||||
ToggleFavorite,
|
||||
ReadFavorites,
|
||||
ReadView(view.id.clone()),
|
||||
])
|
||||
.await;
|
||||
|
||||
assert!(!test.child_view.is_favorite);
|
||||
assert!(test.favorites.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_favorites() {
|
||||
let mut test = FolderTest::new().await;
|
||||
let view = test.child_view.clone();
|
||||
test
|
||||
.run_scripts(vec![
|
||||
ReadView(view.id.clone()),
|
||||
ToggleFavorite,
|
||||
ReadFavorites,
|
||||
ReadView(view.id.clone()),
|
||||
])
|
||||
.await;
|
||||
assert!(test.child_view.is_favorite);
|
||||
assert_ne!(test.favorites.len(), 0);
|
||||
assert_eq!(test.favorites[0].id, view.id);
|
||||
|
||||
test.run_scripts(vec![DeleteView, ReadFavorites]).await;
|
||||
assert_eq!(test.favorites.len(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn move_view_event_test() {
|
||||
let mut test = FolderTest::new().await;
|
||||
let parent_view = test.parent_view.clone();
|
||||
test
|
||||
.run_scripts(vec![
|
||||
CreateView {
|
||||
name: "View A".to_owned(),
|
||||
desc: "View A description".to_owned(),
|
||||
layout: ViewLayout::Document,
|
||||
},
|
||||
ReloadParentView(parent_view.id.clone()),
|
||||
])
|
||||
.await;
|
||||
let view_ids = test
|
||||
.parent_view
|
||||
.child_views
|
||||
.iter()
|
||||
.map(|view| view.id.clone())
|
||||
.collect::<Vec<String>>();
|
||||
let move_view_id = view_ids[0].clone();
|
||||
let new_prev_view_id = view_ids[1].clone();
|
||||
let new_parent_view_id = parent_view.id.clone();
|
||||
test
|
||||
.run_scripts(vec![
|
||||
MoveView {
|
||||
view_id: move_view_id.clone(),
|
||||
new_parent_id: new_parent_view_id.clone(),
|
||||
prev_view_id: Some(new_prev_view_id.clone()),
|
||||
},
|
||||
ReloadParentView(parent_view.id.clone()),
|
||||
])
|
||||
.await;
|
||||
|
||||
let after_view_ids = test
|
||||
.parent_view
|
||||
.child_views
|
||||
.iter()
|
||||
.map(|view| view.id.clone())
|
||||
.collect::<Vec<String>>();
|
||||
assert_eq!(after_view_ids[0], view_ids[1]);
|
||||
assert_eq!(after_view_ids[1], view_ids[0]);
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
use crate::util::unzip;
|
||||
use event_integration_test::EventIntegrationTest;
|
||||
use flowy_core::DEFAULT_NAME;
|
||||
use flowy_folder::entities::{ImportPB, ImportTypePB, ViewLayoutPB};
|
||||
|
||||
#[tokio::test]
|
||||
async fn import_492_row_csv_file_test() {
|
||||
// csv_500r_15c.csv is a file with 492 rows and 17 columns
|
||||
let file_name = "csv_492r_17c.csv".to_string();
|
||||
let (cleaner, csv_file_path) = unzip("./tests/asset", &file_name).unwrap();
|
||||
|
||||
let csv_string = std::fs::read_to_string(csv_file_path).unwrap();
|
||||
let test = EventIntegrationTest::new_with_name(DEFAULT_NAME).await;
|
||||
test.sign_up_as_anon().await;
|
||||
|
||||
let workspace_id = test.get_current_workspace().await.id;
|
||||
let import_data = gen_import_data(file_name, csv_string, workspace_id);
|
||||
|
||||
let view = test.import_data(import_data).await;
|
||||
let database = test.get_database(&view.id).await;
|
||||
assert_eq!(database.rows.len(), 492);
|
||||
drop(cleaner);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn import_10240_row_csv_file_test() {
|
||||
// csv_22577r_15c.csv is a file with 10240 rows and 15 columns
|
||||
let file_name = "csv_10240r_15c.csv".to_string();
|
||||
let (cleaner, csv_file_path) = unzip("./tests/asset", &file_name).unwrap();
|
||||
|
||||
let csv_string = std::fs::read_to_string(csv_file_path).unwrap();
|
||||
let test = EventIntegrationTest::new_with_name(DEFAULT_NAME).await;
|
||||
test.sign_up_as_anon().await;
|
||||
|
||||
let workspace_id = test.get_current_workspace().await.id;
|
||||
let import_data = gen_import_data(file_name, csv_string, workspace_id);
|
||||
|
||||
let view = test.import_data(import_data).await;
|
||||
let database = test.get_database(&view.id).await;
|
||||
assert_eq!(database.rows.len(), 10240);
|
||||
|
||||
drop(cleaner);
|
||||
}
|
||||
|
||||
fn gen_import_data(file_name: String, csv_string: String, workspace_id: String) -> ImportPB {
|
||||
let import_data = ImportPB {
|
||||
parent_view_id: workspace_id.clone(),
|
||||
name: file_name,
|
||||
data: Some(csv_string.as_bytes().to_vec()),
|
||||
file_path: None,
|
||||
view_layout: ViewLayoutPB::Grid,
|
||||
import_type: ImportTypePB::CSV,
|
||||
};
|
||||
import_data
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
mod folder_test;
|
||||
mod import_test;
|
||||
mod script;
|
||||
mod subscription_test;
|
||||
mod test;
|
@ -0,0 +1,384 @@
|
||||
use collab_folder::ViewLayout;
|
||||
|
||||
use event_integration_test::event_builder::EventBuilder;
|
||||
use event_integration_test::EventIntegrationTest;
|
||||
use flowy_folder::entities::icon::{UpdateViewIconPayloadPB, ViewIconPB};
|
||||
use flowy_folder::entities::*;
|
||||
use flowy_folder::event_map::FolderEvent::*;
|
||||
|
||||
pub enum FolderScript {
|
||||
#[allow(dead_code)]
|
||||
CreateWorkspace {
|
||||
name: String,
|
||||
desc: String,
|
||||
},
|
||||
#[allow(dead_code)]
|
||||
AssertWorkspace(WorkspacePB),
|
||||
#[allow(dead_code)]
|
||||
ReadWorkspace(String),
|
||||
CreateParentView {
|
||||
name: String,
|
||||
desc: String,
|
||||
},
|
||||
AssertParentView(ViewPB),
|
||||
ReloadParentView(String),
|
||||
UpdateParentView {
|
||||
name: Option<String>,
|
||||
desc: Option<String>,
|
||||
is_favorite: Option<bool>,
|
||||
},
|
||||
DeleteParentView,
|
||||
|
||||
// View
|
||||
CreateView {
|
||||
name: String,
|
||||
desc: String,
|
||||
layout: ViewLayout,
|
||||
},
|
||||
AssertView(ViewPB),
|
||||
ReadView(String),
|
||||
UpdateView {
|
||||
name: Option<String>,
|
||||
desc: Option<String>,
|
||||
is_favorite: Option<bool>,
|
||||
},
|
||||
UpdateViewIcon {
|
||||
icon: Option<ViewIconPB>,
|
||||
},
|
||||
DeleteView,
|
||||
DeleteViews(Vec<String>),
|
||||
MoveView {
|
||||
view_id: String,
|
||||
new_parent_id: String,
|
||||
prev_view_id: Option<String>,
|
||||
},
|
||||
|
||||
// Trash
|
||||
RestoreAppFromTrash,
|
||||
RestoreViewFromTrash,
|
||||
ReadTrash,
|
||||
DeleteAllTrash,
|
||||
ToggleFavorite,
|
||||
ReadFavorites,
|
||||
}
|
||||
|
||||
pub struct FolderTest {
|
||||
pub sdk: EventIntegrationTest,
|
||||
pub workspace: WorkspacePB,
|
||||
pub parent_view: ViewPB,
|
||||
pub child_view: ViewPB,
|
||||
pub trash: Vec<TrashPB>,
|
||||
pub favorites: Vec<ViewPB>,
|
||||
}
|
||||
|
||||
impl FolderTest {
|
||||
pub async fn new() -> Self {
|
||||
let sdk = EventIntegrationTest::new().await;
|
||||
let _ = sdk.init_anon_user().await;
|
||||
let workspace = sdk.folder_manager.get_current_workspace().await.unwrap();
|
||||
let parent_view = create_view(
|
||||
&sdk,
|
||||
&workspace.id,
|
||||
"first level view",
|
||||
"",
|
||||
ViewLayout::Document,
|
||||
)
|
||||
.await;
|
||||
let view = create_view(
|
||||
&sdk,
|
||||
&parent_view.id,
|
||||
"second level view",
|
||||
"",
|
||||
ViewLayout::Document,
|
||||
)
|
||||
.await;
|
||||
Self {
|
||||
sdk,
|
||||
workspace,
|
||||
parent_view,
|
||||
child_view: view,
|
||||
trash: vec![],
|
||||
favorites: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_scripts(&mut self, scripts: Vec<FolderScript>) {
|
||||
for script in scripts {
|
||||
self.run_script(script).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_script(&mut self, script: FolderScript) {
|
||||
let sdk = &self.sdk;
|
||||
match script {
|
||||
FolderScript::CreateWorkspace { name, desc } => {
|
||||
let workspace = create_workspace(sdk, &name, &desc).await;
|
||||
self.workspace = workspace;
|
||||
},
|
||||
FolderScript::AssertWorkspace(workspace) => {
|
||||
assert_eq!(self.workspace, workspace, "Workspace not equal");
|
||||
},
|
||||
FolderScript::ReadWorkspace(workspace_id) => {
|
||||
let workspace = read_workspace(sdk, workspace_id).await;
|
||||
self.workspace = workspace;
|
||||
},
|
||||
FolderScript::CreateParentView { name, desc } => {
|
||||
let app = create_view(sdk, &self.workspace.id, &name, &desc, ViewLayout::Document).await;
|
||||
self.parent_view = app;
|
||||
},
|
||||
FolderScript::AssertParentView(app) => {
|
||||
assert_eq!(self.parent_view, app, "App not equal");
|
||||
},
|
||||
FolderScript::ReloadParentView(parent_view_id) => {
|
||||
let parent_view = read_view(sdk, &parent_view_id).await;
|
||||
self.parent_view = parent_view;
|
||||
},
|
||||
FolderScript::UpdateParentView {
|
||||
name,
|
||||
desc,
|
||||
is_favorite,
|
||||
} => {
|
||||
update_view(sdk, &self.parent_view.id, name, desc, is_favorite).await;
|
||||
},
|
||||
FolderScript::DeleteParentView => {
|
||||
delete_view(sdk, vec![self.parent_view.id.clone()]).await;
|
||||
},
|
||||
FolderScript::CreateView { name, desc, layout } => {
|
||||
let view = create_view(sdk, &self.parent_view.id, &name, &desc, layout).await;
|
||||
self.child_view = view;
|
||||
},
|
||||
FolderScript::MoveView {
|
||||
view_id,
|
||||
new_parent_id,
|
||||
prev_view_id,
|
||||
} => {
|
||||
move_view(sdk, view_id, new_parent_id, prev_view_id).await;
|
||||
},
|
||||
FolderScript::AssertView(view) => {
|
||||
assert_eq!(self.child_view, view, "View not equal");
|
||||
},
|
||||
FolderScript::ReadView(view_id) => {
|
||||
let view = read_view(sdk, &view_id).await;
|
||||
self.child_view = view;
|
||||
},
|
||||
FolderScript::UpdateView {
|
||||
name,
|
||||
desc,
|
||||
is_favorite,
|
||||
} => {
|
||||
update_view(sdk, &self.child_view.id, name, desc, is_favorite).await;
|
||||
},
|
||||
FolderScript::UpdateViewIcon { icon } => {
|
||||
update_view_icon(sdk, &self.child_view.id, icon).await;
|
||||
},
|
||||
FolderScript::DeleteView => {
|
||||
delete_view(sdk, vec![self.child_view.id.clone()]).await;
|
||||
},
|
||||
FolderScript::DeleteViews(view_ids) => {
|
||||
delete_view(sdk, view_ids).await;
|
||||
},
|
||||
FolderScript::RestoreAppFromTrash => {
|
||||
restore_app_from_trash(sdk, &self.parent_view.id).await;
|
||||
},
|
||||
FolderScript::RestoreViewFromTrash => {
|
||||
restore_view_from_trash(sdk, &self.child_view.id).await;
|
||||
},
|
||||
FolderScript::ReadTrash => {
|
||||
let trash = read_trash(sdk).await;
|
||||
self.trash = trash.items;
|
||||
},
|
||||
FolderScript::DeleteAllTrash => {
|
||||
delete_all_trash(sdk).await;
|
||||
self.trash = vec![];
|
||||
},
|
||||
FolderScript::ToggleFavorite => {
|
||||
toggle_favorites(sdk, vec![self.child_view.id.clone()]).await;
|
||||
},
|
||||
FolderScript::ReadFavorites => {
|
||||
let favorites = read_favorites(sdk).await;
|
||||
self.favorites = favorites.to_vec();
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
pub async fn create_workspace(sdk: &EventIntegrationTest, name: &str, desc: &str) -> WorkspacePB {
|
||||
let request = CreateWorkspacePayloadPB {
|
||||
name: name.to_owned(),
|
||||
desc: desc.to_owned(),
|
||||
};
|
||||
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(CreateFolderWorkspace)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<WorkspacePB>()
|
||||
}
|
||||
|
||||
pub async fn read_workspace(sdk: &EventIntegrationTest, workspace_id: String) -> WorkspacePB {
|
||||
let request = WorkspaceIdPB {
|
||||
value: workspace_id,
|
||||
};
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(ReadCurrentWorkspace)
|
||||
.payload(request.clone())
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<WorkspacePB>()
|
||||
}
|
||||
|
||||
pub async fn create_view(
|
||||
sdk: &EventIntegrationTest,
|
||||
parent_view_id: &str,
|
||||
name: &str,
|
||||
desc: &str,
|
||||
layout: ViewLayout,
|
||||
) -> ViewPB {
|
||||
let request = CreateViewPayloadPB {
|
||||
parent_view_id: parent_view_id.to_string(),
|
||||
name: name.to_string(),
|
||||
desc: desc.to_string(),
|
||||
thumbnail: None,
|
||||
layout: layout.into(),
|
||||
initial_data: vec![],
|
||||
meta: Default::default(),
|
||||
set_as_current: true,
|
||||
index: None,
|
||||
section: None,
|
||||
};
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(CreateView)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<ViewPB>()
|
||||
}
|
||||
|
||||
pub async fn read_view(sdk: &EventIntegrationTest, view_id: &str) -> ViewPB {
|
||||
let view_id = ViewIdPB::from(view_id);
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(GetView)
|
||||
.payload(view_id)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<ViewPB>()
|
||||
}
|
||||
|
||||
pub async fn move_view(
|
||||
sdk: &EventIntegrationTest,
|
||||
view_id: String,
|
||||
parent_id: String,
|
||||
prev_view_id: Option<String>,
|
||||
) {
|
||||
let payload = MoveNestedViewPayloadPB {
|
||||
view_id,
|
||||
new_parent_id: parent_id,
|
||||
prev_view_id,
|
||||
from_section: None,
|
||||
to_section: None,
|
||||
};
|
||||
let error = EventBuilder::new(sdk.clone())
|
||||
.event(MoveNestedView)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
.error();
|
||||
|
||||
assert!(error.is_none());
|
||||
}
|
||||
pub async fn update_view(
|
||||
sdk: &EventIntegrationTest,
|
||||
view_id: &str,
|
||||
name: Option<String>,
|
||||
desc: Option<String>,
|
||||
is_favorite: Option<bool>,
|
||||
) {
|
||||
println!("Toggling update view {:?}", is_favorite);
|
||||
let request = UpdateViewPayloadPB {
|
||||
view_id: view_id.to_string(),
|
||||
name,
|
||||
desc,
|
||||
is_favorite,
|
||||
..Default::default()
|
||||
};
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(UpdateView)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn update_view_icon(sdk: &EventIntegrationTest, view_id: &str, icon: Option<ViewIconPB>) {
|
||||
let request = UpdateViewIconPayloadPB {
|
||||
view_id: view_id.to_string(),
|
||||
icon,
|
||||
};
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(UpdateViewIcon)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn delete_view(sdk: &EventIntegrationTest, view_ids: Vec<String>) {
|
||||
let request = RepeatedViewIdPB { items: view_ids };
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(DeleteView)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn read_trash(sdk: &EventIntegrationTest) -> RepeatedTrashPB {
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(ListTrashItems)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<RepeatedTrashPB>()
|
||||
}
|
||||
|
||||
pub async fn restore_app_from_trash(sdk: &EventIntegrationTest, app_id: &str) {
|
||||
let id = TrashIdPB {
|
||||
id: app_id.to_owned(),
|
||||
};
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(RestoreTrashItem)
|
||||
.payload(id)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn restore_view_from_trash(sdk: &EventIntegrationTest, view_id: &str) {
|
||||
let id = TrashIdPB {
|
||||
id: view_id.to_owned(),
|
||||
};
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(RestoreTrashItem)
|
||||
.payload(id)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn delete_all_trash(sdk: &EventIntegrationTest) {
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(PermanentlyDeleteAllTrashItem)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn toggle_favorites(sdk: &EventIntegrationTest, view_id: Vec<String>) {
|
||||
let request = RepeatedViewIdPB { items: view_id };
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(ToggleFavorite)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn read_favorites(sdk: &EventIntegrationTest) -> RepeatedViewPB {
|
||||
EventBuilder::new(sdk.clone())
|
||||
.event(ReadFavorites)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<RepeatedViewPB>()
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use event_integration_test::EventIntegrationTest;
|
||||
use flowy_folder::entities::{ChildViewUpdatePB, RepeatedViewPB, UpdateViewPayloadPB};
|
||||
use flowy_folder::notification::FolderNotification;
|
||||
|
||||
use crate::util::receive_with_timeout;
|
||||
|
||||
#[tokio::test]
|
||||
/// The primary purpose of this test is to validate that the notification subscription mechanism
|
||||
/// correctly notifies the subscriber of updates to workspace views.
|
||||
/// 1. Initialize the `FlowyCoreTest` with a guest user.
|
||||
/// 2. Retrieve the current workspace for the test user.
|
||||
/// 3. Subscribe to workspace view updates using the `RepeatedViewPB` notification.
|
||||
/// 4. Spawn a new asynchronous task to create a new view named "test_view" within the workspace.
|
||||
/// 5. Await the notification for workspace view updates with a timeout of 30 seconds.
|
||||
/// 6. Ensure that the received views contain the newly created "test_view".
|
||||
async fn create_child_view_in_workspace_subscription_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let workspace = test.get_current_workspace().await;
|
||||
let rx = test
|
||||
.notification_sender
|
||||
.subscribe::<RepeatedViewPB>(&workspace.id, FolderNotification::DidUpdateWorkspaceViews);
|
||||
|
||||
let cloned_test = test.clone();
|
||||
let cloned_workspace_id = workspace.id.clone();
|
||||
test.appflowy_core.dispatcher().spawn(async move {
|
||||
cloned_test
|
||||
.create_view(&cloned_workspace_id, "workspace child view".to_string())
|
||||
.await;
|
||||
});
|
||||
|
||||
let views = receive_with_timeout(rx, Duration::from_secs(30))
|
||||
.await
|
||||
.unwrap()
|
||||
.items;
|
||||
assert_eq!(views.len(), 2);
|
||||
assert_eq!(views[1].name, "workspace child view".to_string());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_child_view_in_view_subscription_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let mut workspace = test.get_current_workspace().await;
|
||||
let workspace_child_view = workspace.views.pop().unwrap();
|
||||
let rx = test.notification_sender.subscribe::<ChildViewUpdatePB>(
|
||||
&workspace_child_view.id,
|
||||
FolderNotification::DidUpdateChildViews,
|
||||
);
|
||||
|
||||
let cloned_test = test.clone();
|
||||
let child_view_id = workspace_child_view.id.clone();
|
||||
test.appflowy_core.dispatcher().spawn(async move {
|
||||
cloned_test
|
||||
.create_view(
|
||||
&child_view_id,
|
||||
"workspace child view's child view".to_string(),
|
||||
)
|
||||
.await;
|
||||
});
|
||||
|
||||
let update = receive_with_timeout(rx, Duration::from_secs(30))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(update.create_child_views.len(), 1);
|
||||
assert_eq!(
|
||||
update.create_child_views[0].name,
|
||||
"workspace child view's child view".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_view_subscription_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let workspace = test.get_current_workspace().await;
|
||||
let rx = test
|
||||
.notification_sender
|
||||
.subscribe::<ChildViewUpdatePB>(&workspace.id, FolderNotification::DidUpdateChildViews);
|
||||
|
||||
let cloned_test = test.clone();
|
||||
let delete_view_id = workspace.views.first().unwrap().id.clone();
|
||||
let cloned_delete_view_id = delete_view_id.clone();
|
||||
test
|
||||
.appflowy_core
|
||||
.dispatcher()
|
||||
.spawn(async move {
|
||||
cloned_test.delete_view(&cloned_delete_view_id).await;
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let update = test
|
||||
.appflowy_core
|
||||
.dispatcher()
|
||||
.run_until(receive_with_timeout(rx, Duration::from_secs(30)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(update.delete_child_views.len(), 1);
|
||||
assert_eq!(update.delete_child_views[0], delete_view_id);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_view_subscription_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let mut workspace = test.get_current_workspace().await;
|
||||
let rx = test
|
||||
.notification_sender
|
||||
.subscribe::<ChildViewUpdatePB>(&workspace.id, FolderNotification::DidUpdateChildViews);
|
||||
|
||||
let cloned_test = test.clone();
|
||||
let view = workspace.views.pop().unwrap();
|
||||
assert!(!view.is_favorite);
|
||||
|
||||
let update_view_id = view.id.clone();
|
||||
test.appflowy_core.dispatcher().spawn(async move {
|
||||
cloned_test
|
||||
.update_view(UpdateViewPayloadPB {
|
||||
view_id: update_view_id,
|
||||
name: Some("hello world".to_string()),
|
||||
is_favorite: Some(true),
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
});
|
||||
|
||||
let update = receive_with_timeout(rx, Duration::from_secs(30))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(update.update_child_views.len(), 1);
|
||||
let expected_view = update.update_child_views.first().unwrap();
|
||||
assert_eq!(expected_view.id, view.id);
|
||||
assert_eq!(expected_view.name, "hello world".to_string());
|
||||
assert!(expected_view.is_favorite);
|
||||
}
|
@ -0,0 +1,560 @@
|
||||
use event_integration_test::event_builder::EventBuilder;
|
||||
use event_integration_test::EventIntegrationTest;
|
||||
use flowy_folder::entities::icon::{UpdateViewIconPayloadPB, ViewIconPB, ViewIconTypePB};
|
||||
use flowy_folder::entities::*;
|
||||
use flowy_user::errors::ErrorCode;
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_workspace_event_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let request = CreateWorkspacePayloadPB {
|
||||
name: "my second workspace".to_owned(),
|
||||
desc: "".to_owned(),
|
||||
};
|
||||
let view_pb = EventBuilder::new(test)
|
||||
.event(flowy_folder::event_map::FolderEvent::CreateFolderWorkspace)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<flowy_folder::entities::ViewPB>();
|
||||
|
||||
assert_eq!(view_pb.parent_view_id, "my second workspace".to_owned());
|
||||
}
|
||||
|
||||
// #[tokio::test]
|
||||
// async fn open_workspace_event_test() {
|
||||
// let test = EventIntegrationTest::new_with_guest_user().await;
|
||||
// let payload = CreateWorkspacePayloadPB {
|
||||
// name: "my second workspace".to_owned(),
|
||||
// desc: "".to_owned(),
|
||||
// };
|
||||
// // create a workspace
|
||||
// let resp_1 = EventBuilder::new(test.clone())
|
||||
// .event(flowy_folder::event_map::FolderEvent::CreateWorkspace)
|
||||
// .payload(payload)
|
||||
// .async_send()
|
||||
// .await
|
||||
// .parse::<flowy_folder::entities::WorkspacePB>();
|
||||
//
|
||||
// // open the workspace
|
||||
// let payload = WorkspaceIdPB {
|
||||
// value: Some(resp_1.id.clone()),
|
||||
// };
|
||||
// let resp_2 = EventBuilder::new(test)
|
||||
// .event(flowy_folder::event_map::FolderEvent::OpenWorkspace)
|
||||
// .payload(payload)
|
||||
// .async_send()
|
||||
// .await
|
||||
// .parse::<flowy_folder::entities::WorkspacePB>();
|
||||
//
|
||||
// assert_eq!(resp_1.id, resp_2.id);
|
||||
// assert_eq!(resp_1.name, resp_2.name);
|
||||
// }
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_view_event_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let current_workspace = test.get_current_workspace().await;
|
||||
let view = test
|
||||
.create_view(¤t_workspace.id, "My first view".to_string())
|
||||
.await;
|
||||
assert_eq!(view.parent_view_id, current_workspace.id);
|
||||
assert_eq!(view.name, "My first view");
|
||||
assert_eq!(view.layout, ViewLayoutPB::Document);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_view_event_with_name_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let current_workspace = test.get_current_workspace().await;
|
||||
let view = test
|
||||
.create_view(¤t_workspace.id, "My first view".to_string())
|
||||
.await;
|
||||
|
||||
let error = test
|
||||
.update_view(UpdateViewPayloadPB {
|
||||
view_id: view.id.clone(),
|
||||
name: Some("My second view".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
assert!(error.is_none());
|
||||
|
||||
let view = test.get_view(&view.id).await;
|
||||
assert_eq!(view.name, "My second view");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn update_view_icon_event_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let current_workspace = test.get_current_workspace().await;
|
||||
let view = test
|
||||
.create_view(¤t_workspace.id, "My first view".to_string())
|
||||
.await;
|
||||
|
||||
let new_icon = ViewIconPB {
|
||||
ty: ViewIconTypePB::Emoji,
|
||||
value: "👍".to_owned(),
|
||||
};
|
||||
let error = test
|
||||
.update_view_icon(UpdateViewIconPayloadPB {
|
||||
view_id: view.id.clone(),
|
||||
icon: Some(new_icon.clone()),
|
||||
})
|
||||
.await;
|
||||
assert!(error.is_none());
|
||||
|
||||
let view = test.get_view(&view.id).await;
|
||||
assert_eq!(view.icon, Some(new_icon));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_view_event_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let current_workspace = test.get_current_workspace().await;
|
||||
let view = test
|
||||
.create_view(¤t_workspace.id, "My first view".to_string())
|
||||
.await;
|
||||
test.delete_view(&view.id).await;
|
||||
|
||||
// Try the read the view
|
||||
let payload = ViewIdPB {
|
||||
value: view.id.clone(),
|
||||
};
|
||||
let error = EventBuilder::new(test.clone())
|
||||
.event(flowy_folder::event_map::FolderEvent::GetView)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
.error()
|
||||
.unwrap();
|
||||
assert_eq!(error.code, ErrorCode::RecordNotFound);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn put_back_trash_event_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let current_workspace = test.get_current_workspace().await;
|
||||
let view = test
|
||||
.create_view(¤t_workspace.id, "My first view".to_string())
|
||||
.await;
|
||||
test.delete_view(&view.id).await;
|
||||
|
||||
// After delete view, the view will be moved to trash
|
||||
let payload = ViewIdPB {
|
||||
value: view.id.clone(),
|
||||
};
|
||||
let error = EventBuilder::new(test.clone())
|
||||
.event(flowy_folder::event_map::FolderEvent::GetView)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
.error()
|
||||
.unwrap();
|
||||
assert_eq!(error.code, ErrorCode::RecordNotFound);
|
||||
|
||||
let payload = TrashIdPB {
|
||||
id: view.id.clone(),
|
||||
};
|
||||
EventBuilder::new(test.clone())
|
||||
.event(flowy_folder::event_map::FolderEvent::RestoreTrashItem)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await;
|
||||
|
||||
let payload = ViewIdPB {
|
||||
value: view.id.clone(),
|
||||
};
|
||||
let error = EventBuilder::new(test.clone())
|
||||
.event(flowy_folder::event_map::FolderEvent::GetView)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
.error();
|
||||
assert!(error.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_view_permanently_event_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let current_workspace = test.get_current_workspace().await;
|
||||
let view = test
|
||||
.create_view(¤t_workspace.id, "My first view".to_string())
|
||||
.await;
|
||||
let payload = RepeatedViewIdPB {
|
||||
items: vec![view.id.clone()],
|
||||
};
|
||||
|
||||
// delete the view. the view will be moved to trash
|
||||
EventBuilder::new(test.clone())
|
||||
.event(flowy_folder::event_map::FolderEvent::DeleteView)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await;
|
||||
|
||||
let trash = EventBuilder::new(test.clone())
|
||||
.event(flowy_folder::event_map::FolderEvent::ListTrashItems)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<flowy_folder::entities::RepeatedTrashPB>()
|
||||
.items;
|
||||
assert_eq!(trash.len(), 1);
|
||||
assert_eq!(trash[0].id, view.id);
|
||||
|
||||
// delete the view from trash
|
||||
let payload = RepeatedTrashIdPB {
|
||||
items: vec![TrashIdPB {
|
||||
id: view.id.clone(),
|
||||
}],
|
||||
};
|
||||
EventBuilder::new(test.clone())
|
||||
.event(flowy_folder::event_map::FolderEvent::PermanentlyDeleteTrashItem)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await;
|
||||
|
||||
// After delete the last view, the trash should be empty
|
||||
let trash = EventBuilder::new(test.clone())
|
||||
.event(flowy_folder::event_map::FolderEvent::ListTrashItems)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<flowy_folder::entities::RepeatedTrashPB>()
|
||||
.items;
|
||||
assert!(trash.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_all_trash_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let current_workspace = test.get_current_workspace().await;
|
||||
|
||||
for i in 0..3 {
|
||||
let view = test
|
||||
.create_view(¤t_workspace.id, format!("My {} view", i))
|
||||
.await;
|
||||
let payload = RepeatedViewIdPB {
|
||||
items: vec![view.id.clone()],
|
||||
};
|
||||
// delete the view. the view will be moved to trash
|
||||
EventBuilder::new(test.clone())
|
||||
.event(flowy_folder::event_map::FolderEvent::DeleteView)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
||||
|
||||
let trash = EventBuilder::new(test.clone())
|
||||
.event(flowy_folder::event_map::FolderEvent::ListTrashItems)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<flowy_folder::entities::RepeatedTrashPB>()
|
||||
.items;
|
||||
assert_eq!(trash.len(), 3);
|
||||
|
||||
// Delete all the trash
|
||||
EventBuilder::new(test.clone())
|
||||
.event(flowy_folder::event_map::FolderEvent::PermanentlyDeleteAllTrashItem)
|
||||
.async_send()
|
||||
.await;
|
||||
|
||||
// After delete the last view, the trash should be empty
|
||||
let trash = EventBuilder::new(test.clone())
|
||||
.event(flowy_folder::event_map::FolderEvent::ListTrashItems)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<flowy_folder::entities::RepeatedTrashPB>()
|
||||
.items;
|
||||
assert!(trash.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn multiple_hierarchy_view_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let current_workspace = test.get_current_workspace().await;
|
||||
for i in 1..4 {
|
||||
let parent = test
|
||||
.create_view(¤t_workspace.id, format!("My {} view", i))
|
||||
.await;
|
||||
for j in 1..3 {
|
||||
let child = test
|
||||
.create_view(&parent.id, format!("My {}-{} view", i, j))
|
||||
.await;
|
||||
for k in 1..2 {
|
||||
let _sub_child = test
|
||||
.create_view(&child.id, format!("My {}-{}-{} view", i, j, k))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// workspace
|
||||
// - view1
|
||||
// - view1-1
|
||||
// - view1-1-1
|
||||
// - view1-2
|
||||
// - view1-2-1
|
||||
// - view2
|
||||
// - view2-1
|
||||
// - view2-1-1
|
||||
// - view2-2
|
||||
// - view2-2-1
|
||||
// - view3
|
||||
// - view3-1
|
||||
// - view3-1-1
|
||||
// - view3-2
|
||||
// - view3-2-1
|
||||
assert_eq!(views[0].name, "My 1 view");
|
||||
assert_eq!(views[1].name, "My 2 view");
|
||||
assert_eq!(views[2].name, "My 3 view");
|
||||
|
||||
assert_eq!(views[0].child_views.len(), 2);
|
||||
// By default only the first level of child views will be loaded
|
||||
assert!(views[0].child_views[0].child_views.is_empty());
|
||||
|
||||
for (i, view) in views.into_iter().enumerate() {
|
||||
for (j, child_view) in view.child_views.into_iter().enumerate() {
|
||||
let payload = ViewIdPB {
|
||||
value: child_view.id.clone(),
|
||||
};
|
||||
|
||||
let child = EventBuilder::new(test.clone())
|
||||
.event(flowy_folder::event_map::FolderEvent::GetView)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<flowy_folder::entities::ViewPB>();
|
||||
assert_eq!(child.name, format!("My {}-{} view", i + 1, j + 1));
|
||||
assert_eq!(child.child_views.len(), 1);
|
||||
// By default only the first level of child views will be loaded
|
||||
assert!(child.child_views[0].child_views.is_empty());
|
||||
|
||||
for (k, _child_view) in child_view.child_views.into_iter().enumerate() {
|
||||
// Get the last level view
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn move_view_event_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let current_workspace = test.get_current_workspace().await;
|
||||
for i in 1..4 {
|
||||
let parent = test
|
||||
.create_view(¤t_workspace.id, format!("My {} view", i))
|
||||
.await;
|
||||
for j in 1..3 {
|
||||
let _ = test
|
||||
.create_view(&parent.id, format!("My {}-{} view", i, j))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
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");
|
||||
assert_eq!(views[2].name, "My 2 view");
|
||||
assert_eq!(views[3].name, "My 3 view");
|
||||
|
||||
let payload = MoveViewPayloadPB {
|
||||
view_id: views[1].id.clone(),
|
||||
from: 1,
|
||||
to: 2,
|
||||
};
|
||||
let _ = EventBuilder::new(test.clone())
|
||||
.event(flowy_folder::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 1 view");
|
||||
assert_eq!(views[3].name, "My 3 view");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn move_view_event_after_delete_view_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let current_workspace = test.get_current_workspace().await;
|
||||
for i in 1..6 {
|
||||
let _ = test
|
||||
.create_view(¤t_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_folder::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 = EventIntegrationTest::new_anon().await;
|
||||
let current_workspace = test.get_current_workspace().await;
|
||||
let parent = test
|
||||
.create_view(¤t_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_folder::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() {
|
||||
let sdk = EventIntegrationTest::new().await;
|
||||
let request = CreateWorkspacePayloadPB {
|
||||
name,
|
||||
desc: "".to_owned(),
|
||||
};
|
||||
assert_eq!(
|
||||
EventBuilder::new(sdk)
|
||||
.event(flowy_folder::event_map::FolderEvent::CreateFolderWorkspace)
|
||||
.payload(request)
|
||||
.async_send()
|
||||
.await
|
||||
.error()
|
||||
.unwrap()
|
||||
.code,
|
||||
code
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid_workspace_name_test_case() -> Vec<(String, ErrorCode)> {
|
||||
vec![
|
||||
("".to_owned(), ErrorCode::WorkspaceNameInvalid),
|
||||
("1234".repeat(100), ErrorCode::WorkspaceNameTooLong),
|
||||
]
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn move_view_across_parent_test() {
|
||||
let test = EventIntegrationTest::new_anon().await;
|
||||
let current_workspace = test.get_current_workspace().await;
|
||||
let parent_1 = test
|
||||
.create_view(¤t_workspace.id, "My view 1".to_string())
|
||||
.await;
|
||||
let parent_2 = test
|
||||
.create_view(¤t_workspace.id, "My view 2".to_string())
|
||||
.await;
|
||||
|
||||
for j in 1..6 {
|
||||
let _ = test
|
||||
.create_view(&parent_1.id, format!("My 1-{} view 1", j))
|
||||
.await;
|
||||
}
|
||||
|
||||
let views = test.get_view(&parent_1.id).await.child_views;
|
||||
// Move `My 1-1 view 1` to `My view 2`
|
||||
let move_view_id = views[0].id.clone();
|
||||
let new_parent_id = parent_2.id.clone();
|
||||
let prev_id = None;
|
||||
move_folder_nested_view(test.clone(), move_view_id, new_parent_id, prev_id).await;
|
||||
let parent1_views = test.get_view(&parent_1.id).await.child_views;
|
||||
let parent2_views = test.get_view(&parent_2.id).await.child_views;
|
||||
assert_eq!(parent2_views.len(), 1);
|
||||
assert_eq!(parent2_views[0].name, "My 1-1 view 1");
|
||||
assert_eq!(parent1_views[0].name, "My 1-2 view 1");
|
||||
|
||||
// Move My 1-2 view 1 from My view 1 to the current workspace and insert it after My view 1.
|
||||
let move_view_id = parent1_views[0].id.clone();
|
||||
let new_parent_id = current_workspace.id.clone();
|
||||
let prev_id = Some(parent_1.id.clone());
|
||||
move_folder_nested_view(test.clone(), move_view_id, new_parent_id, prev_id).await;
|
||||
let parent1_views = test.get_view(&parent_1.id).await.child_views;
|
||||
let workspace_views = test.get_all_workspace_views().await;
|
||||
let workspace_views_len = workspace_views.len();
|
||||
assert_eq!(parent1_views[0].name, "My 1-3 view 1");
|
||||
assert_eq!(workspace_views[workspace_views_len - 3].name, "My view 1");
|
||||
assert_eq!(
|
||||
workspace_views[workspace_views_len - 2].name,
|
||||
"My 1-2 view 1"
|
||||
);
|
||||
assert_eq!(workspace_views[workspace_views_len - 1].name, "My view 2");
|
||||
}
|
||||
|
||||
async fn move_folder_nested_view(
|
||||
sdk: EventIntegrationTest,
|
||||
view_id: String,
|
||||
new_parent_id: String,
|
||||
prev_view_id: Option<String>,
|
||||
) {
|
||||
let payload = MoveNestedViewPayloadPB {
|
||||
view_id,
|
||||
new_parent_id,
|
||||
prev_view_id,
|
||||
from_section: None,
|
||||
to_section: None,
|
||||
};
|
||||
EventBuilder::new(sdk)
|
||||
.event(flowy_folder::event_map::FolderEvent::MoveNestedView)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
mod local_test;
|
||||
|
||||
// #[cfg(feature = "supabase_cloud_test")]
|
||||
// mod supabase_test;
|
@ -0,0 +1,91 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use assert_json_diff::assert_json_eq;
|
||||
use collab::core::collab::MutexCollab;
|
||||
use collab::core::origin::CollabOrigin;
|
||||
use collab::preclude::updates::decoder::Decode;
|
||||
use collab::preclude::{Collab, JsonValue, Update};
|
||||
use collab_entity::CollabType;
|
||||
use collab_folder::FolderData;
|
||||
|
||||
use event_integration_test::event_builder::EventBuilder;
|
||||
use flowy_folder::entities::{FolderSnapshotPB, RepeatedFolderSnapshotPB, WorkspaceIdPB};
|
||||
use flowy_folder::event_map::FolderEvent::GetFolderSnapshots;
|
||||
|
||||
use crate::util::FlowySupabaseTest;
|
||||
|
||||
pub struct FlowySupabaseFolderTest {
|
||||
inner: FlowySupabaseTest,
|
||||
}
|
||||
|
||||
impl FlowySupabaseFolderTest {
|
||||
pub async fn new() -> Option<Self> {
|
||||
let inner = FlowySupabaseTest::new().await?;
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
let _ = inner.supabase_sign_up_with_uuid(&uuid, None).await;
|
||||
Some(Self { inner })
|
||||
}
|
||||
|
||||
pub async fn get_collab_json(&self) -> JsonValue {
|
||||
let folder = self.folder_manager.get_mutex_folder().lock();
|
||||
folder.as_ref().unwrap().to_json_value()
|
||||
}
|
||||
|
||||
pub async fn get_local_folder_data(&self) -> FolderData {
|
||||
let folder = self.folder_manager.get_mutex_folder().lock();
|
||||
folder.as_ref().unwrap().get_folder_data().unwrap()
|
||||
}
|
||||
|
||||
pub async fn get_folder_snapshots(&self, workspace_id: &str) -> Vec<FolderSnapshotPB> {
|
||||
EventBuilder::new(self.inner.deref().clone())
|
||||
.event(GetFolderSnapshots)
|
||||
.payload(WorkspaceIdPB {
|
||||
value: workspace_id.to_string(),
|
||||
})
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<RepeatedFolderSnapshotPB>()
|
||||
.items
|
||||
}
|
||||
|
||||
pub async fn get_collab_update(&self, workspace_id: &str) -> Vec<u8> {
|
||||
let cloud_service = self.folder_manager.get_cloud_service().clone();
|
||||
cloud_service
|
||||
.get_folder_doc_state(
|
||||
workspace_id,
|
||||
self.user_manager.user_id().unwrap(),
|
||||
CollabType::Folder,
|
||||
workspace_id,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn assert_folder_collab_content(workspace_id: &str, collab_update: &[u8], expected: JsonValue) {
|
||||
if collab_update.is_empty() {
|
||||
panic!("collab update is empty");
|
||||
}
|
||||
|
||||
let collab = MutexCollab::new(Collab::new_with_origin(
|
||||
CollabOrigin::Server,
|
||||
workspace_id,
|
||||
vec![],
|
||||
false,
|
||||
));
|
||||
collab.lock().with_origin_transact_mut(|txn| {
|
||||
let update = Update::decode_v1(collab_update).unwrap();
|
||||
txn.apply_update(update);
|
||||
});
|
||||
|
||||
let json = collab.to_json_value();
|
||||
assert_json_eq!(json["folder"], expected);
|
||||
}
|
||||
|
||||
impl Deref for FlowySupabaseFolderTest {
|
||||
type Target = FlowySupabaseTest;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
mod helper;
|
||||
mod test;
|
@ -0,0 +1,122 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use assert_json_diff::assert_json_eq;
|
||||
use serde_json::json;
|
||||
|
||||
use flowy_folder::entities::{FolderSnapshotStatePB, FolderSyncStatePB};
|
||||
use flowy_folder::notification::FolderNotification::DidUpdateFolderSnapshotState;
|
||||
|
||||
use crate::folder::supabase_test::helper::{assert_folder_collab_content, FlowySupabaseFolderTest};
|
||||
use crate::util::{get_folder_data_from_server, receive_with_timeout};
|
||||
|
||||
#[tokio::test]
|
||||
async fn supabase_encrypt_folder_test() {
|
||||
if let Some(test) = FlowySupabaseFolderTest::new().await {
|
||||
let uid = test.user_manager.user_id().unwrap();
|
||||
let secret = test.enable_encryption().await;
|
||||
|
||||
let local_folder_data = test.get_local_folder_data().await;
|
||||
let workspace_id = test.get_current_workspace().await.id;
|
||||
let remote_folder_data = get_folder_data_from_server(&uid, &workspace_id, Some(secret))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_json_eq!(json!(local_folder_data), json!(remote_folder_data));
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn supabase_decrypt_folder_data_test() {
|
||||
if let Some(test) = FlowySupabaseFolderTest::new().await {
|
||||
let uid = test.user_manager.user_id().unwrap();
|
||||
let secret = Some(test.enable_encryption().await);
|
||||
let workspace_id = test.get_current_workspace().await.id;
|
||||
test
|
||||
.create_view(&workspace_id, "encrypt view".to_string())
|
||||
.await;
|
||||
|
||||
let rx = test
|
||||
.notification_sender
|
||||
.subscribe_with_condition::<FolderSyncStatePB, _>(&workspace_id, |pb| pb.is_finish);
|
||||
|
||||
receive_with_timeout(rx, Duration::from_secs(10))
|
||||
.await
|
||||
.unwrap();
|
||||
let folder_data = get_folder_data_from_server(&uid, &workspace_id, secret)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(folder_data.views.len(), 2);
|
||||
assert_eq!(folder_data.views[1].name, "encrypt view");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[should_panic]
|
||||
async fn supabase_decrypt_with_invalid_secret_folder_data_test() {
|
||||
if let Some(test) = FlowySupabaseFolderTest::new().await {
|
||||
let uid = test.user_manager.user_id().unwrap();
|
||||
let _ = Some(test.enable_encryption().await);
|
||||
let workspace_id = test.get_current_workspace().await.id;
|
||||
test
|
||||
.create_view(&workspace_id, "encrypt view".to_string())
|
||||
.await;
|
||||
let rx = test
|
||||
.notification_sender
|
||||
.subscribe_with_condition::<FolderSyncStatePB, _>(&workspace_id, |pb| pb.is_finish);
|
||||
receive_with_timeout(rx, Duration::from_secs(10))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let _ = get_folder_data_from_server(&uid, &workspace_id, Some("invalid secret".to_string()))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn supabase_folder_snapshot_test() {
|
||||
if let Some(test) = FlowySupabaseFolderTest::new().await {
|
||||
let workspace_id = test.get_current_workspace().await.id;
|
||||
let rx = test
|
||||
.notification_sender
|
||||
.subscribe::<FolderSnapshotStatePB>(&workspace_id, DidUpdateFolderSnapshotState);
|
||||
receive_with_timeout(rx, Duration::from_secs(10))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let expected = test.get_collab_json().await;
|
||||
let snapshots = test.get_folder_snapshots(&workspace_id).await;
|
||||
assert_eq!(snapshots.len(), 1);
|
||||
assert_folder_collab_content(&workspace_id, &snapshots[0].data, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn supabase_initial_folder_snapshot_test2() {
|
||||
if let Some(test) = FlowySupabaseFolderTest::new().await {
|
||||
let workspace_id = test.get_current_workspace().await.id;
|
||||
|
||||
test
|
||||
.create_view(&workspace_id, "supabase test view1".to_string())
|
||||
.await;
|
||||
test
|
||||
.create_view(&workspace_id, "supabase test view2".to_string())
|
||||
.await;
|
||||
test
|
||||
.create_view(&workspace_id, "supabase test view3".to_string())
|
||||
.await;
|
||||
|
||||
let rx = test
|
||||
.notification_sender
|
||||
.subscribe_with_condition::<FolderSyncStatePB, _>(&workspace_id, |pb| pb.is_finish);
|
||||
|
||||
receive_with_timeout(rx, Duration::from_secs(10))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let expected = test.get_collab_json().await;
|
||||
let update = test.get_collab_update(&workspace_id).await;
|
||||
assert_folder_collab_content(&workspace_id, &update, expected);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user