add frontend folder

This commit is contained in:
appflowy
2021-11-20 09:32:46 +08:00
parent f93f012bc8
commit 8f1d62f115
1697 changed files with 754 additions and 104 deletions

View File

@ -0,0 +1,34 @@
[package]
name = "flowy-test"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
flowy-sdk = { path = "../flowy-sdk"}
lib-dispatch = { path = "../lib-dispatch" }
flowy-user = { path = "../flowy-user"}
flowy-workspace = { path = "../flowy-workspace", default-features = false}
lib-infra = { path = "../lib-infra" }
flowy-document = { path = "../flowy-document"}
flowy-document-infra = { path = "../flowy-document-infra"}
backend-service = { path = "../backend-service" }
serde = { version = "1.0", features = ["derive"] }
bincode = { version = "1.3"}
protobuf = {version = "2.24.1"}
claim = "0.5.0"
tokio = { version = "1", features = ["full"]}
futures-util = "0.3.15"
thread-id = "3.3.0"
log = "0.4"
bytes = "1.0"
[dev-dependencies]
quickcheck = "0.9.2"
quickcheck_macros = "0.9.1"
fake = "~2.3.0"
claim = "0.4.0"
futures = "0.3.15"
serial_test = "0.5.1"

View File

@ -0,0 +1,144 @@
use flowy_user::entities::UserProfile;
use lib_dispatch::prelude::{EventDispatch, EventResponse, FromBytes, ModuleRequest, StatusCode, ToBytes};
use std::{
fmt::{Debug, Display},
hash::Hash,
};
use crate::FlowyTestSDK;
use lib_dispatch::prelude::*;
use flowy_sdk::*;
use flowy_user::errors::UserError;
use flowy_workspace::errors::WorkspaceError;
use std::{convert::TryFrom, marker::PhantomData, sync::Arc};
pub type FlowyWorkspaceTest = Builder<WorkspaceError>;
impl FlowyWorkspaceTest {
pub fn new(sdk: FlowyTestSDK) -> Self { Builder::test(TestContext::new(sdk)) }
}
pub type UserTest = Builder<UserError>;
impl UserTest {
pub fn new(sdk: FlowyTestSDK) -> Self { Builder::test(TestContext::new(sdk)) }
pub fn user_profile(&self) -> &Option<UserProfile> { &self.user_profile }
}
#[derive(Clone)]
pub struct Builder<E> {
context: TestContext,
user_profile: Option<UserProfile>,
err_phantom: PhantomData<E>,
}
impl<E> Builder<E>
where
E: FromBytes + Debug,
{
pub(crate) fn test(context: TestContext) -> Self {
Self {
context,
user_profile: None,
err_phantom: PhantomData,
}
}
pub fn request<P>(mut self, payload: P) -> Self
where
P: ToBytes,
{
match payload.into_bytes() {
Ok(bytes) => {
let module_request = self.get_request();
self.context.request = Some(module_request.payload(bytes))
},
Err(e) => {
log::error!("Set payload failed: {:?}", e);
},
}
self
}
pub fn event<Event>(mut self, event: Event) -> Self
where
Event: Eq + Hash + Debug + Clone + Display,
{
self.context.request = Some(ModuleRequest::new(event));
self
}
pub fn sync_send(mut self) -> Self {
let request = self.get_request();
let resp = EventDispatch::sync_send(self.dispatch(), request);
self.context.response = Some(resp);
self
}
pub async fn async_send(mut self) -> Self {
let request = self.get_request();
let resp = EventDispatch::async_send(self.dispatch(), request).await;
self.context.response = Some(resp);
self
}
pub fn parse<R>(self) -> R
where
R: FromBytes,
{
let response = self.get_response();
match response.parse::<R, E>() {
Ok(Ok(data)) => data,
Ok(Err(e)) => {
panic!("parse failed: {:?}", e)
},
Err(e) => panic!("Internal error: {:?}", e),
}
}
pub fn error(self) -> E {
let response = self.get_response();
assert_eq!(response.status_code, StatusCode::Err);
<Data<E>>::try_from(response.payload).unwrap().into_inner()
}
pub fn assert_error(self) -> Self {
// self.context.assert_error();
self
}
pub fn assert_success(self) -> Self {
// self.context.assert_success();
self
}
pub fn sdk(&self) -> FlowySDK { self.context.sdk.clone() }
fn dispatch(&self) -> Arc<EventDispatch> { self.context.sdk.dispatch() }
fn get_response(&self) -> EventResponse {
self.context
.response
.as_ref()
.expect("must call sync_send first")
.clone()
}
fn get_request(&mut self) -> ModuleRequest { self.context.request.take().expect("must call event first") }
}
#[derive(Clone)]
pub struct TestContext {
sdk: FlowyTestSDK,
request: Option<ModuleRequest>,
response: Option<EventResponse>,
}
impl TestContext {
pub fn new(sdk: FlowyTestSDK) -> Self {
Self {
sdk,
request: None,
response: None,
}
}
}

