refactor: rename flowy-test to event-integration (#3667)

This commit is contained in:
Nathan.fooo
2023-10-11 20:02:09 +08:00
committed by GitHub
parent cf521fe20b
commit e59870e421
65 changed files with 91 additions and 94 deletions

View File

@ -0,0 +1,376 @@
use collab_folder::core::ViewLayout;
use flowy_folder2::entities::icon::{ViewIconPB, ViewIconTypePB};
use crate::folder::local_test::script::FolderScript::*;
use crate::folder::local_test::script::FolderTest;
#[tokio::test]
async fn read_all_workspace_test() {
let mut test = FolderTest::new().await;
test.run_scripts(vec![ReadAllWorkspaces]).await;
assert!(!test.all_workspace.is_empty());
}
#[tokio::test]
async fn create_workspace_test() {
let mut test = FolderTest::new().await;
let name = "My new workspace".to_owned();
let desc = "Daily routines".to_owned();
test
.run_scripts(vec![CreateWorkspace {
name: name.clone(),
desc: desc.clone(),
}])
.await;
let workspace = test.workspace.clone();
assert_eq!(workspace.name, name);
test
.run_scripts(vec![
ReadWorkspace(Some(workspace.id.clone())),
AssertWorkspace(workspace),
])
.await;
}
#[tokio::test]
async fn get_workspace_test() {
let mut test = FolderTest::new().await;
let workspace = test.workspace.clone();
test
.run_scripts(vec![
ReadWorkspace(Some(workspace.id.clone())),
AssertWorkspace(workspace),
])
.await;
}
#[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 app_create_with_view() {
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]);
}

View File

@ -0,0 +1,4 @@
mod folder_test;
mod script;
mod subscription_test;
mod test;

View File

