mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: add more test and fix warnings
This commit is contained in:
parent
47081f3095
commit
b3ed254caf
@ -64,6 +64,8 @@ impl FlowyError {
|
||||
static_flowy_error!(name_empty, ErrorCode::UserNameIsEmpty);
|
||||
static_flowy_error!(user_id, ErrorCode::UserIdInvalid);
|
||||
static_flowy_error!(user_not_exist, ErrorCode::UserNotExist);
|
||||
static_flowy_error!(text_too_long, ErrorCode::TextTooLong);
|
||||
static_flowy_error!(invalid_data, ErrorCode::InvalidData);
|
||||
}
|
||||
|
||||
impl std::convert::From<ErrorCode> for FlowyError {
|
||||
|
@ -351,7 +351,7 @@ pub async fn create_view(sdk: &FlowySDKTest, app_id: &str, name: &str, desc: &st
|
||||
thumbnail: None,
|
||||
data_type,
|
||||
plugin_type: 0,
|
||||
data: "".to_string(),
|
||||
data: vec![],
|
||||
};
|
||||
let view = FolderEventBuilder::new(sdk.clone())
|
||||
.event(CreateView)
|
||||
|
@ -36,11 +36,7 @@ pub(crate) async fn get_fields_handler(
|
||||
let payload: QueryFieldPayload = data.into_inner();
|
||||
let editor = manager.get_grid_editor(&payload.grid_id)?;
|
||||
let field_metas = editor.get_field_metas(Some(payload.field_orders)).await?;
|
||||
let repeated_field: RepeatedField = field_metas
|
||||
.into_iter()
|
||||
.map(|field_meta| Field::from(field_meta))
|
||||
.collect::<Vec<_>>()
|
||||
.into();
|
||||
let repeated_field: RepeatedField = field_metas.into_iter().map(Field::from).collect::<Vec<_>>().into();
|
||||
data_result(repeated_field)
|
||||
}
|
||||
|
||||
|
@ -111,8 +111,7 @@ impl GridManager {
|
||||
) -> Result<Arc<ClientGridEditor>, FlowyError> {
|
||||
let user = self.grid_user.clone();
|
||||
let rev_manager = self.make_grid_rev_manager(grid_id, pool.clone())?;
|
||||
let kv_persistence = self.get_kv_persistence()?;
|
||||
let grid_editor = ClientGridEditor::new(grid_id, user, rev_manager, kv_persistence).await?;
|
||||
let grid_editor = ClientGridEditor::new(grid_id, user, rev_manager).await?;
|
||||
Ok(grid_editor)
|
||||
}
|
||||
|
||||
@ -137,6 +136,7 @@ impl GridManager {
|
||||
Ok(rev_manager)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn get_kv_persistence(&self) -> FlowyResult<Arc<GridKVPersistence>> {
|
||||
let read_guard = self.kv_persistence.read();
|
||||
if read_guard.is_some() {
|
||||
@ -198,7 +198,7 @@ pub async fn make_grid_view_data(
|
||||
let grid_block_meta_delta = make_block_meta_delta(&build_context.grid_block_meta);
|
||||
let block_meta_delta_data = grid_block_meta_delta.to_delta_bytes();
|
||||
let repeated_revision: RepeatedRevision =
|
||||
Revision::initial_revision(&user_id, &block_id, block_meta_delta_data).into();
|
||||
Revision::initial_revision(user_id, &block_id, block_meta_delta_data).into();
|
||||
let _ = grid_manager
|
||||
.create_grid_block_meta(&block_id, repeated_revision)
|
||||
.await?;
|
||||
|
@ -139,7 +139,7 @@ impl GridBlockMetaEditorManager {
|
||||
for grid_block in grid_blocks {
|
||||
let editor = self.get_editor(&grid_block.id).await?;
|
||||
let row_metas = editor.get_row_metas(None).await?;
|
||||
let block_row_orders = row_metas.iter().map(|row_meta| RowOrder::from(row_meta));
|
||||
let block_row_orders = row_metas.iter().map(RowOrder::from);
|
||||
row_orders.extend(block_row_orders);
|
||||
}
|
||||
Ok(row_orders)
|
||||
@ -257,7 +257,7 @@ impl ClientGridBlockMetaEditor {
|
||||
.await
|
||||
.get_rows(None)?
|
||||
.iter()
|
||||
.map(|row_meta| RowOrder::from(row_meta))
|
||||
.map(RowOrder::from)
|
||||
.collect::<Vec<RowOrder>>();
|
||||
Ok(row_orders)
|
||||
}
|
||||
|
@ -53,14 +53,11 @@ impl CellDataSerde for DateDescription {
|
||||
}
|
||||
|
||||
fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> {
|
||||
let timestamp = match data.parse::<i64>() {
|
||||
Ok(timestamp) => timestamp,
|
||||
Err(e) => {
|
||||
tracing::error!("Parse {} to i64 failed: {}", data, e);
|
||||
chrono::Utc::now().timestamp()
|
||||
}
|
||||
if let Err(e) = data.parse::<i64>() {
|
||||
tracing::error!("Parse {} to i64 failed: {}", data, e);
|
||||
return Err(FlowyError::internal().context(e));
|
||||
};
|
||||
Ok(format!("{}", timestamp))
|
||||
Ok(data.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ impl NumberDescription {
|
||||
}
|
||||
|
||||
fn decimal_from_str(&self, s: &str) -> Decimal {
|
||||
let mut decimal = Decimal::from_str(s).unwrap_or(Decimal::zero());
|
||||
let mut decimal = Decimal::from_str(s).unwrap_or_else(|_| Decimal::zero());
|
||||
match decimal.set_scale(self.scale) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
@ -130,7 +130,12 @@ impl CellDataSerde for NumberDescription {
|
||||
}
|
||||
|
||||
fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> {
|
||||
Ok(self.strip_symbol(data))
|
||||
let data = self.strip_symbol(data);
|
||||
|
||||
if !data.chars().all(char::is_numeric) {
|
||||
return Err(FlowyError::invalid_data().context("Should only contain numbers"));
|
||||
}
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,8 +193,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn number_description_scale_test() {
|
||||
let mut description = NumberDescription::default();
|
||||
description.scale = 1;
|
||||
let mut description = NumberDescription {
|
||||
scale: 1,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for format in NumberFormat::iter() {
|
||||
description.format = format;
|
||||
@ -224,8 +231,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn number_description_sign_test() {
|
||||
let mut description = NumberDescription::default();
|
||||
description.sign_positive = false;
|
||||
let mut description = NumberDescription {
|
||||
sign_positive: false,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for format in NumberFormat::iter() {
|
||||
description.format = format;
|
||||
|
@ -2,9 +2,13 @@ use crate::impl_from_and_to_type_option;
|
||||
use crate::services::row::CellDataSerde;
|
||||
use crate::services::util::*;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_grid_data_model::entities::{FieldMeta, FieldType};
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub const SELECTION_IDS_SEPARATOR: &str = ",";
|
||||
|
||||
// Single select
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
|
||||
@ -23,7 +27,7 @@ impl CellDataSerde for SingleSelectDescription {
|
||||
}
|
||||
|
||||
fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> {
|
||||
Ok(select_option_id_from_data(data.to_owned(), true))
|
||||
single_select_option_id_from_data(data.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,20 +47,45 @@ impl CellDataSerde for MultiSelectDescription {
|
||||
}
|
||||
|
||||
fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> {
|
||||
Ok(select_option_id_from_data(data.to_owned(), false))
|
||||
multi_select_option_id_from_data(data.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
fn select_option_id_from_data(data: String, is_single_select: bool) -> String {
|
||||
if !is_single_select {
|
||||
return data;
|
||||
}
|
||||
let select_option_ids = data.split(',').collect::<Vec<&str>>();
|
||||
fn single_select_option_id_from_data(data: String) -> FlowyResult<String> {
|
||||
let select_option_ids = select_option_ids(data)?;
|
||||
if select_option_ids.is_empty() {
|
||||
return "".to_owned();
|
||||
return Ok("".to_owned());
|
||||
}
|
||||
|
||||
select_option_ids.split_first().unwrap().0.to_string()
|
||||
Ok(select_option_ids.split_first().unwrap().0.to_string())
|
||||
}
|
||||
|
||||
fn multi_select_option_id_from_data(data: String) -> FlowyResult<String> {
|
||||
let select_option_ids = select_option_ids(data)?;
|
||||
Ok(select_option_ids.join(SELECTION_IDS_SEPARATOR))
|
||||
}
|
||||
|
||||
fn select_option_ids(mut data: String) -> FlowyResult<Vec<String>> {
|
||||
data.retain(|c| !c.is_whitespace());
|
||||
let select_option_ids = data.split(SELECTION_IDS_SEPARATOR).collect::<Vec<&str>>();
|
||||
if select_option_ids
|
||||
.par_iter()
|
||||
.find_first(|option_id| match Uuid::parse_str(option_id) {
|
||||
Ok(_) => false,
|
||||
Err(e) => {
|
||||
tracing::error!("{}", e);
|
||||
true
|
||||
}
|
||||
})
|
||||
.is_some()
|
||||
{
|
||||
let msg = format!(
|
||||
"Invalid selection id string: {}. It should consist of the uuid string and separated by comma",
|
||||
data
|
||||
);
|
||||
return Err(FlowyError::internal().context(msg));
|
||||
}
|
||||
Ok(select_option_ids.iter().map(|id| id.to_string()).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
|
||||
|
@ -19,6 +19,11 @@ impl CellDataSerde for RichTextDescription {
|
||||
}
|
||||
|
||||
fn serialize_cell_data(&self, data: &str) -> Result<String, FlowyError> {
|
||||
Ok(data.to_owned())
|
||||
let data = data.to_owned();
|
||||
if data.len() > 10000 {
|
||||
Err(FlowyError::text_too_long().context("The len of the text should not be more than 10000"))
|
||||
} else {
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
use crate::manager::GridUser;
|
||||
use crate::services::block_meta_editor::GridBlockMetaEditorManager;
|
||||
use crate::services::kv_persistence::{GridKVPersistence, KVTransaction};
|
||||
use bytes::Bytes;
|
||||
use flowy_collaboration::client_grid::{GridChange, GridMetaPad};
|
||||
use flowy_collaboration::entities::revision::Revision;
|
||||
use flowy_collaboration::util::make_delta_from_revisions;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_grid_data_model::entities::{
|
||||
CellMetaChangeset, Field, FieldChangeset, FieldMeta, Grid, GridBlock, GridBlockChangeset, RepeatedFieldOrder,
|
||||
CellMetaChangeset, FieldChangeset, FieldMeta, Grid, GridBlock, GridBlockChangeset, RepeatedFieldOrder,
|
||||
RepeatedRowOrder, Row, RowMeta, RowMetaChangeset,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::services::row::{
|
||||
make_row_by_row_id, make_rows, row_meta_from_context, CreateRowContext, CreateRowContextBuilder,
|
||||
make_row_by_row_id, make_rows, row_meta_from_context, serialize_cell_data, CreateRowContext,
|
||||
CreateRowContextBuilder,
|
||||
};
|
||||
use flowy_sync::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder};
|
||||
use lib_infra::future::FutureResult;
|
||||
@ -27,7 +27,6 @@ pub struct ClientGridEditor {
|
||||
grid_meta_pad: Arc<RwLock<GridMetaPad>>,
|
||||
rev_manager: Arc<RevisionManager>,
|
||||
block_meta_manager: Arc<GridBlockMetaEditorManager>,
|
||||
kv_persistence: Arc<GridKVPersistence>,
|
||||
}
|
||||
|
||||
impl ClientGridEditor {
|
||||
@ -35,7 +34,6 @@ impl ClientGridEditor {
|
||||
grid_id: &str,
|
||||
user: Arc<dyn GridUser>,
|
||||
mut rev_manager: RevisionManager,
|
||||
kv_persistence: Arc<GridKVPersistence>,
|
||||
) -> FlowyResult<Arc<Self>> {
|
||||
let token = user.token()?;
|
||||
let cloud = Arc::new(GridRevisionCloudService { token });
|
||||
@ -52,7 +50,6 @@ impl ClientGridEditor {
|
||||
grid_meta_pad,
|
||||
rev_manager,
|
||||
block_meta_manager,
|
||||
kv_persistence,
|
||||
}))
|
||||
}
|
||||
|
||||
@ -88,7 +85,8 @@ impl ClientGridEditor {
|
||||
pub async fn create_row(&self) -> FlowyResult<()> {
|
||||
let field_metas = self.grid_meta_pad.read().await.get_field_metas(None)?;
|
||||
let block_id = self.last_block_id().await?;
|
||||
let row = row_meta_from_context(&block_id, CreateRowContextBuilder::new(&field_metas).build());
|
||||
let create_row_ctx = CreateRowContextBuilder::new(&field_metas).build();
|
||||
let row = row_meta_from_context(&block_id, create_row_ctx);
|
||||
let row_count = self.block_meta_manager.create_row(row).await?;
|
||||
let changeset = GridBlockChangeset::from_row_count(&block_id, row_count);
|
||||
let _ = self.update_block(changeset).await?;
|
||||
@ -98,12 +96,11 @@ impl ClientGridEditor {
|
||||
pub async fn insert_rows(&self, contexts: Vec<CreateRowContext>) -> FlowyResult<()> {
|
||||
let block_id = self.last_block_id().await?;
|
||||
let mut rows_by_block_id: HashMap<String, Vec<RowMeta>> = HashMap::new();
|
||||
|
||||
for ctx in contexts {
|
||||
let row_meta = row_meta_from_context(&block_id, ctx);
|
||||
rows_by_block_id
|
||||
.entry(block_id.clone())
|
||||
.or_insert(Vec::new())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(row_meta);
|
||||
}
|
||||
let changesets = self.block_meta_manager.insert_row(rows_by_block_id).await?;
|
||||
@ -118,6 +115,18 @@ impl ClientGridEditor {
|
||||
}
|
||||
|
||||
pub async fn update_cell(&self, changeset: CellMetaChangeset) -> FlowyResult<()> {
|
||||
if let Some(cell_data) = changeset.data.as_ref() {
|
||||
match self.grid_meta_pad.read().await.get_field(&changeset.field_id) {
|
||||
None => {
|
||||
return Err(FlowyError::internal()
|
||||
.context(format!("Can not find the field with id: {}", &changeset.field_id)));
|
||||
}
|
||||
Some(field_meta) => {
|
||||
let _ = serialize_cell_data(cell_data, field_meta)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let row_changeset: RowMetaChangeset = changeset.into();
|
||||
self.update_row(row_changeset).await
|
||||
}
|
||||
|
@ -1,28 +1,43 @@
|
||||
use crate::services::row::serialize_cell_data;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_grid_data_model::entities::{CellMeta, FieldMeta, RowMeta, DEFAULT_ROW_HEIGHT};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct CreateRowContextBuilder<'a> {
|
||||
#[allow(dead_code)]
|
||||
fields: &'a [FieldMeta],
|
||||
field_meta_map: HashMap<&'a String, &'a FieldMeta>,
|
||||
ctx: CreateRowContext,
|
||||
}
|
||||
|
||||
impl<'a> CreateRowContextBuilder<'a> {
|
||||
pub fn new(fields: &'a [FieldMeta]) -> Self {
|
||||
let field_meta_map = fields
|
||||
.iter()
|
||||
.map(|field| (&field.id, field))
|
||||
.collect::<HashMap<&String, &FieldMeta>>();
|
||||
|
||||
let ctx = CreateRowContext {
|
||||
row_id: uuid::Uuid::new_v4().to_string(),
|
||||
cell_by_field_id: Default::default(),
|
||||
height: DEFAULT_ROW_HEIGHT,
|
||||
visibility: true,
|
||||
};
|
||||
Self { fields, ctx }
|
||||
|
||||
Self { field_meta_map, ctx }
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn add_cell(mut self, field_id: &str, data: String) -> Self {
|
||||
let cell = CellMeta::new(field_id, data);
|
||||
self.ctx.cell_by_field_id.insert(field_id.to_owned(), cell);
|
||||
self
|
||||
pub fn add_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> {
|
||||
match self.field_meta_map.get(&field_id.to_owned()) {
|
||||
None => {
|
||||
let msg = format!("Invalid field_id: {}", field_id);
|
||||
Err(FlowyError::internal().context(msg))
|
||||
}
|
||||
Some(field_meta) => {
|
||||
let data = serialize_cell_data(&data, field_meta)?;
|
||||
let cell = CellMeta::new(field_id, data);
|
||||
self.ctx.cell_by_field_id.insert(field_id.to_owned(), cell);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -1,8 +1,11 @@
|
||||
use crate::grid::script::EditorScript::*;
|
||||
use crate::grid::script::*;
|
||||
use chrono::NaiveDateTime;
|
||||
use flowy_grid::services::cell::*;
|
||||
use flowy_grid::services::row::{deserialize_cell_data, serialize_cell_data, CellDataSerde, CreateRowContextBuilder};
|
||||
use flowy_grid_data_model::entities::{FieldChangeset, FieldType, GridBlock, GridBlockChangeset, RowMetaChangeset};
|
||||
use flowy_grid_data_model::entities::{
|
||||
CellMetaChangeset, FieldChangeset, FieldType, GridBlock, GridBlockChangeset, RowMetaChangeset,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_create_field() {
|
||||
@ -237,29 +240,28 @@ async fn grid_delete_row() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_update_cell() {
|
||||
async fn grid_row_add_cells_test() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let mut builder = CreateRowContextBuilder::new(&test.field_metas);
|
||||
for field in &test.field_metas {
|
||||
match field.field_type {
|
||||
FieldType::RichText => {
|
||||
let data = serialize_cell_data("hello world", field).unwrap();
|
||||
builder = builder.add_cell(&field.id, data);
|
||||
builder.add_cell(&field.id, data).unwrap();
|
||||
}
|
||||
FieldType::Number => {
|
||||
let data = serialize_cell_data("¥18,443", field).unwrap();
|
||||
builder = builder.add_cell(&field.id, data);
|
||||
builder.add_cell(&field.id, data).unwrap();
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
let data = serialize_cell_data("1647251762", field).unwrap();
|
||||
builder = builder.add_cell(&field.id, data);
|
||||
builder.add_cell(&field.id, data).unwrap();
|
||||
}
|
||||
FieldType::SingleSelect => {
|
||||
let description = SingleSelectDescription::from(field);
|
||||
let options = description.options.first().unwrap();
|
||||
|
||||
let data = description.serialize_cell_data(&options.id).unwrap();
|
||||
builder = builder.add_cell(&field.id, data);
|
||||
builder.add_cell(&field.id, data).unwrap();
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
let description = MultiSelectDescription::from(field);
|
||||
@ -268,17 +270,161 @@ async fn grid_update_cell() {
|
||||
.iter()
|
||||
.map(|option| option.id.clone())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
.join(SELECTION_IDS_SEPARATOR);
|
||||
let data = description.serialize_cell_data(&options).unwrap();
|
||||
builder = builder.add_cell(&field.id, data);
|
||||
builder.add_cell(&field.id, data).unwrap();
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
let data = serialize_cell_data("false", field).unwrap();
|
||||
builder = builder.add_cell(&field.id, data);
|
||||
builder.add_cell(&field.id, data).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
let context = builder.build();
|
||||
let scripts = vec![AssertRowCount(3), CreateRow { context }, AssertGridMetaPad];
|
||||
let scripts = vec![CreateRow { context }, AssertGridMetaPad];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_row_add_selection_cell_test() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let mut builder = CreateRowContextBuilder::new(&test.field_metas);
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
let mut single_select_field_id = "".to_string();
|
||||
let mut multi_select_field_id = "".to_string();
|
||||
for field in &test.field_metas {
|
||||
match field.field_type {
|
||||
FieldType::SingleSelect => {
|
||||
single_select_field_id = field.id.clone();
|
||||
// The element must be parsed as uuid
|
||||
assert!(builder.add_cell(&field.id, "data".to_owned()).is_err());
|
||||
// // The data should not be empty
|
||||
assert!(builder.add_cell(&field.id, "".to_owned()).is_err());
|
||||
// The element must be parsed as uuid
|
||||
assert!(builder.add_cell(&field.id, "1,2,3".to_owned()).is_err(),);
|
||||
// The separator must be comma
|
||||
assert!(builder.add_cell(&field.id, format!("{}. {}", &uuid, &uuid),).is_err());
|
||||
//
|
||||
|
||||
assert!(builder.add_cell(&field.id, uuid.clone()).is_ok());
|
||||
assert!(builder.add_cell(&field.id, format!("{}, {}", &uuid, &uuid)).is_ok());
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
multi_select_field_id = field.id.clone();
|
||||
assert!(builder.add_cell(&field.id, format!("{}, {}", &uuid, &uuid)).is_ok());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let context = builder.build();
|
||||
assert_eq!(
|
||||
&context
|
||||
.cell_by_field_id
|
||||
.get(&single_select_field_id)
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.data,
|
||||
&uuid
|
||||
);
|
||||
assert_eq!(
|
||||
context
|
||||
.cell_by_field_id
|
||||
.get(&multi_select_field_id)
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.data,
|
||||
format!("{},{}", &uuid, &uuid)
|
||||
);
|
||||
|
||||
let scripts = vec![CreateRow { context }];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_row_add_date_cell_test() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let mut builder = CreateRowContextBuilder::new(&test.field_metas);
|
||||
let mut date_field = None;
|
||||
let timestamp = 1647390674;
|
||||
for field in &test.field_metas {
|
||||
if field.field_type == FieldType::DateTime {
|
||||
date_field = Some(field.clone());
|
||||
NaiveDateTime::from_timestamp(123, 0);
|
||||
// The data should not be empty
|
||||
assert!(builder.add_cell(&field.id, "".to_owned()).is_err());
|
||||
|
||||
assert!(builder.add_cell(&field.id, "123".to_owned()).is_ok());
|
||||
assert!(builder.add_cell(&field.id, format!("{}", timestamp)).is_ok());
|
||||
}
|
||||
}
|
||||
let context = builder.build();
|
||||
let date_field = date_field.unwrap();
|
||||
let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone();
|
||||
assert_eq!(
|
||||
deserialize_cell_data(cell_data.data.clone(), &date_field).unwrap(),
|
||||
"2022/03/16 08:31",
|
||||
);
|
||||
let scripts = vec![CreateRow { context }];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_cell_update() {
|
||||
let mut test = GridEditorTest::new().await;
|
||||
let field_metas = &test.field_metas;
|
||||
let row_metas = &test.row_metas;
|
||||
assert_eq!(row_metas.len(), 3);
|
||||
|
||||
let mut scripts = vec![];
|
||||
for (index, row_meta) in row_metas.iter().enumerate() {
|
||||
for field_meta in field_metas {
|
||||
if index == 0 {
|
||||
let data = match field_meta.field_type {
|
||||
FieldType::RichText => "".to_string(),
|
||||
FieldType::Number => "123".to_string(),
|
||||
FieldType::DateTime => "123".to_string(),
|
||||
FieldType::SingleSelect => {
|
||||
let description = SingleSelectDescription::from(field_meta);
|
||||
description.options.first().unwrap().id.clone()
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
let description = MultiSelectDescription::from(field_meta);
|
||||
description.options.first().unwrap().id.clone()
|
||||
}
|
||||
FieldType::Checkbox => "1".to_string(),
|
||||
};
|
||||
|
||||
scripts.push(UpdateCell {
|
||||
changeset: CellMetaChangeset {
|
||||
row_id: row_meta.id.clone(),
|
||||
field_id: field_meta.id.clone(),
|
||||
data: Some(data),
|
||||
},
|
||||
is_err: false,
|
||||
});
|
||||
}
|
||||
|
||||
if index == 1 {
|
||||
let (data, is_err) = match field_meta.field_type {
|
||||
FieldType::RichText => ("1".to_string().repeat(10001), true),
|
||||
FieldType::Number => ("abc".to_string(), true),
|
||||
FieldType::DateTime => ("abc".to_string(), true),
|
||||
FieldType::SingleSelect => ("abc".to_string(), true),
|
||||
FieldType::MultiSelect => ("abc".to_string(), true),
|
||||
FieldType::Checkbox => ("2".to_string(), false),
|
||||
};
|
||||
|
||||
scripts.push(UpdateCell {
|
||||
changeset: CellMetaChangeset {
|
||||
row_id: row_meta.id.clone(),
|
||||
field_id: field_meta.id.clone(),
|
||||
data: Some(data),
|
||||
},
|
||||
is_err,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
use bytes::Bytes;
|
||||
use flowy_collaboration::client_grid::GridBuilder;
|
||||
use flowy_collaboration::entities::revision::{RepeatedRevision, Revision};
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_grid::manager::{make_grid_view_data, GridManager};
|
||||
|
||||
use flowy_grid::services::cell::*;
|
||||
use flowy_grid::services::field::*;
|
||||
use flowy_grid::services::grid_editor::{ClientGridEditor, GridPadBuilder};
|
||||
@ -16,7 +14,7 @@ use flowy_test::helper::ViewTest;
|
||||
use flowy_test::FlowySDKTest;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use strum::{EnumCount, IntoEnumIterator};
|
||||
use strum::EnumCount;
|
||||
use tokio::time::sleep;
|
||||
|
||||
pub enum EditorScript {
|
||||
@ -65,6 +63,7 @@ pub enum EditorScript {
|
||||
},
|
||||
UpdateCell {
|
||||
changeset: CellMetaChangeset,
|
||||
is_err: bool,
|
||||
},
|
||||
AssertRowCount(usize),
|
||||
// AssertRowEqual{ row_index: usize, row: RowMeta},
|
||||
@ -199,9 +198,14 @@ impl GridEditorTest {
|
||||
assert_eq!(row.height, height);
|
||||
}
|
||||
}
|
||||
EditorScript::UpdateCell { changeset } => {
|
||||
self.editor.update_cell(changeset).await.unwrap();
|
||||
self.row_metas = self.editor.get_row_metas(None).await.unwrap();
|
||||
EditorScript::UpdateCell { changeset, is_err } => {
|
||||
let result = self.editor.update_cell(changeset).await;
|
||||
if is_err {
|
||||
assert!(result.is_err())
|
||||
} else {
|
||||
let _ = result.unwrap();
|
||||
self.row_metas = self.editor.get_row_metas(None).await.unwrap();
|
||||
}
|
||||
}
|
||||
EditorScript::AssertRowCount(count) => {
|
||||
assert_eq!(self.editor.get_rows(None).await.unwrap().len(), count);
|
||||
|
@ -4,7 +4,7 @@ use flowy_collaboration::client_document::default::initial_quill_delta_string;
|
||||
use flowy_collaboration::entities::revision::{RepeatedRevision, Revision};
|
||||
use flowy_collaboration::entities::ws_data::ClientRevisionWSData;
|
||||
use flowy_database::ConnectionPool;
|
||||
use flowy_folder::errors::FlowyResult;
|
||||
|
||||
use flowy_folder::manager::{ViewDataProcessor, ViewDataProcessorMap};
|
||||
use flowy_folder::prelude::ViewDataType;
|
||||
use flowy_folder::{
|
||||
|
@ -52,7 +52,7 @@ impl GridBlockMetaPad {
|
||||
|
||||
pub fn get_rows(&self, row_ids: Option<Vec<String>>) -> CollaborateResult<Vec<Arc<RowMeta>>> {
|
||||
match row_ids {
|
||||
None => Ok(self.rows.iter().map(|row| row.clone()).collect::<Vec<_>>()),
|
||||
None => Ok(self.rows.to_vec()),
|
||||
Some(row_ids) => {
|
||||
let row_map = self
|
||||
.rows
|
||||
|
@ -1,19 +1,11 @@
|
||||
use crate::client_grid::{make_block_meta_delta, make_grid_delta, GridBlockMetaDelta, GridMetaDelta};
|
||||
use crate::errors::{CollaborateError, CollaborateResult};
|
||||
use flowy_grid_data_model::entities::{BuildGridContext, FieldMeta, GridBlock, GridBlockMeta, GridMeta, RowMeta};
|
||||
use flowy_grid_data_model::entities::{BuildGridContext, FieldMeta, RowMeta};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GridBuilder {
|
||||
build_context: BuildGridContext,
|
||||
}
|
||||
|
||||
impl std::default::Default for GridBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
build_context: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GridBuilder {
|
||||
pub fn add_field(mut self, field: FieldMeta) -> Self {
|
||||
self.build_context.field_metas.push(field);
|
||||
@ -63,7 +55,7 @@ mod tests {
|
||||
.build();
|
||||
|
||||
let grid_meta = GridMeta {
|
||||
grid_id: grid_id.clone(),
|
||||
grid_id,
|
||||
fields: build_context.field_metas,
|
||||
blocks: vec![build_context.grid_block],
|
||||
};
|
||||
|
@ -58,19 +58,15 @@ impl GridMetaPad {
|
||||
}
|
||||
|
||||
pub fn contain_field(&self, field_id: &str) -> bool {
|
||||
self.grid_meta
|
||||
.fields
|
||||
.iter()
|
||||
.find(|field| &field.id == field_id)
|
||||
.is_some()
|
||||
self.grid_meta.fields.iter().any(|field| field.id == field_id)
|
||||
}
|
||||
|
||||
pub fn get_field(&self, field_id: &str) -> Option<&FieldMeta> {
|
||||
self.grid_meta.fields.iter().find(|field| field.id == field_id)
|
||||
}
|
||||
|
||||
pub fn get_field_orders(&self) -> Vec<FieldOrder> {
|
||||
self.grid_meta
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field_meta| FieldOrder::from(field_meta))
|
||||
.collect()
|
||||
self.grid_meta.fields.iter().map(FieldOrder::from).collect()
|
||||
}
|
||||
|
||||
pub fn get_field_metas(&self, field_orders: Option<RepeatedFieldOrder>) -> CollaborateResult<Vec<FieldMeta>> {
|
||||
@ -154,7 +150,7 @@ impl GridMetaPad {
|
||||
if last_block.start_row_index > block.start_row_index
|
||||
&& last_block.len() > block.start_row_index
|
||||
{
|
||||
let msg = format!("GridBlock's start_row_index should be greater than the last_block's start_row_index and its len");
|
||||
let msg = "GridBlock's start_row_index should be greater than the last_block's start_row_index and its len".to_string();
|
||||
return Err(CollaborateError::internal().context(msg))
|
||||
}
|
||||
grid.blocks.push(block);
|
@ -1,7 +1,7 @@
|
||||
mod block_pad;
|
||||
mod grid_builder;
|
||||
mod grid_pad;
|
||||
mod grid_meta_pad;
|
||||
|
||||
pub use block_pad::*;
|
||||
pub use grid_builder::*;
|
||||
pub use grid_pad::*;
|
||||
pub use grid_meta_pad::*;
|
||||
|
@ -83,6 +83,10 @@ pub enum ErrorCode {
|
||||
UserIdInvalid = 311,
|
||||
#[display(fmt = "User not exist")]
|
||||
UserNotExist = 312,
|
||||
#[display(fmt = "Text is too long")]
|
||||
TextTooLong = 400,
|
||||
#[display(fmt = "Invalid data")]
|
||||
InvalidData = 401,
|
||||
}
|
||||
|
||||
impl ErrorCode {
|
||||
|
@ -55,6 +55,8 @@ pub enum ErrorCode {
|
||||
UserNameIsEmpty = 310,
|
||||
UserIdInvalid = 311,
|
||||
UserNotExist = 312,
|
||||
TextTooLong = 400,
|
||||
InvalidData = 401,
|
||||
}
|
||||
|
||||
impl ::protobuf::ProtobufEnum for ErrorCode {
|
||||
@ -94,6 +96,8 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
|
||||
310 => ::std::option::Option::Some(ErrorCode::UserNameIsEmpty),
|
||||
311 => ::std::option::Option::Some(ErrorCode::UserIdInvalid),
|
||||
312 => ::std::option::Option::Some(ErrorCode::UserNotExist),
|
||||
400 => ::std::option::Option::Some(ErrorCode::TextTooLong),
|
||||
401 => ::std::option::Option::Some(ErrorCode::InvalidData),
|
||||
_ => ::std::option::Option::None
|
||||
}
|
||||
}
|
||||
@ -130,6 +134,8 @@ impl ::protobuf::ProtobufEnum for ErrorCode {
|
||||
ErrorCode::UserNameIsEmpty,
|
||||
ErrorCode::UserIdInvalid,
|
||||
ErrorCode::UserNotExist,
|
||||
ErrorCode::TextTooLong,
|
||||
ErrorCode::InvalidData,
|
||||
];
|
||||
values
|
||||
}
|
||||
@ -158,7 +164,7 @@ impl ::protobuf::reflect::ProtobufValue for ErrorCode {
|
||||
}
|
||||
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\ncode.proto*\xc4\x05\n\tErrorCode\x12\x0c\n\x08Internal\x10\0\x12\x14\
|
||||
\n\ncode.proto*\xe8\x05\n\tErrorCode\x12\x0c\n\x08Internal\x10\0\x12\x14\
|
||||
\n\x10UserUnauthorized\x10\x02\x12\x12\n\x0eRecordNotFound\x10\x03\x12\
|
||||
\x18\n\x14WorkspaceNameInvalid\x10d\x12\x16\n\x12WorkspaceIdInvalid\x10e\
|
||||
\x12\x18\n\x14AppColorStyleInvalid\x10f\x12\x18\n\x14WorkspaceDescTooLon\
|
||||
@ -174,8 +180,9 @@ static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
rmatInvalid\x10\xb2\x02\x12\x15\n\x10PasswordNotMatch\x10\xb3\x02\x12\
|
||||
\x14\n\x0fUserNameTooLong\x10\xb4\x02\x12'\n\"UserNameContainForbiddenCh\
|
||||
aracters\x10\xb5\x02\x12\x14\n\x0fUserNameIsEmpty\x10\xb6\x02\x12\x12\n\
|
||||
\rUserIdInvalid\x10\xb7\x02\x12\x11\n\x0cUserNotExist\x10\xb8\x02b\x06pr\
|
||||
oto3\
|
||||
\rUserIdInvalid\x10\xb7\x02\x12\x11\n\x0cUserNotExist\x10\xb8\x02\x12\
|
||||
\x10\n\x0bTextTooLong\x10\x90\x03\x12\x10\n\x0bInvalidData\x10\x91\x03b\
|
||||
\x06proto3\
|
||||
";
|
||||
|
||||
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
||||
|
@ -31,4 +31,6 @@ enum ErrorCode {
|
||||
UserNameIsEmpty = 310;
|
||||
UserIdInvalid = 311;
|
||||
UserNotExist = 312;
|
||||
TextTooLong = 400;
|
||||
InvalidData = 401;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use crate::{
|
||||
impl_def_and_def_mut,
|
||||
parser::{
|
||||
app::AppIdentify,
|
||||
view::{ViewDesc, ViewExtensionData, ViewIdentify, ViewName, ViewThumbnail},
|
||||
view::{ViewDesc, ViewIdentify, ViewName, ViewThumbnail},
|
||||
},
|
||||
};
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
|
@ -1,7 +1,7 @@
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use strum::{EnumCount, IntoEnumIterator};
|
||||
|
||||
use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter, EnumString};
|
||||
|
||||
pub const DEFAULT_ROW_HEIGHT: i32 = 36;
|
||||
@ -35,6 +35,10 @@ impl GridBlock {
|
||||
pub fn len(&self) -> i32 {
|
||||
self.start_row_index + self.row_count
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.row_count == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl GridBlock {
|
||||
|
@ -65,34 +65,3 @@ where
|
||||
}
|
||||
|
||||
pub type BoxResultFuture<'a, T, E> = BoxFuture<'a, Result<T, E>>;
|
||||
|
||||
#[pin_project]
|
||||
pub struct FutureResultSend<T, E> {
|
||||
#[pin]
|
||||
pub fut: Pin<Box<dyn Future<Output = Result<T, E>> + Send>>,
|
||||
}
|
||||
|
||||
impl<T, E> FutureResultSend<T, E> {
|
||||
pub fn new<F>(f: F) -> Self
|
||||
where
|
||||
F: Future<Output = Result<T, E>> + Send + 'static,
|
||||
{
|
||||
Self {
|
||||
fut: Box::pin(async { f.await }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Future for FutureResultSend<T, E>
|
||||
where
|
||||
T: Send,
|
||||
E: Debug,
|
||||
{
|
||||
type Output = Result<T, E>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.as_mut().project();
|
||||
let result = ready!(this.fut.poll(cx));
|
||||
Poll::Ready(result)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user