View File

@ -0,0 +1,144 @@
use bytes::Bytes;
use lib_dispatch::prelude::{EventDispatch, ModuleRequest, ToBytes};
use lib_infra::{kv::KV, uuid};
use flowy_user::{
entities::{SignInRequest, SignUpRequest, UserProfile},
errors::UserError,
event::UserEvent::{SignIn, SignOut, SignUp},
};
use flowy_workspace::{
entities::workspace::{CreateWorkspaceRequest, QueryWorkspaceRequest, Workspace},
errors::WorkspaceError,
event::WorkspaceEvent::{CreateWorkspace, OpenWorkspace},
};
use std::{fs, path::PathBuf, sync::Arc};
pub fn root_dir() -> String {
// https://doc.rust-lang.org/cargo/reference/environment-variables.html
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or("./".to_owned());
let mut path_buf = fs::canonicalize(&PathBuf::from(&manifest_dir)).unwrap();
path_buf.pop(); // rust-lib
path_buf.push("flowy-test");
path_buf.push("temp");
path_buf.push("flowy");
let root_dir = path_buf.to_str().unwrap().to_string();
if !std::path::Path::new(&root_dir).exists() {
std::fs::create_dir_all(&root_dir).unwrap();
}
root_dir
}
pub fn random_email() -> String { format!("{}@appflowy.io", uuid()) }
pub fn login_email() -> String { "annie2@appflowy.io".to_string() }
pub fn login_password() -> String { "HelloWorld!123".to_string() }
const DEFAULT_WORKSPACE_NAME: &'static str = "My workspace";
const DEFAULT_WORKSPACE_DESC: &'static str = "This is your first workspace";
const DEFAULT_WORKSPACE: &'static str = "Default_Workspace";
#[allow(dead_code)]
pub(crate) fn create_default_workspace_if_need(dispatch: Arc<EventDispatch>, user_id: &str) -> Result<(), UserError> {
let key = format!("{}{}", user_id, DEFAULT_WORKSPACE);
if KV::get_bool(&key).unwrap_or(false) {
return Err(UserError::internal());
}
KV::set_bool(&key, true);
let payload: Bytes = CreateWorkspaceRequest {
name: DEFAULT_WORKSPACE_NAME.to_string(),
desc: DEFAULT_WORKSPACE_DESC.to_string(),
}
.into_bytes()
.unwrap();
let request = ModuleRequest::new(CreateWorkspace).payload(payload);
let result = EventDispatch::sync_send(dispatch.clone(), request)
.parse::<Workspace, WorkspaceError>()
.map_err(|e| UserError::internal().context(e))?;
let workspace = result.map_err(|e| UserError::internal().context(e))?;
let query: Bytes = QueryWorkspaceRequest {
workspace_id: Some(workspace.id.clone()),
}
.into_bytes()
.unwrap();
let request = ModuleRequest::new(OpenWorkspace).payload(query);
let _result = EventDispatch::sync_send(dispatch.clone(), request)
.parse::<Workspace, WorkspaceError>()
.unwrap()
.unwrap();
Ok(())
}
pub struct SignUpContext {
pub user_profile: UserProfile,
pub password: String,
}
pub fn sign_up(dispatch: Arc<EventDispatch>) -> SignUpContext {
let password = login_password();
let payload = SignUpRequest {
email: random_email(),
name: "app flowy".to_string(),
password: password.clone(),
}
.into_bytes()
.unwrap();
let request = ModuleRequest::new(SignUp).payload(payload);
let user_profile = EventDispatch::sync_send(dispatch.clone(), request)
.parse::<UserProfile, UserError>()
.unwrap()
.unwrap();
SignUpContext { user_profile, password }
}
pub async fn async_sign_up(dispatch: Arc<EventDispatch>) -> SignUpContext {
let password = login_password();
let payload = SignUpRequest {
email: random_email(),
name: "app flowy".to_string(),
password: password.clone(),
}
.into_bytes()
.unwrap();
let request = ModuleRequest::new(SignUp).payload(payload);
let user_profile = EventDispatch::async_send(dispatch.clone(), request)
.await
.parse::<UserProfile, UserError>()
.unwrap()
.unwrap();
// let _ = create_default_workspace_if_need(dispatch.clone(), &user_profile.id);
SignUpContext { user_profile, password }
}
#[allow(dead_code)]
fn sign_in(dispatch: Arc<EventDispatch>) -> UserProfile {
let payload = SignInRequest {
email: login_email(),
password: login_password(),
name: "rust".to_owned(),
}
.into_bytes()
.unwrap();
let request = ModuleRequest::new(SignIn).payload(payload);
let user_profile = EventDispatch::sync_send(dispatch, request)
.parse::<UserProfile, UserError>()
.unwrap()
.unwrap();
user_profile
}
#[allow(dead_code)]
fn logout(dispatch: Arc<EventDispatch>) { let _ = EventDispatch::sync_send(dispatch, ModuleRequest::new(SignOut)); }