@ -0,0 +1,416 @@
use collab_folder::core::ViewLayout;
use event_integration::event_builder::EventBuilder;
use event_integration::FlowyCoreTest;
use flowy_folder2::entities::icon::{UpdateViewIconPayloadPB, ViewIconPB};
use flowy_folder2::entities::*;
use flowy_folder2::event_map::FolderEvent::*;
pub enum FolderScript {
// Workspace
ReadAllWorkspaces,
CreateWorkspace {
name: String,
desc: String,
},
AssertWorkspace(WorkspacePB),
ReadWorkspace(Option<String>),
// App
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: FlowyCoreTest,
pub all_workspace: Vec<WorkspacePB>,
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 = FlowyCoreTest::new();
let _ = sdk.init_user().await;
let workspace = create_workspace(&sdk, "FolderWorkspace", "Folder test workspace").await;
let parent_view = create_app(&sdk, &workspace.id, "Folder App", "Folder test app").await;
let view = create_view(
&sdk,
&parent_view.id,
"Folder View",
"Folder test view",
ViewLayout::Document,
)
.await;
Self {
sdk,
all_workspace: vec![],
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::ReadAllWorkspaces => {
let all_workspace = read_workspace(sdk, None).await;
self.all_workspace = all_workspace;
},
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.pop().unwrap();
self.workspace = workspace;
},
FolderScript::CreateParentView { name, desc } => {
let app = create_app(sdk, &self.workspace.id, &name, &desc).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: &FlowyCoreTest, name: &str, desc: &str) -> WorkspacePB {
let request = CreateWorkspacePayloadPB {
name: name.to_owned(),
desc: desc.to_owned(),
};
EventBuilder::new(sdk.clone())
.event(CreateWorkspace)
.payload(request)
.async_send()
.await
.parse::<WorkspacePB>()
}
pub async fn read_workspace(sdk: &FlowyCoreTest, workspace_id: Option<String>) -> Vec<WorkspacePB> {
let request = WorkspaceIdPB {
value: workspace_id,
};
let repeated_workspace = EventBuilder::new(sdk.clone())
.event(ReadAllWorkspaces)
.payload(request.clone())
.async_send()
.await
.parse::<RepeatedWorkspacePB>();
let workspaces;
if let Some(workspace_id) = &request.value {
workspaces = repeated_workspace
.items
.into_iter()
.filter(|workspace| &workspace.id == workspace_id)
.collect::<Vec<WorkspacePB>>();
debug_assert_eq!(workspaces.len(), 1);
} else {
workspaces = repeated_workspace.items;
}
workspaces
}
pub async fn create_app(sdk: &FlowyCoreTest, workspace_id: &str, name: &str, desc: &str) -> ViewPB {
let create_view_request = CreateViewPayloadPB {
parent_view_id: workspace_id.to_owned(),
name: name.to_string(),
desc: desc.to_string(),
thumbnail: None,
layout: ViewLayout::Document.into(),
initial_data: vec![],
meta: Default::default(),
set_as_current: true,
index: None,
};
EventBuilder::new(sdk.clone())
.event(CreateView)
.payload(create_view_request)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn create_view(
sdk: &FlowyCoreTest,
app_id: &str,
name: &str,
desc: &str,
layout: ViewLayout,
) -> ViewPB {
let request = CreateViewPayloadPB {
parent_view_id: app_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,
};
EventBuilder::new(sdk.clone())
.event(CreateView)
.payload(request)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn read_view(sdk: &FlowyCoreTest, view_id: &str) -> ViewPB {
let view_id = ViewIdPB::from(view_id);
EventBuilder::new(sdk.clone())
.event(ReadView)
.payload(view_id)
.async_send()
.await
.parse::<ViewPB>()
}
pub async fn move_view(
sdk: &FlowyCoreTest,
view_id: String,
parent_id: String,
prev_view_id: Option<String>,
) {
let payload = MoveNestedViewPayloadPB {
view_id,
new_parent_id: parent_id,
prev_view_id,
};
let error = EventBuilder::new(sdk.clone())
.event(MoveNestedView)
.payload(payload)
.async_send()
.await
.error();
assert!(error.is_none());
}
pub async fn update_view(
sdk: &FlowyCoreTest,
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: &FlowyCoreTest, 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: &FlowyCoreTest, 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: &FlowyCoreTest) -> RepeatedTrashPB {
EventBuilder::new(sdk.clone())
.event(ReadTrash)
.async_send()
.await
.parse::<RepeatedTrashPB>()
}
pub async fn restore_app_from_trash(sdk: &FlowyCoreTest, app_id: &str) {
let id = TrashIdPB {
id: app_id.to_owned(),
};
EventBuilder::new(sdk.clone())
.event(PutbackTrash)
.payload(id)
.async_send()
.await;
}
pub async fn restore_view_from_trash(sdk: &FlowyCoreTest, view_id: &str) {
let id = TrashIdPB {
id: view_id.to_owned(),
};
EventBuilder::new(sdk.clone())
.event(PutbackTrash)
.payload(id)
.async_send()
.await;
}
pub async fn delete_all_trash(sdk: &FlowyCoreTest) {
EventBuilder::new(sdk.clone())
.event(DeleteAllTrash)
.async_send()
.await;
}
pub async fn toggle_favorites(sdk: &FlowyCoreTest, 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: &FlowyCoreTest) -> RepeatedViewPB {
EventBuilder::new(sdk.clone())
.event(ReadFavorites)
.async_send()
.await
.parse::<RepeatedViewPB>()
}

View File

@ -0,0 +1,127 @@
use std::time::Duration;
use event_integration::FlowyCoreTest;
use flowy_folder2::entities::{ChildViewUpdatePB, RepeatedViewPB, UpdateViewPayloadPB};
use flowy_folder2::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 = FlowyCoreTest::new_with_guest_user().await;
let workspace = test.get_current_workspace().await.workspace;
let mut rx = test
.notification_sender
.subscribe::<RepeatedViewPB>(&workspace.id, FolderNotification::DidUpdateWorkspaceViews);
let cloned_test = test.clone();
let cloned_workspace_id = workspace.id.clone();
tokio::spawn(async move {
cloned_test
.create_view(&cloned_workspace_id, "workspace child view".to_string())
.await;
});
let views = receive_with_timeout(&mut 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 = FlowyCoreTest::new_with_guest_user().await;
let mut workspace = test.get_current_workspace().await.workspace;
let workspace_child_view = workspace.views.pop().unwrap();
let mut 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();
tokio::spawn(async move {
cloned_test
.create_view(
&child_view_id,
"workspace child view's child view".to_string(),
)
.await;
});
let update = receive_with_timeout(&mut 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 = FlowyCoreTest::new_with_guest_user().await;
let workspace = test.get_current_workspace().await.workspace;
let mut 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();
tokio::spawn(async move {
cloned_test.delete_view(&cloned_delete_view_id).await;
});
let update = receive_with_timeout(&mut 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 = FlowyCoreTest::new_with_guest_user().await;
let mut workspace = test.get_current_workspace().await.workspace;
let mut 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();
tokio::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(&mut 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);
}

View File

@ -0,0 +1,557 @@
use event_integration::event_builder::EventBuilder;
use event_integration::FlowyCoreTest;
use flowy_folder2::entities::icon::{UpdateViewIconPayloadPB, ViewIconPB, ViewIconTypePB};
use flowy_folder2::entities::*;
use flowy_user::errors::ErrorCode;
#[tokio::test]
async fn create_workspace_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let request = CreateWorkspacePayloadPB {
name: "my second workspace".to_owned(),
desc: "".to_owned(),
};
let resp = EventBuilder::new(test)
.event(flowy_folder2::event_map::FolderEvent::CreateWorkspace)
.payload(request)
.async_send()
.await
.parse::<flowy_folder2::entities::WorkspacePB>();
assert_eq!(resp.name, "my second workspace");
}
#[tokio::test]
async fn open_workspace_event_test() {
let test = FlowyCoreTest::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_folder2::event_map::FolderEvent::CreateWorkspace)
.payload(payload)
.async_send()
.await
.parse::<flowy_folder2::entities::WorkspacePB>();
// open the workspace
let payload = WorkspaceIdPB {
value: Some(resp_1.id.clone()),
};
let resp_2 = EventBuilder::new(test)
.event(flowy_folder2::event_map::FolderEvent::OpenWorkspace)
.payload(payload)
.async_send()
.await
.parse::<flowy_folder2::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 = FlowyCoreTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let view = test
.create_view(&current_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 = FlowyCoreTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let view = test
.create_view(&current_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 = FlowyCoreTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let view = test
.create_view(&current_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 = FlowyCoreTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let view = test
.create_view(&current_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_folder2::event_map::FolderEvent::ReadView)
.payload(payload)
.async_send()
.await
.error()
.unwrap();
assert_eq!(error.code, ErrorCode::RecordNotFound);
}
#[tokio::test]
async fn put_back_trash_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let view = test
.create_view(&current_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_folder2::event_map::FolderEvent::ReadView)
.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_folder2::event_map::FolderEvent::PutbackTrash)
.payload(payload)
.async_send()
.await;
let payload = ViewIdPB {
value: view.id.clone(),
};
let error = EventBuilder::new(test.clone())
.event(flowy_folder2::event_map::FolderEvent::ReadView)
.payload(payload)
.async_send()
.await
.error();
assert!(error.is_none());
}
#[tokio::test]
async fn delete_view_permanently_event_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let view = test
.create_view(&current_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_folder2::event_map::FolderEvent::DeleteView)
.payload(payload)
.async_send()
.await;
let trash = EventBuilder::new(test.clone())
.event(flowy_folder2::event_map::FolderEvent::ReadTrash)
.async_send()
.await
.parse::<flowy_folder2::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_folder2::event_map::FolderEvent::DeleteTrash)
.payload(payload)
.async_send()
.await;
// After delete the last view, the trash should be empty
let trash = EventBuilder::new(test.clone())
.event(flowy_folder2::event_map::FolderEvent::ReadTrash)
.async_send()
.await
.parse::<flowy_folder2::entities::RepeatedTrashPB>()
.items;
assert!(trash.is_empty());
}
#[tokio::test]
async fn delete_all_trash_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
for i in 0..3 {
let view = test
.create_view(&current_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_folder2::event_map::FolderEvent::DeleteView)
.payload(payload)
.async_send()
.await;
}
let trash = EventBuilder::new(test.clone())
.event(flowy_folder2::event_map::FolderEvent::ReadTrash)
.async_send()
.await
.parse::<flowy_folder2::entities::RepeatedTrashPB>()
.items;
assert_eq!(trash.len(), 3);
// Delete all the trash
EventBuilder::new(test.clone())
.event(flowy_folder2::event_map::FolderEvent::DeleteAllTrash)
.async_send()
.await;
// After delete the last view, the trash should be empty
let trash = EventBuilder::new(test.clone())
.event(flowy_folder2::event_map::FolderEvent::ReadTrash)
.async_send()
.await
.parse::<flowy_folder2::entities::RepeatedTrashPB>()
.items;
assert!(trash.is_empty());
}
#[tokio::test]
async fn multiple_hierarchy_view_test() {
let test = FlowyCoreTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
for i in 1..4 {
let parent = test
.create_view(&current_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_folder2::event_map::FolderEvent::ReadView)
.payload(payload)
.async_send()
.await
.parse::<flowy_folder2::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 = FlowyCoreTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
for i in 1..4 {
let parent = test
.create_view(&current_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_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 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_guest_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_guest_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() {
let sdk = FlowyCoreTest::new();
let request = CreateWorkspacePayloadPB {
name,
desc: "".to_owned(),
};
assert_eq!(
EventBuilder::new(sdk)
.event(flowy_folder2::event_map::FolderEvent::CreateWorkspace)
.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 = FlowyCoreTest::new_with_guest_user().await;
let current_workspace = test.get_current_workspace().await.workspace;
let parent_1 = test
.create_view(&current_workspace.id, "My view 1".to_string())
.await;
let parent_2 = test
.create_view(&current_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: FlowyCoreTest,
view_id: String,
new_parent_id: String,
prev_view_id: Option<String>,
) {
let payload = MoveNestedViewPayloadPB {
view_id,
new_parent_id,
prev_view_id,
};
EventBuilder::new(sdk)
.event(flowy_folder2::event_map::FolderEvent::MoveNestedView)
.payload(payload)
.async_send()
.await;
}

View File

@ -0,0 +1,4 @@
mod local_test;
#[cfg(feature = "supabase_cloud_test")]
mod supabase_test;

View File

@ -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::{merge_updates_v1, JsonValue, Update};
use collab_folder::core::FolderData;
use event_integration::event_builder::EventBuilder;
use flowy_folder2::entities::{FolderSnapshotPB, RepeatedFolderSnapshotPB, WorkspaceIdPB};
use flowy_folder2::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()?;
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: Some(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();
let remote_updates = cloud_service
.get_folder_updates(workspace_id, self.user_manager.user_id().unwrap())
.await
.unwrap();
if remote_updates.is_empty() {
return vec![];
}
let updates = remote_updates
.iter()
.map(|update| update.as_ref())
.collect::<Vec<&[u8]>>();
merge_updates_v1(&updates).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(CollabOrigin::Server, workspace_id, vec![]);
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
}
}

View File

@ -0,0 +1,2 @@
mod helper;
mod test;

View File

@ -0,0 +1,119 @@
use std::time::Duration;
use assert_json_diff::assert_json_eq;
use serde_json::json;
use flowy_folder2::entities::{FolderSnapshotStatePB, FolderSyncStatePB};
use flowy_folder2::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 secret = test.enable_encryption().await;
let local_folder_data = test.get_local_folder_data().await;
let workspace_id = test.get_current_workspace().await.workspace.id;
let remote_folder_data = get_folder_data_from_server(&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 secret = Some(test.enable_encryption().await);
let workspace_id = test.get_current_workspace().await.workspace.id;
test
.create_view(&workspace_id, "encrypt view".to_string())
.await;
let mut rx = test
.notification_sender
.subscribe_with_condition::<FolderSyncStatePB, _>(&workspace_id, |pb| pb.is_finish);
receive_with_timeout(&mut rx, Duration::from_secs(10))
.await
.unwrap();
let folder_data = get_folder_data_from_server(&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 _ = Some(test.enable_encryption().await);
let workspace_id = test.get_current_workspace().await.workspace.id;
test
.create_view(&workspace_id, "encrypt view".to_string())
.await;
let mut rx = test
.notification_sender
.subscribe_with_condition::<FolderSyncStatePB, _>(&workspace_id, |pb| pb.is_finish);
receive_with_timeout(&mut rx, Duration::from_secs(10))
.await
.unwrap();
let _ = get_folder_data_from_server(&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.workspace.id;
let mut rx = test
.notification_sender
.subscribe::<FolderSnapshotStatePB>(&workspace_id, DidUpdateFolderSnapshotState);
receive_with_timeout(&mut 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.workspace.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 mut rx = test
.notification_sender
.subscribe_with_condition::<FolderSyncStatePB, _>(&workspace_id, |pb| pb.is_finish);
receive_with_timeout(&mut 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);
}
}