View File

@ -0,0 +1,48 @@
pub mod builder;
mod helper;
pub mod workspace;
use crate::helper::*;
use backend_service::config::ServerConfig;
use flowy_sdk::{FlowySDK, FlowySDKConfig};
use flowy_user::entities::UserProfile;
use lib_infra::uuid;
pub mod prelude {
pub use crate::{builder::*, helper::*, *};
pub use lib_dispatch::prelude::*;
}
pub type FlowyTestSDK = FlowySDK;
#[derive(Clone)]
pub struct FlowyTest {
pub sdk: FlowyTestSDK,
}
impl FlowyTest {
pub fn setup() -> Self {
let server_config = ServerConfig::default();
let test = Self::setup_with(server_config);
std::mem::forget(test.sdk.dispatch());
test
}
pub async fn sign_up(&self) -> SignUpContext {
let context = async_sign_up(self.sdk.dispatch()).await;
context
}
pub async fn init_user(&self) -> UserProfile {
let context = async_sign_up(self.sdk.dispatch()).await;
context.user_profile
}
pub fn setup_with(server_config: ServerConfig) -> Self {
let config = FlowySDKConfig::new(&root_dir(), server_config, &uuid().to_string()).log_filter("debug");
let sdk = FlowySDK::new(config);
Self { sdk }
}
pub fn sdk(&self) -> FlowyTestSDK { self.sdk.clone() }
}

View File

@ -0,0 +1,276 @@
use crate::prelude::*;
use flowy_document_infra::entities::doc::Doc;
use flowy_workspace::{
entities::{
app::*,
trash::{RepeatedTrash, TrashIdentifier},
view::*,
workspace::*,
},
errors::ErrorCode,
event::WorkspaceEvent::*,
};
pub struct WorkspaceTest {
pub sdk: FlowyTestSDK,
pub workspace: Workspace,
}
impl WorkspaceTest {
pub async fn new() -> Self {
let test = FlowyTest::setup();
let _ = test.init_user().await;
let workspace = create_workspace(&test.sdk, "Workspace", "").await;
open_workspace(&test.sdk, &workspace.id).await;
Self {
sdk: test.sdk,
workspace,
}
}
}
pub struct AppTest {
pub sdk: FlowyTestSDK,
pub workspace: Workspace,
pub app: App,
}
impl AppTest {
pub async fn new() -> Self {
let test = FlowyTest::setup();
let _ = test.init_user().await;
let workspace = create_workspace(&test.sdk, "Workspace", "").await;
open_workspace(&test.sdk, &workspace.id).await;
let app = create_app(&test.sdk, "App", "AppFlowy Github Project", &workspace.id).await;
Self {
sdk: test.sdk,
workspace,
app,
}
}
pub async fn move_app_to_trash(&self) {
let request = UpdateAppRequest {
app_id: self.app.id.clone(),
name: None,
desc: None,
color_style: None,
is_trash: Some(true),
};
update_app(&self.sdk, request).await;
}
}
pub struct ViewTest {
pub sdk: FlowyTestSDK,
pub workspace: Workspace,
pub app: App,
pub view: View,
}
impl ViewTest {
pub async fn new(test: &FlowyTest) -> Self {
let workspace = create_workspace(&test.sdk, "Workspace", "").await;
open_workspace(&test.sdk, &workspace.id).await;
let app = create_app(&test.sdk, "App", "AppFlowy Github Project", &workspace.id).await;
let view = create_view(&test.sdk, &app.id).await;
Self {
sdk: test.sdk.clone(),
workspace,
app,
view,
}
}
pub async fn delete_views(&self, view_ids: Vec<String>) {
let request = QueryViewRequest { view_ids };
delete_view(&self.sdk, request).await;
}
pub async fn delete_views_permanent(&self, view_ids: Vec<String>) {
let request = QueryViewRequest { view_ids };
delete_view(&self.sdk, request).await;
FlowyWorkspaceTest::new(self.sdk.clone())
.event(DeleteAll)
.async_send()
.await;
}
}
pub fn invalid_workspace_name_test_case() -> Vec<(String, ErrorCode)> {
vec![
("".to_owned(), ErrorCode::WorkspaceNameInvalid),
("1234".repeat(100), ErrorCode::WorkspaceNameTooLong),
]
}
pub async fn create_workspace(sdk: &FlowyTestSDK, name: &str, desc: &str) -> Workspace {
let request = CreateWorkspaceRequest {
name: name.to_owned(),
desc: desc.to_owned(),
};
let workspace = FlowyWorkspaceTest::new(sdk.clone())
.event(CreateWorkspace)
.request(request)
.async_send()
.await
.parse::<Workspace>();
workspace
}
async fn open_workspace(sdk: &FlowyTestSDK, workspace_id: &str) {
let request = QueryWorkspaceRequest {
workspace_id: Some(workspace_id.to_owned()),
};
let _ = FlowyWorkspaceTest::new(sdk.clone())
.event(OpenWorkspace)
.request(request)
.async_send()
.await;
}
pub async fn read_workspace(sdk: &FlowyTestSDK, request: QueryWorkspaceRequest) -> Vec<Workspace> {
let mut repeated_workspace = FlowyWorkspaceTest::new(sdk.clone())
.event(ReadWorkspaces)
.request(request.clone())
.async_send()
.await
.parse::<RepeatedWorkspace>();
let workspaces;
if let Some(workspace_id) = &request.workspace_id {
workspaces = repeated_workspace
.into_inner()
.into_iter()
.filter(|workspace| &workspace.id == workspace_id)
.collect::<Vec<Workspace>>();
debug_assert_eq!(workspaces.len(), 1);
} else {
workspaces = repeated_workspace.items;
}
workspaces
}
pub async fn create_app(sdk: &FlowyTestSDK, name: &str, desc: &str, workspace_id: &str) -> App {
let create_app_request = CreateAppRequest {
workspace_id: workspace_id.to_owned(),
name: name.to_string(),
desc: desc.to_string(),
color_style: Default::default(),
};
let app = FlowyWorkspaceTest::new(sdk.clone())
.event(CreateApp)
.request(create_app_request)
.async_send()
.await
.parse::<App>();
app
}
pub async fn delete_app(sdk: &FlowyTestSDK, app_id: &str) {
let delete_app_request = AppIdentifier {
app_id: app_id.to_string(),
};
FlowyWorkspaceTest::new(sdk.clone())
.event(DeleteApp)
.request(delete_app_request)
.async_send()
.await;
}
pub async fn update_app(sdk: &FlowyTestSDK, request: UpdateAppRequest) {
FlowyWorkspaceTest::new(sdk.clone())
.event(UpdateApp)
.request(request)
.async_send()
.await;
}
pub async fn read_app(sdk: &FlowyTestSDK, request: QueryAppRequest) -> App {
let app = FlowyWorkspaceTest::new(sdk.clone())
.event(ReadApp)
.request(request)
.async_send()
.await
.parse::<App>();
app
}
pub async fn create_view_with_request(sdk: &FlowyTestSDK, request: CreateViewRequest) -> View {
let view = FlowyWorkspaceTest::new(sdk.clone())
.event(CreateView)
.request(request)
.async_send()
.await
.parse::<View>();
view
}
pub async fn create_view(sdk: &FlowyTestSDK, app_id: &str) -> View {
let request = CreateViewRequest {
belong_to_id: app_id.to_string(),
name: "View A".to_string(),
desc: "".to_string(),
thumbnail: Some("http://1.png".to_string()),
view_type: ViewType::Doc,
};
create_view_with_request(sdk, request).await
}
pub async fn update_view(sdk: &FlowyTestSDK, request: UpdateViewRequest) {
FlowyWorkspaceTest::new(sdk.clone())
.event(UpdateView)
.request(request)
.async_send()
.await;
}
pub async fn read_view(sdk: &FlowyTestSDK, request: QueryViewRequest) -> View {
FlowyWorkspaceTest::new(sdk.clone())
.event(ReadView)
.request(request)
.async_send()
.await
.parse::<View>()
}
pub async fn delete_view(sdk: &FlowyTestSDK, request: QueryViewRequest) {
FlowyWorkspaceTest::new(sdk.clone())
.event(DeleteView)
.request(request)
.async_send()
.await;
}
pub async fn read_trash(sdk: &FlowyTestSDK) -> RepeatedTrash {
FlowyWorkspaceTest::new(sdk.clone())
.event(ReadTrash)
.async_send()
.await
.parse::<RepeatedTrash>()
}
pub async fn putback_trash(sdk: &FlowyTestSDK, id: TrashIdentifier) {
FlowyWorkspaceTest::new(sdk.clone())
.event(PutbackTrash)
.request(id)
.async_send()
.await;
}
pub async fn open_view(sdk: &FlowyTestSDK, request: QueryViewRequest) -> Doc {
FlowyWorkspaceTest::new(sdk.clone())
.event(OpenView)
.request(request)
.async_send()
.await
.parse::<Doc>()
}