소스 검색

fix: unit test

appflowy 3 년 전
부모
커밋
8c7b0bd5a7

+ 24 - 34
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs

@@ -1,15 +1,14 @@
 use crate::impl_type_option;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData};
+use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData};
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
-use flowy_error::FlowyError;
+use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{
     CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
 };
 
 use serde::{Deserialize, Serialize};
-use std::str::FromStr;
 
 #[derive(Default)]
 pub struct CheckboxTypeOptionBuilder(CheckboxTypeOption);
@@ -43,23 +42,26 @@ impl_type_option!(CheckboxTypeOption, FieldType::Checkbox);
 const YES: &str = "Yes";
 const NO: &str = "No";
 
-impl CellDataOperation<String> for CheckboxTypeOption {
-    fn decode_cell_data<T: Into<TypeOptionCellData>>(
+impl CellDataOperation<String, String> for CheckboxTypeOption {
+    fn decode_cell_data<T>(
         &self,
-        type_option_cell_data: T,
+        encoded_data: T,
         decoded_field_type: &FieldType,
         _field_meta: &FieldMeta,
-    ) -> DecodedCellData {
-        let type_option_cell_data = type_option_cell_data.into();
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<String>,
+    {
         if !decoded_field_type.is_checkbox() {
-            return DecodedCellData::default();
+            return Ok(DecodedCellData::default());
         }
-        let cell_data = type_option_cell_data.data;
-        if cell_data == YES || cell_data == NO {
-            return DecodedCellData::from_content(cell_data);
+
+        let encoded_data = encoded_data.into();
+        if encoded_data == YES || encoded_data == NO {
+            return Ok(DecodedCellData::from_content(encoded_data));
         }
 
-        DecodedCellData::default()
+        Ok(DecodedCellData::default())
     }
 
     fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
@@ -91,10 +93,10 @@ fn string_to_bool(bool_str: &str) -> bool {
 #[cfg(test)]
 mod tests {
     use crate::services::field::type_options::checkbox_type_option::{NO, YES};
-    use crate::services::field::CheckboxTypeOption;
+
     use crate::services::field::FieldBuilder;
-    use crate::services::row::{apply_cell_data_changeset, decode_cell_data, CellDataOperation};
-    use diesel::types::IsNull::No;
+    use crate::services::row::{apply_cell_data_changeset, decode_cell_data_from_type_option_cell_data};
+
     use flowy_grid_data_model::entities::FieldType;
 
     #[test]
@@ -102,49 +104,37 @@ mod tests {
         let field_meta = FieldBuilder::from_field_type(&FieldType::Checkbox).build();
         let data = apply_cell_data_changeset("true", None, &field_meta).unwrap();
         assert_eq!(
-            decode_cell_data(data, &field_meta, &field_meta.field_type)
-                .unwrap()
-                .content,
+            decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
             YES
         );
 
         let data = apply_cell_data_changeset("1", None, &field_meta).unwrap();
         assert_eq!(
-            decode_cell_data(data, &field_meta, &field_meta.field_type)
-                .unwrap()
-                .content,
+            decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
             YES
         );
 
         let data = apply_cell_data_changeset("yes", None, &field_meta).unwrap();
         assert_eq!(
-            decode_cell_data(data, &field_meta, &field_meta.field_type)
-                .unwrap()
-                .content,
+            decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
             YES
         );
 
         let data = apply_cell_data_changeset("false", None, &field_meta).unwrap();
         assert_eq!(
-            decode_cell_data(data, &field_meta, &field_meta.field_type)
-                .unwrap()
-                .content,
+            decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
             NO
         );
 
         let data = apply_cell_data_changeset("no", None, &field_meta).unwrap();
         assert_eq!(
-            decode_cell_data(data, &field_meta, &field_meta.field_type)
-                .unwrap()
-                .content,
+            decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
             NO
         );
 
         let data = apply_cell_data_changeset("12", None, &field_meta).unwrap();
         assert_eq!(
-            decode_cell_data(data, &field_meta, &field_meta.field_type)
-                .unwrap()
-                .content,
+            decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
             NO
         );
     }

+ 210 - 140
frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs

@@ -1,7 +1,9 @@
 use crate::entities::{CellIdentifier, CellIdentifierPayload};
 use crate::impl_type_option;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData};
+use crate::services::row::{
+    CellContentChangeset, CellDataOperation, DecodedCellData, EncodedCellData, TypeOptionCellData,
+};
 use bytes::Bytes;
 use chrono::format::strftime::StrftimeItems;
 use chrono::NaiveDateTime;
@@ -90,10 +92,10 @@ impl DateTypeOption {
 
         let serde_cell_data = DateCellDataSerde::from_str(&result.unwrap().data)?;
         let date = self.decode_cell_data_from_timestamp(&serde_cell_data).content;
-        let time = serde_cell_data.time.unwrap_or("".to_owned());
+        let time = serde_cell_data.time.unwrap_or_else(|| "".to_owned());
         let timestamp = serde_cell_data.timestamp;
 
-        return Ok(DateCellData { date, time, timestamp });
+        Ok(DateCellData { date, time, timestamp })
     }
 
     fn decode_cell_data_from_timestamp(&self, serde_cell_data: &DateCellDataSerde) -> DecodedCellData {
@@ -102,7 +104,7 @@ impl DateTypeOption {
         }
 
         let cell_content = self.today_desc_from_timestamp(serde_cell_data.timestamp, &serde_cell_data.time);
-        return DecodedCellData::new(serde_cell_data.timestamp.to_string(), cell_content);
+        DecodedCellData::new(serde_cell_data.timestamp.to_string(), cell_content)
     }
 
     fn timestamp_from_utc_with_time(
@@ -131,29 +133,30 @@ impl DateTypeOption {
             }
         }
 
-        return Ok(utc.timestamp());
+        Ok(utc.timestamp())
     }
 }
 
-impl CellDataOperation<DateCellDataSerde> for DateTypeOption {
-    fn decode_cell_data<T: Into<TypeOptionCellData>>(
+impl CellDataOperation<EncodedCellData<DateCellDataSerde>, DateCellDataSerde> for DateTypeOption {
+    fn decode_cell_data<T>(
         &self,
-        type_option_cell_data: T,
+        encoded_data: T,
         decoded_field_type: &FieldType,
         _field_meta: &FieldMeta,
-    ) -> DecodedCellData {
-        let type_option_cell_data = type_option_cell_data.into();
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<EncodedCellData<DateCellDataSerde>>,
+    {
         // Return default data if the type_option_cell_data is not FieldType::DateTime.
         // It happens when switching from one field to another.
         // For example:
         // FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen.
         if !decoded_field_type.is_date() {
-            return DecodedCellData::default();
-        }
-        match DateCellDataSerde::from_str(&type_option_cell_data.data) {
-            Ok(serde_cell_data) => self.decode_cell_data_from_timestamp(&serde_cell_data),
-            Err(_) => DecodedCellData::default(),
+            return Ok(DecodedCellData::default());
         }
+
+        let encoded_data = encoded_data.into().try_into_inner()?;
+        Ok(self.decode_cell_data_from_timestamp(&encoded_data))
     }
 
     fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<DateCellDataSerde, FlowyError>
@@ -308,22 +311,26 @@ impl DateCellDataSerde {
     fn new(timestamp: i64, time: Option<String>, time_format: &TimeFormat) -> Self {
         Self {
             timestamp,
-            time: Some(time.unwrap_or(default_time_str(time_format))),
+            time: Some(time.unwrap_or_else(|| default_time_str(time_format))),
         }
     }
 
     pub(crate) fn from_timestamp(timestamp: i64, time: Option<String>) -> Self {
         Self { timestamp, time }
     }
+}
+
+impl FromStr for DateCellDataSerde {
+    type Err = FlowyError;
 
-    fn from_str(s: &str) -> FlowyResult<Self> {
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
         serde_json::from_str::<DateCellDataSerde>(s).map_err(internal_error)
     }
 }
 
 impl ToString for DateCellDataSerde {
     fn to_string(&self) -> String {
-        serde_json::to_string(&self).unwrap_or("".to_string())
+        serde_json::to_string(&self).unwrap_or_else(|_| "".to_string())
     }
 }
 
@@ -410,11 +417,11 @@ impl std::convert::From<DateCellContentChangeset> for CellContentChangeset {
 #[cfg(test)]
 mod tests {
     use crate::services::field::FieldBuilder;
-    use crate::services::field::{
-        DateCellContentChangeset, DateCellData, DateCellDataSerde, DateFormat, DateTypeOption, TimeFormat,
+    use crate::services::field::{DateCellContentChangeset, DateCellDataSerde, DateFormat, DateTypeOption, TimeFormat};
+    use crate::services::row::{
+        apply_cell_data_changeset, decode_cell_data_from_type_option_cell_data, CellDataOperation, EncodedCellData,
     };
-    use crate::services::row::{apply_cell_data_changeset, decode_cell_data, CellDataOperation, TypeOptionCellData};
-    use flowy_grid_data_model::entities::FieldType;
+    use flowy_grid_data_model::entities::{FieldMeta, FieldType};
     use strum::IntoEnumIterator;
 
     #[test]
@@ -422,9 +429,7 @@ mod tests {
         let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
         let data = apply_cell_data_changeset("1e", None, &field_meta).unwrap();
         assert_eq!(
-            decode_cell_data(data, &field_meta, &field_meta.field_type)
-                .unwrap()
-                .content,
+            decode_cell_data_from_type_option_cell_data(data, &field_meta, &field_meta.field_type).content,
             "".to_owned()
         );
     }
@@ -432,32 +437,44 @@ mod tests {
     #[test]
     fn date_description_date_format_test() {
         let mut type_option = DateTypeOption::default();
-        let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
+        let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
         for date_format in DateFormat::iter() {
             type_option.date_format = date_format;
             match date_format {
                 DateFormat::Friendly => {
                     assert_eq!(
                         "Mar 14,2022".to_owned(),
-                        type_option.decode_cell_data(data(1647251762), &field_meta).content
+                        type_option
+                            .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
+                            .unwrap()
+                            .content
                     );
                 }
                 DateFormat::US => {
                     assert_eq!(
                         "2022/03/14".to_owned(),
-                        type_option.decode_cell_data(data(1647251762), &field_meta).content
+                        type_option
+                            .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
+                            .unwrap()
+                            .content
                     );
                 }
                 DateFormat::ISO => {
                     assert_eq!(
                         "2022-03-14".to_owned(),
-                        type_option.decode_cell_data(data(1647251762), &field_meta).content
+                        type_option
+                            .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
+                            .unwrap()
+                            .content
                     );
                 }
                 DateFormat::Local => {
                     assert_eq!(
                         "2022/03/14".to_owned(),
-                        type_option.decode_cell_data(data(1647251762), &field_meta).content
+                        type_option
+                            .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
+                            .unwrap()
+                            .content
                     );
                 }
             }
@@ -467,7 +484,7 @@ mod tests {
     #[test]
     fn date_description_time_format_test() {
         let mut type_option = DateTypeOption::default();
-        let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
+        let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
         for time_format in TimeFormat::iter() {
             type_option.time_format = time_format;
             match time_format {
@@ -478,7 +495,10 @@ mod tests {
                     );
                     assert_eq!(
                         "Mar 14,2022".to_owned(),
-                        type_option.decode_cell_data(data(1647251762), &field_meta).content
+                        type_option
+                            .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
+                            .unwrap()
+                            .content
                     );
                 }
                 TimeFormat::TwelveHour => {
@@ -488,7 +508,10 @@ mod tests {
                     );
                     assert_eq!(
                         "Mar 14,2022".to_owned(),
-                        type_option.decode_cell_data(data(1647251762), &field_meta).content
+                        type_option
+                            .decode_cell_data(data(1647251762), &FieldType::DateTime, &field_meta)
+                            .unwrap()
+                            .content
                     );
                 }
             }
@@ -498,124 +521,171 @@ mod tests {
     #[test]
     fn date_description_time_format_test2() {
         let mut type_option = DateTypeOption::default();
-        let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
+        let field_type = FieldType::DateTime;
+        let field_meta = FieldBuilder::from_field_type(&field_type).build();
+
         for time_format in TimeFormat::iter() {
             type_option.time_format = time_format;
             type_option.include_time = true;
             match time_format {
                 TimeFormat::TwentyFourHour => {
-                    let changeset = DateCellContentChangeset {
-                        date: Some(1653609600.to_string()),
-                        time: None,
-                    };
-                    let result = type_option.apply_changeset(changeset, None).unwrap();
-                    let content = type_option.decode_cell_data(result, &field_meta).content;
-                    assert_eq!("May 27,2022 00:00".to_owned(), content);
-
-                    let changeset = DateCellContentChangeset {
-                        date: Some(1653609600.to_string()),
-                        time: Some("23:00".to_owned()),
-                    };
-
-                    let result = type_option.apply_changeset(changeset, None).unwrap();
-                    let content = type_option.decode_cell_data(result, &field_meta).content;
-                    assert_eq!("May 27,2022 23:00".to_owned(), content);
+                    assert_result(
+                        &type_option,
+                        DateCellContentChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: None,
+                        },
+                        &field_type,
+                        &field_meta,
+                        "May 27,2022 00:00",
+                    );
+                    assert_result(
+                        &type_option,
+                        DateCellContentChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: Some("23:00".to_owned()),
+                        },
+                        &field_type,
+                        &field_meta,
+                        "May 27,2022 23:00",
+                    );
                 }
                 TimeFormat::TwelveHour => {
-                    let changeset = DateCellContentChangeset {
-                        date: Some(1653609600.to_string()),
-                        time: None,
-                    };
-                    let result = type_option.apply_changeset(changeset, None).unwrap();
-                    let content = type_option.decode_cell_data(result, &field_meta).content;
-                    assert_eq!("May 27,2022 12:00 AM".to_owned(), content);
-
-                    let changeset = DateCellContentChangeset {
-                        date: Some(1653609600.to_string()),
-                        time: Some("".to_owned()),
-                    };
-                    let result = type_option.apply_changeset(changeset, None).unwrap();
-                    let content = type_option.decode_cell_data(result, &field_meta).content;
-                    assert_eq!("May 27,2022".to_owned(), content);
-
-                    let changeset = DateCellContentChangeset {
-                        date: Some(1653609600.to_string()),
-                        time: Some("11:23 pm".to_owned()),
-                    };
-                    let result = type_option.apply_changeset(changeset, None).unwrap();
-                    let content = type_option.decode_cell_data(result, &field_meta).content;
-                    assert_eq!("May 27,2022 11:23 PM".to_owned(), content);
+                    assert_result(
+                        &type_option,
+                        DateCellContentChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: None,
+                        },
+                        &field_type,
+                        &field_meta,
+                        "May 27,2022 12:00 AM",
+                    );
+
+                    assert_result(
+                        &type_option,
+                        DateCellContentChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: Some("".to_owned()),
+                        },
+                        &field_type,
+                        &field_meta,
+                        "May 27,2022",
+                    );
+
+                    assert_result(
+                        &type_option,
+                        DateCellContentChangeset {
+                            date: Some(1653609600.to_string()),
+                            time: Some("11:23 pm".to_owned()),
+                        },
+                        &field_type,
+                        &field_meta,
+                        "May 27,2022 11:23 PM",
+                    );
                 }
             }
         }
     }
 
-    // #[test]
-    // fn date_description_apply_changeset_test() {
-    //     let mut type_option = DateTypeOption::default();
-    //     let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
-    //     let date_timestamp = "1653609600".to_owned();
-    //
-    //     let changeset = DateCellContentChangeset {
-    //         date: Some(date_timestamp.clone()),
-    //         time: None,
-    //     };
-    //     let result = type_option.apply_changeset(changeset, None).unwrap();
-    //     let content = type_option.decode_cell_data(result.clone(), &field_meta).content;
-    //     assert_eq!(content, "May 27,2022".to_owned());
-    //
-    //     type_option.include_time = true;
-    //     let content = type_option.decode_cell_data(result, &field_meta).content;
-    //     assert_eq!(content, "May 27,2022 00:00".to_owned());
-    //
-    //     let changeset = DateCellContentChangeset {
-    //         date: Some(date_timestamp.clone()),
-    //         time: Some("1:00".to_owned()),
-    //     };
-    //     let result = type_option.apply_changeset(changeset, None).unwrap();
-    //     let content = type_option.decode_cell_data(result, &field_meta).content;
-    //     assert_eq!(content, "May 27,2022 01:00".to_owned());
-    //
-    //     let changeset = DateCellContentChangeset {
-    //         date: Some(date_timestamp),
-    //         time: Some("1:00 am".to_owned()),
-    //     };
-    //     type_option.time_format = TimeFormat::TwelveHour;
-    //     let result = type_option.apply_changeset(changeset, None).unwrap();
-    //     let content = type_option.decode_cell_data(result, &field_meta).content;
-    //     assert_eq!(content, "May 27,2022 01:00 AM".to_owned());
-    // }
-    //
-    // #[test]
-    // #[should_panic]
-    // fn date_description_apply_changeset_error_test() {
-    //     let mut type_option = DateTypeOption::default();
-    //     type_option.include_time = true;
-    //     let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
-    //     let date_timestamp = "1653609600".to_owned();
-    //
-    //     let changeset = DateCellContentChangeset {
-    //         date: Some(date_timestamp.clone()),
-    //         time: Some("1:a0".to_owned()),
-    //     };
-    //     let _ = type_option.apply_changeset(changeset, None).unwrap();
-    //
-    //     let changeset = DateCellContentChangeset {
-    //         date: Some(date_timestamp.clone()),
-    //         time: Some("1:".to_owned()),
-    //     };
-    //     let _ = type_option.apply_changeset(changeset, None).unwrap();
-    // }
-    //
-    // #[test]
-    // #[should_panic]
-    // fn date_description_invalid_data_test() {
-    //     let type_option = DateTypeOption::default();
-    //     type_option.apply_changeset("he", None).unwrap();
-    // }
-    //
-    fn data(s: i64) -> TypeOptionCellData {
-        let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(s, None)).unwrap();
-        TypeOptionCellData::new(&json, FieldType::DateTime)
+    #[test]
+    fn date_description_apply_changeset_test() {
+        let mut type_option = DateTypeOption::default();
+        let field_type = FieldType::DateTime;
+        let field_meta = FieldBuilder::from_field_type(&field_type).build();
+        let date_timestamp = "1653609600".to_owned();
+
+        assert_result(
+            &type_option,
+            DateCellContentChangeset {
+                date: Some(date_timestamp.clone()),
+                time: None,
+            },
+            &field_type,
+            &field_meta,
+            "May 27,2022",
+        );
+
+        type_option.include_time = true;
+        assert_result(
+            &type_option,
+            DateCellContentChangeset {
+                date: Some(date_timestamp.clone()),
+                time: None,
+            },
+            &field_type,
+            &field_meta,
+            "May 27,2022 00:00",
+        );
+
+        assert_result(
+            &type_option,
+            DateCellContentChangeset {
+                date: Some(date_timestamp.clone()),
+                time: Some("1:00".to_owned()),
+            },
+            &field_type,
+            &field_meta,
+            "May 27,2022 01:00",
+        );
+
+        type_option.time_format = TimeFormat::TwelveHour;
+        assert_result(
+            &type_option,
+            DateCellContentChangeset {
+                date: Some(date_timestamp),
+                time: Some("1:00 am".to_owned()),
+            },
+            &field_type,
+            &field_meta,
+            "May 27,2022 01:00 AM",
+        );
+    }
+
+    #[test]
+    #[should_panic]
+    fn date_description_apply_changeset_error_test() {
+        let mut type_option = DateTypeOption::default();
+        type_option.include_time = true;
+        let _field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
+        let date_timestamp = "1653609600".to_owned();
+
+        let changeset = DateCellContentChangeset {
+            date: Some(date_timestamp.clone()),
+            time: Some("1:a0".to_owned()),
+        };
+        let _ = type_option.apply_changeset(changeset, None).unwrap();
+
+        let changeset = DateCellContentChangeset {
+            date: Some(date_timestamp),
+            time: Some("1:".to_owned()),
+        };
+        let _ = type_option.apply_changeset(changeset, None).unwrap();
+    }
+
+    #[test]
+    #[should_panic]
+    fn date_description_invalid_data_test() {
+        let type_option = DateTypeOption::default();
+        type_option.apply_changeset("he", None).unwrap();
+    }
+
+    fn data(s: i64) -> String {
+        serde_json::to_string(&DateCellDataSerde::from_timestamp(s, None)).unwrap()
+    }
+
+    fn assert_result(
+        type_option: &DateTypeOption,
+        changeset: DateCellContentChangeset,
+        field_type: &FieldType,
+        field_meta: &FieldMeta,
+        expected: &str,
+    ) {
+        let encoded_data = EncodedCellData(Some(type_option.apply_changeset(changeset, None).unwrap()));
+        let content = type_option
+            .decode_cell_data(encoded_data, field_type, field_meta)
+            .unwrap()
+            .content;
+        assert_eq!(expected.to_owned(), content);
     }
 }

+ 145 - 174
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs

@@ -1,9 +1,9 @@
 use crate::impl_type_option;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData};
+use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData};
 use bytes::Bytes;
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
-use flowy_error::FlowyError;
+use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{
     CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
 };
@@ -76,38 +76,40 @@ pub struct NumberTypeOption {
 }
 impl_type_option!(NumberTypeOption, FieldType::Number);
 
-impl CellDataOperation<String> for NumberTypeOption {
-    fn decode_cell_data<T: Into<TypeOptionCellData>>(
+impl CellDataOperation<String, String> for NumberTypeOption {
+    fn decode_cell_data<T>(
         &self,
-        type_option_cell_data: T,
+        encoded_data: T,
         decoded_field_type: &FieldType,
         _field_meta: &FieldMeta,
-    ) -> DecodedCellData {
-        let type_option_cell_data = type_option_cell_data.into();
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<String>,
+    {
         if decoded_field_type.is_date() {
-            return DecodedCellData::default();
+            return Ok(DecodedCellData::default());
         }
 
-        let cell_data = type_option_cell_data.data;
+        let cell_data = encoded_data.into();
         match self.format {
             NumberFormat::Number => {
                 if let Ok(v) = cell_data.parse::<f64>() {
-                    return DecodedCellData::from_content(v.to_string());
+                    return Ok(DecodedCellData::from_content(v.to_string()));
                 }
 
                 if let Ok(v) = cell_data.parse::<i64>() {
-                    return DecodedCellData::from_content(v.to_string());
+                    return Ok(DecodedCellData::from_content(v.to_string()));
                 }
 
-                DecodedCellData::default()
+                Ok(DecodedCellData::default())
             }
             NumberFormat::Percent => {
                 let content = cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string());
-                DecodedCellData::from_content(content)
+                Ok(DecodedCellData::from_content(content))
             }
             _ => {
                 let content = self.money_from_str(&cell_data);
-                DecodedCellData::from_content(content)
+                Ok(DecodedCellData::from_content(content))
             }
         }
     }
@@ -616,163 +618,132 @@ fn make_strip_symbol() -> Vec<String> {
     symbols
 }
 
-// #[cfg(test)]
-// mod tests {
-//     use crate::services::field::FieldBuilder;
-//     use crate::services::field::{NumberFormat, NumberTypeOption};
-//     use crate::services::row::{CellDataOperation, TypeOptionCellData};
-//     use flowy_grid_data_model::entities::FieldType;
-//     use strum::IntoEnumIterator;
-//
-//     #[test]
-//     fn number_description_invalid_input_test() {
-//         let type_option = NumberTypeOption::default();
-//         let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
-//         assert_eq!(
-//             "".to_owned(),
-//             type_option.decode_cell_data(data(""), &field_meta).content
-//         );
-//         assert_eq!(
-//             "".to_owned(),
-//             type_option.decode_cell_data(data("abc"), &field_meta).content
-//         );
-//     }
-//
-//     #[test]
-//     fn number_description_test() {
-//         let mut type_option = NumberTypeOption::default();
-//         let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
-//         assert_eq!(type_option.strip_symbol("¥18,443"), "18443".to_owned());
-//         assert_eq!(type_option.strip_symbol("$18,443"), "18443".to_owned());
-//         assert_eq!(type_option.strip_symbol("€18.443"), "18443".to_owned());
-//
-//         for format in NumberFormat::iter() {
-//             type_option.format = format;
-//             match format {
-//                 NumberFormat::Number => {
-//                     assert_eq!(
-//                         type_option.decode_cell_data(data("18443"), &field_meta).content,
-//                         "18443".to_owned()
-//                     );
-//                 }
-//                 NumberFormat::USD => {
-//                     assert_eq!(
-//                         type_option.decode_cell_data(data("18443"), &field_meta).content,
-//                         "$18,443".to_owned()
-//                     );
-//                     assert_eq!(
-//                         type_option.decode_cell_data(data(""), &field_meta).content,
-//                         "".to_owned()
-//                     );
-//                     assert_eq!(
-//                         type_option.decode_cell_data(data("abc"), &field_meta).content,
-//                         "".to_owned()
-//                     );
-//                 }
-//                 NumberFormat::Yen => {
-//                     assert_eq!(
-//                         type_option.decode_cell_data(data("18443"), &field_meta).content,
-//                         "¥18,443".to_owned()
-//                     );
-//                 }
-//                 NumberFormat::Yuan => {
-//                     assert_eq!(
-//                         type_option.decode_cell_data(data("18443"), &field_meta).content,
-//                         "CN¥18,443".to_owned()
-//                     );
-//                 }
-//                 NumberFormat::EUR => {
-//                     assert_eq!(
-//                         type_option.decode_cell_data(data("18443"), &field_meta).content,
-//                         "€18.443".to_owned()
-//                     );
-//                 }
-//                 _ => {}
-//             }
-//         }
-//     }
-//
-//     fn data(s: &str) -> String {
-//         TypeOptionCellData::new(s, FieldType::Number).json()
-//     }
-//
-//     #[test]
-//     fn number_description_scale_test() {
-//         let mut type_option = NumberTypeOption {
-//             scale: 1,
-//             ..Default::default()
-//         };
-//         let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
-//
-//         for format in NumberFormat::iter() {
-//             type_option.format = format;
-//             match format {
-//                 NumberFormat::Number => {
-//                     assert_eq!(
-//                         type_option.decode_cell_data(data("18443"), &field_meta).content,
-//                         "18443".to_owned()
-//                     );
-//                 }
-//                 NumberFormat::USD => {
-//                     assert_eq!(
-//                         type_option.decode_cell_data(data("18443"), &field_meta).content,
-//                         "$1,844.3".to_owned()
-//                     );
-//                 }
-//                 NumberFormat::Yen => {
-//                     assert_eq!(
-//                         type_option.decode_cell_data(data("18443"), &field_meta).content,
-//                         "¥1,844.3".to_owned()
-//                     );
-//                 }
-//                 NumberFormat::EUR => {
-//                     assert_eq!(
-//                         type_option.decode_cell_data(data("18443"), &field_meta).content,
-//                         "€1.844,3".to_owned()
-//                     );
-//                 }
-//                 _ => {}
-//             }
-//         }
-//     }
-//
-//     #[test]
-//     fn number_description_sign_test() {
-//         let mut type_option = NumberTypeOption {
-//             sign_positive: false,
-//             ..Default::default()
-//         };
-//         let field_meta = FieldBuilder::from_field_type(&FieldType::Number).build();
-//
-//         for format in NumberFormat::iter() {
-//             type_option.format = format;
-//             match format {
-//                 NumberFormat::Number => {
-//                     assert_eq!(
-//                         type_option.decode_cell_data(data("18443"), &field_meta).content,
-//                         "18443".to_owned()
-//                     );
-//                 }
-//                 NumberFormat::USD => {
-//                     assert_eq!(
-//                         type_option.decode_cell_data(data("18443"), &field_meta).content,
-//                         "-$18,443".to_owned()
-//                     );
-//                 }
-//                 NumberFormat::Yen => {
-//                     assert_eq!(
-//                         type_option.decode_cell_data(data("18443"), &field_meta).content,
-//                         "-¥18,443".to_owned()
-//                     );
-//                 }
-//                 NumberFormat::EUR => {
-//                     assert_eq!(
-//                         type_option.decode_cell_data(data("18443"), &field_meta).content,
-//                         "-€18.443".to_owned()
-//                     );
-//                 }
-//                 _ => {}
-//             }
-//         }
-//     }
-// }
+#[cfg(test)]
+mod tests {
+    use crate::services::field::FieldBuilder;
+    use crate::services::field::{NumberFormat, NumberTypeOption};
+    use crate::services::row::CellDataOperation;
+    use flowy_grid_data_model::entities::{FieldMeta, FieldType};
+    use strum::IntoEnumIterator;
+
+    #[test]
+    fn number_description_invalid_input_test() {
+        let type_option = NumberTypeOption::default();
+        let field_type = FieldType::Number;
+        let field_meta = FieldBuilder::from_field_type(&field_type).build();
+        assert_equal(&type_option, "", "", &field_type, &field_meta);
+        assert_equal(&type_option, "abc", "", &field_type, &field_meta);
+    }
+
+    #[test]
+    fn number_description_test() {
+        let mut type_option = NumberTypeOption::default();
+        let field_type = FieldType::Number;
+        let field_meta = FieldBuilder::from_field_type(&field_type).build();
+        assert_eq!(type_option.strip_symbol("¥18,443"), "18443".to_owned());
+        assert_eq!(type_option.strip_symbol("$18,443"), "18443".to_owned());
+        assert_eq!(type_option.strip_symbol("€18.443"), "18443".to_owned());
+
+        for format in NumberFormat::iter() {
+            type_option.format = format;
+            match format {
+                NumberFormat::Number => {
+                    assert_equal(&type_option, "18443", "18443", &field_type, &field_meta);
+                }
+                NumberFormat::USD => {
+                    assert_equal(&type_option, "18443", "$18,443", &field_type, &field_meta);
+                    assert_equal(&type_option, "", "", &field_type, &field_meta);
+                    assert_equal(&type_option, "abc", "", &field_type, &field_meta);
+                }
+                NumberFormat::Yen => {
+                    assert_equal(&type_option, "18443", "¥18,443", &field_type, &field_meta);
+                }
+                NumberFormat::Yuan => {
+                    assert_equal(&type_option, "18443", "CN¥18,443", &field_type, &field_meta);
+                }
+                NumberFormat::EUR => {
+                    assert_equal(&type_option, "18443", "€18.443", &field_type, &field_meta);
+                }
+                _ => {}
+            }
+        }
+    }
+
+    #[test]
+    fn number_description_scale_test() {
+        let mut type_option = NumberTypeOption {
+            scale: 1,
+            ..Default::default()
+        };
+        let field_type = FieldType::Number;
+        let field_meta = FieldBuilder::from_field_type(&field_type).build();
+
+        for format in NumberFormat::iter() {
+            type_option.format = format;
+            match format {
+                NumberFormat::Number => {
+                    assert_equal(&type_option, "18443", "18443", &field_type, &field_meta);
+                }
+                NumberFormat::USD => {
+                    assert_equal(&type_option, "18443", "$1,844.3", &field_type, &field_meta);
+                }
+                NumberFormat::Yen => {
+                    assert_equal(&type_option, "18443", "¥1,844.3", &field_type, &field_meta);
+                }
+                NumberFormat::EUR => {
+                    assert_equal(&type_option, "18443", "€1.844,3", &field_type, &field_meta);
+                }
+                _ => {}
+            }
+        }
+    }
+
+    #[test]
+    fn number_description_sign_test() {
+        let mut type_option = NumberTypeOption {
+            sign_positive: false,
+            ..Default::default()
+        };
+        let field_type = FieldType::Number;
+        let field_meta = FieldBuilder::from_field_type(&field_type).build();
+
+        for format in NumberFormat::iter() {
+            type_option.format = format;
+            match format {
+                NumberFormat::Number => {
+                    assert_equal(&type_option, "18443", "18443", &field_type, &field_meta);
+                }
+                NumberFormat::USD => {
+                    assert_equal(&type_option, "18443", "-$18,443", &field_type, &field_meta);
+                }
+                NumberFormat::Yen => {
+                    assert_equal(&type_option, "18443", "-¥18,443", &field_type, &field_meta);
+                }
+                NumberFormat::EUR => {
+                    assert_equal(&type_option, "18443", "-€18.443", &field_type, &field_meta);
+                }
+                _ => {}
+            }
+        }
+    }
+
+    fn assert_equal(
+        type_option: &NumberTypeOption,
+        cell_data: &str,
+        expected_str: &str,
+        field_type: &FieldType,
+        field_meta: &FieldMeta,
+    ) {
+        assert_eq!(
+            type_option
+                .decode_cell_data(data(cell_data), field_type, field_meta)
+                .unwrap()
+                .content,
+            expected_str.to_owned()
+        );
+    }
+
+    fn data(s: &str) -> String {
+        s.to_owned()
+    }
+}

+ 166 - 124
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs

@@ -95,26 +95,30 @@ impl SelectOptionOperation for SingleSelectTypeOption {
     }
 }
 
-impl CellDataOperation<String> for SingleSelectTypeOption {
-    fn decode_cell_data<T: Into<TypeOptionCellData>>(
+impl CellDataOperation<String, String> for SingleSelectTypeOption {
+    fn decode_cell_data<T>(
         &self,
-        type_option_cell_data: T,
+        encoded_data: T,
         decoded_field_type: &FieldType,
         _field_meta: &FieldMeta,
-    ) -> DecodedCellData {
-        let type_option_cell_data = type_option_cell_data.into();
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<String>,
+    {
         if !decoded_field_type.is_select_option() {
-            return DecodedCellData::default();
+            return Ok(DecodedCellData::default());
         }
 
-        if let Some(option_id) = select_option_ids(type_option_cell_data.data).first() {
-            return match self.options.iter().find(|option| &option.id == option_id) {
+        let cell_data = encoded_data.into();
+        if let Some(option_id) = select_option_ids(cell_data).first() {
+            let data = match self.options.iter().find(|option| &option.id == option_id) {
                 None => DecodedCellData::default(),
                 Some(option) => DecodedCellData::from_content(option.name.clone()),
             };
+            Ok(data)
+        } else {
+            Ok(DecodedCellData::default())
         }
-
-        DecodedCellData::default()
     }
 
     fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
@@ -187,19 +191,21 @@ impl SelectOptionOperation for MultiSelectTypeOption {
     }
 }
 
-impl CellDataOperation<String> for MultiSelectTypeOption {
-    fn decode_cell_data<T: Into<TypeOptionCellData>>(
+impl CellDataOperation<String, String> for MultiSelectTypeOption {
+    fn decode_cell_data<T>(
         &self,
-        type_option_cell_data: T,
+        encoded_data: T,
         decoded_field_type: &FieldType,
         _field_meta: &FieldMeta,
-    ) -> DecodedCellData {
-        let type_option_cell_data = type_option_cell_data.into();
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<String>,
+    {
         if !decoded_field_type.is_select_option() {
-            return DecodedCellData::default();
+            return Ok(DecodedCellData::default());
         }
-
-        let option_ids = select_option_ids(type_option_cell_data.data);
+        let cell_data = encoded_data.into();
+        let option_ids = select_option_ids(cell_data);
         let content = self
             .options
             .iter()
@@ -208,7 +214,7 @@ impl CellDataOperation<String> for MultiSelectTypeOption {
             .collect::<Vec<String>>()
             .join(SELECTION_IDS_SEPARATOR);
 
-        DecodedCellData::from_content(content)
+        Ok(DecodedCellData::from_content(content))
     }
 
     fn apply_changeset<T>(&self, changeset: T, cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
@@ -491,108 +497,144 @@ fn make_select_context_from(cell_meta: &Option<CellMeta>, options: &[SelectOptio
     }
 }
 
-// #[cfg(test)]
-// mod tests {
-//     use crate::services::field::FieldBuilder;
-//     use crate::services::field::{
-//         MultiSelectTypeOption, MultiSelectTypeOptionBuilder, SelectOption, SelectOptionCellContentChangeset,
-//         SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR,
-//     };
-//     use crate::services::row::CellDataOperation;
-//
-//     #[test]
-//     fn single_select_test() {
-//         let google_option = SelectOption::new("Google");
-//         let facebook_option = SelectOption::new("Facebook");
-//         let twitter_option = SelectOption::new("Twitter");
-//         let single_select = SingleSelectTypeOptionBuilder::default()
-//             .option(google_option.clone())
-//             .option(facebook_option.clone())
-//             .option(twitter_option);
-//
-//         let field_meta = FieldBuilder::new(single_select)
-//             .name("Platform")
-//             .visibility(true)
-//             .build();
-//
-//         let type_option = SingleSelectTypeOption::from(&field_meta);
-//
-//         let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR);
-//         let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
-//         let cell_data = type_option.apply_changeset(data, None).unwrap();
-//         assert_eq!(
-//             type_option.decode_cell_data(cell_data, &field_meta).content,
-//             google_option.name,
-//         );
-//
-//         let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
-//         let cell_data = type_option.apply_changeset(data, None).unwrap();
-//         assert_eq!(
-//             type_option.decode_cell_data(cell_data, &field_meta).content,
-//             google_option.name,
-//         );
-//
-//         // Invalid option id
-//         let cell_data = type_option
-//             .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
-//             .unwrap();
-//         assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",);
-//
-//         // Invalid option id
-//         let cell_data = type_option
-//             .apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None)
-//             .unwrap();
-//         assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",);
-//
-//         // Invalid changeset
-//         assert!(type_option.apply_changeset("123", None).is_err());
-//     }
-//
-//     #[test]
-//     fn multi_select_test() {
-//         let google_option = SelectOption::new("Google");
-//         let facebook_option = SelectOption::new("Facebook");
-//         let twitter_option = SelectOption::new("Twitter");
-//         let multi_select = MultiSelectTypeOptionBuilder::default()
-//             .option(google_option.clone())
-//             .option(facebook_option.clone())
-//             .option(twitter_option);
-//
-//         let field_meta = FieldBuilder::new(multi_select)
-//             .name("Platform")
-//             .visibility(true)
-//             .build();
-//
-//         let type_option = MultiSelectTypeOption::from(&field_meta);
-//
-//         let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR);
-//         let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
-//         let cell_data = type_option.apply_changeset(data, None).unwrap();
-//         assert_eq!(
-//             type_option.decode_cell_data(cell_data, &field_meta).content,
-//             vec![google_option.name.clone(), facebook_option.name].join(SELECTION_IDS_SEPARATOR),
-//         );
-//
-//         let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
-//         let cell_data = type_option.apply_changeset(data, None).unwrap();
-//         assert_eq!(
-//             type_option.decode_cell_data(cell_data, &field_meta).content,
-//             google_option.name,
-//         );
-//
-//         // Invalid option id
-//         let cell_data = type_option
-//             .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
-//             .unwrap();
-//         assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",);
-//
-//         // Invalid option id
-//         let cell_data = type_option
-//             .apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None)
-//             .unwrap();
-//         assert_eq!(type_option.decode_cell_data(cell_data, &field_meta).content, "",);
-//
-//         // Invalid changeset
-//         assert!(type_option.apply_changeset("123", None).is_err());
-//     }
-// }
+#[cfg(test)]
+mod tests {
+    use crate::services::field::FieldBuilder;
+    use crate::services::field::{
+        MultiSelectTypeOption, MultiSelectTypeOptionBuilder, SelectOption, SelectOptionCellContentChangeset,
+        SingleSelectTypeOption, SingleSelectTypeOptionBuilder, SELECTION_IDS_SEPARATOR,
+    };
+    use crate::services::row::CellDataOperation;
+
+    #[test]
+    fn single_select_test() {
+        let google_option = SelectOption::new("Google");
+        let facebook_option = SelectOption::new("Facebook");
+        let twitter_option = SelectOption::new("Twitter");
+        let single_select = SingleSelectTypeOptionBuilder::default()
+            .option(google_option.clone())
+            .option(facebook_option.clone())
+            .option(twitter_option);
+
+        let field_meta = FieldBuilder::new(single_select)
+            .name("Platform")
+            .visibility(true)
+            .build();
+
+        let type_option = SingleSelectTypeOption::from(&field_meta);
+
+        let option_ids = vec![google_option.id.clone(), facebook_option.id].join(SELECTION_IDS_SEPARATOR);
+        let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
+        let cell_data = type_option.apply_changeset(data, None).unwrap();
+        assert_eq!(
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
+            google_option.name,
+        );
+
+        let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
+        let cell_data = type_option.apply_changeset(data, None).unwrap();
+        assert_eq!(
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
+            google_option.name,
+        );
+
+        // Invalid option id
+        let cell_data = type_option
+            .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
+            .unwrap();
+        assert_eq!(
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
+            "",
+        );
+
+        // Invalid option id
+        let cell_data = type_option
+            .apply_changeset(SelectOptionCellContentChangeset::from_insert("123").to_str(), None)
+            .unwrap();
+        assert_eq!(
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
+            "",
+        );
+
+        // Invalid changeset
+        assert!(type_option.apply_changeset("123", None).is_err());
+    }
+
+    #[test]
+    fn multi_select_test() {
+        let google_option = SelectOption::new("Google");
+        let facebook_option = SelectOption::new("Facebook");
+        let twitter_option = SelectOption::new("Twitter");
+        let multi_select = MultiSelectTypeOptionBuilder::default()
+            .option(google_option.clone())
+            .option(facebook_option.clone())
+            .option(twitter_option);
+
+        let field_meta = FieldBuilder::new(multi_select)
+            .name("Platform")
+            .visibility(true)
+            .build();
+
+        let type_option = MultiSelectTypeOption::from(&field_meta);
+
+        let option_ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR);
+        let data = SelectOptionCellContentChangeset::from_insert(&option_ids).to_str();
+        let cell_data = type_option.apply_changeset(data, None).unwrap();
+        assert_eq!(
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
+            vec![google_option.name.clone(), facebook_option.name].join(SELECTION_IDS_SEPARATOR),
+        );
+
+        let data = SelectOptionCellContentChangeset::from_insert(&google_option.id).to_str();
+        let cell_data = type_option.apply_changeset(data, None).unwrap();
+        assert_eq!(
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
+            google_option.name,
+        );
+
+        // Invalid option id
+        let cell_data = type_option
+            .apply_changeset(SelectOptionCellContentChangeset::from_insert("").to_str(), None)
+            .unwrap();
+        assert_eq!(
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
+            "",
+        );
+
+        // Invalid option id
+        let cell_data = type_option
+            .apply_changeset(SelectOptionCellContentChangeset::from_insert("123,456").to_str(), None)
+            .unwrap();
+        assert_eq!(
+            type_option
+                .decode_cell_data(cell_data, &field_meta.field_type, &field_meta)
+                .unwrap()
+                .content,
+            "",
+        );
+
+        // Invalid changeset
+        assert!(type_option.apply_changeset("123", None).is_err());
+    }
+}

+ 80 - 74
frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option.rs

@@ -1,16 +1,13 @@
 use crate::impl_type_option;
 use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
-use crate::services::row::{
-    decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData, TypeOptionCellData,
-};
+use crate::services::row::{decode_cell_data, CellContentChangeset, CellDataOperation, DecodedCellData};
 use bytes::Bytes;
 use flowy_derive::ProtoBuf;
-use flowy_error::FlowyError;
+use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{
     CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
 };
 use serde::{Deserialize, Serialize};
-use std::str::FromStr;
 
 #[derive(Default)]
 pub struct RichTextTypeOptionBuilder(RichTextTypeOption);
@@ -34,23 +31,25 @@ pub struct RichTextTypeOption {
 }
 impl_type_option!(RichTextTypeOption, FieldType::RichText);
 
-impl CellDataOperation<String> for RichTextTypeOption {
-    fn decode_cell_data<T: Into<TypeOptionCellData>>(
+impl CellDataOperation<String, String> for RichTextTypeOption {
+    fn decode_cell_data<T>(
         &self,
-        type_option_cell_data: T,
+        encoded_data: T,
         decoded_field_type: &FieldType,
         field_meta: &FieldMeta,
-    ) -> DecodedCellData {
-        let type_option_cell_data = type_option_cell_data.into();
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<String>,
+    {
         if decoded_field_type.is_date()
             || decoded_field_type.is_single_select()
             || decoded_field_type.is_multi_select()
             || decoded_field_type.is_number()
         {
-            let field_type = type_option_cell_data.field_type.clone();
-            decode_cell_data(type_option_cell_data, field_meta, &field_type).unwrap_or_default()
+            decode_cell_data(encoded_data, decoded_field_type, decoded_field_type, field_meta)
         } else {
-            DecodedCellData::from_content(type_option_cell_data.data)
+            let cell_data = encoded_data.into();
+            Ok(DecodedCellData::from_content(cell_data))
         }
     }
 
@@ -67,64 +66,71 @@ impl CellDataOperation<String> for RichTextTypeOption {
     }
 }
 
-// #[cfg(test)]
-// mod tests {
-//     use crate::services::field::FieldBuilder;
-//     use crate::services::field::*;
-//     use crate::services::row::{CellDataOperation, TypeOptionCellData};
-//     use flowy_grid_data_model::entities::FieldType;
-//
-//     #[test]
-//     fn text_description_test() {
-//         let type_option = RichTextTypeOption::default();
-//
-//         // date
-//         let date_time_field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
-//         let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(1647251762, None)).unwrap();
-//         let data = TypeOptionCellData::new(&json, FieldType::DateTime).json();
-//         assert_eq!(
-//             type_option.decode_cell_data(data, &date_time_field_meta).content,
-//             "Mar 14,2022".to_owned()
-//         );
-//
-//         // Single select
-//         let done_option = SelectOption::new("Done");
-//         let done_option_id = done_option.id.clone();
-//         let single_select = SingleSelectTypeOptionBuilder::default().option(done_option);
-//         let single_select_field_meta = FieldBuilder::new(single_select).build();
-//         let cell_data = TypeOptionCellData::new(&done_option_id, FieldType::SingleSelect).json();
-//         assert_eq!(
-//             type_option
-//                 .decode_cell_data(cell_data, &single_select_field_meta)
-//                 .content,
-//             "Done".to_owned()
-//         );
-//
-//         // Multiple select
-//         let google_option = SelectOption::new("Google");
-//         let facebook_option = SelectOption::new("Facebook");
-//         let ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR);
-//         let cell_data_changeset = SelectOptionCellContentChangeset::from_insert(&ids).to_str();
-//         let multi_select = MultiSelectTypeOptionBuilder::default()
-//             .option(google_option)
-//             .option(facebook_option);
-//         let multi_select_field_meta = FieldBuilder::new(multi_select).build();
-//         let multi_type_option = MultiSelectTypeOption::from(&multi_select_field_meta);
-//         let cell_data = multi_type_option.apply_changeset(cell_data_changeset, None).unwrap();
-//         assert_eq!(
-//             type_option
-//                 .decode_cell_data(cell_data, &multi_select_field_meta)
-//                 .content,
-//             "Google,Facebook".to_owned()
-//         );
-//
-//         //Number
-//         let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
-//         let number_field_meta = FieldBuilder::new(number).build();
-//         let data = TypeOptionCellData::new("18443", FieldType::Number).json();
-//         assert_eq!(
-//             type_option.decode_cell_data(data, &number_field_meta).content,
-//             "$18,443".to_owned()
-//         );
-//     }
-// }
+#[cfg(test)]
+mod tests {
+    use crate::services::field::FieldBuilder;
+    use crate::services::field::*;
+    use crate::services::row::CellDataOperation;
+    use flowy_grid_data_model::entities::FieldType;
+
+    #[test]
+    fn text_description_test() {
+        let type_option = RichTextTypeOption::default();
+
+        // date
+        let field_type = FieldType::DateTime;
+        let date_time_field_meta = FieldBuilder::from_field_type(&field_type).build();
+        let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(1647251762, None)).unwrap();
+        assert_eq!(
+            type_option
+                .decode_cell_data(json, &field_type, &date_time_field_meta)
+                .unwrap()
+                .content,
+            "Mar 14,2022".to_owned()
+        );
+
+        // Single select
+        let done_option = SelectOption::new("Done");
+        let done_option_id = done_option.id.clone();
+        let single_select = SingleSelectTypeOptionBuilder::default().option(done_option);
+        let single_select_field_meta = FieldBuilder::new(single_select).build();
+
+        assert_eq!(
+            type_option
+                .decode_cell_data(done_option_id, &FieldType::SingleSelect, &single_select_field_meta)
+                .unwrap()
+                .content,
+            "Done".to_owned()
+        );
+
+        // Multiple select
+        let google_option = SelectOption::new("Google");
+        let facebook_option = SelectOption::new("Facebook");
+        let ids = vec![google_option.id.clone(), facebook_option.id.clone()].join(SELECTION_IDS_SEPARATOR);
+        let cell_data_changeset = SelectOptionCellContentChangeset::from_insert(&ids).to_str();
+        let multi_select = MultiSelectTypeOptionBuilder::default()
+            .option(google_option)
+            .option(facebook_option);
+        let multi_select_field_meta = FieldBuilder::new(multi_select).build();
+        let multi_type_option = MultiSelectTypeOption::from(&multi_select_field_meta);
+        let cell_data = multi_type_option.apply_changeset(cell_data_changeset, None).unwrap();
+        assert_eq!(
+            type_option
+                .decode_cell_data(cell_data, &FieldType::MultiSelect, &multi_select_field_meta)
+                .unwrap()
+                .content,
+            "Google,Facebook".to_owned()
+        );
+
+        //Number
+        let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
+        let number_field_meta = FieldBuilder::new(number).build();
+        assert_eq!(
+            type_option
+                .decode_cell_data("18443".to_owned(), &FieldType::Number, &number_field_meta)
+                .unwrap()
+                .content,
+            "$18,443".to_owned()
+        );
+    }
+}

+ 81 - 36
frontend/rust-lib/flowy-grid/src/services/row/cell_data_operation.rs

@@ -1,22 +1,26 @@
 use crate::services::field::*;
-use flowy_error::FlowyError;
+use flowy_error::{ErrorCode, FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{CellMeta, FieldMeta, FieldType};
 use serde::{Deserialize, Serialize};
 use std::fmt::Formatter;
 use std::str::FromStr;
 
 pub trait CellDataOperation<D, CO: ToString> {
-    fn decode_cell_data<T: Into<TypeOptionCellData>>(
+    fn decode_cell_data<T>(
         &self,
-        data: T,
+        encoded_data: T,
         decoded_field_type: &FieldType,
         field_meta: &FieldMeta,
-    ) -> DecodedCellData;
+    ) -> FlowyResult<DecodedCellData>
+    where
+        T: Into<D>;
+
+    //
     fn apply_changeset<C: Into<CellContentChangeset>>(
         &self,
         changeset: C,
         cell_meta: Option<CellMeta>,
-    ) -> Result<CO, FlowyError>;
+    ) -> FlowyResult<CO>;
 }
 
 #[derive(Debug)]
@@ -72,14 +76,6 @@ impl std::convert::TryInto<TypeOptionCellData> for String {
     }
 }
 
-// impl std::convert::Into<TypeOptionCellData> for String {
-//     type Error = FlowyError;
-//
-//     fn try_into(self) -> Result<TypeOptionCellData, Self::Error> {
-//         TypeOptionCellData::from_str(&self)
-//     }
-// }
-
 impl TypeOptionCellData {
     pub fn new<T: ToString>(data: T, field_type: FieldType) -> Self {
         TypeOptionCellData {
@@ -142,40 +138,89 @@ pub fn apply_cell_data_changeset<T: Into<CellContentChangeset>>(
     Ok(TypeOptionCellData::new(s, field_meta.field_type.clone()).json())
 }
 
-pub fn decode_cell_data<T: TryInto<TypeOptionCellData>>(
+pub fn decode_cell_data_from_type_option_cell_data<T: TryInto<TypeOptionCellData>>(
     data: T,
     field_meta: &FieldMeta,
     field_type: &FieldType,
-) -> Option<DecodedCellData> {
+) -> DecodedCellData {
     if let Ok(type_option_cell_data) = data.try_into() {
-        let (decoded_field_type, data) = &type_option_cell_data.split();
-        let s = match field_type {
+        let (encoded_data, s_field_type) = type_option_cell_data.split();
+        decode_cell_data(encoded_data, &s_field_type, field_type, field_meta).unwrap_or_default()
+    } else {
+        DecodedCellData::default()
+    }
+}
+
+pub fn decode_cell_data<T: Into<String>>(
+    encoded_data: T,
+    s_field_type: &FieldType,
+    t_field_type: &FieldType,
+    field_meta: &FieldMeta,
+) -> FlowyResult<DecodedCellData> {
+    let encoded_data = encoded_data.into();
+    let get_cell_data = || {
+        let data = match t_field_type {
             FieldType::RichText => field_meta
-                .get_type_option_entry::<RichTextTypeOption>(field_type)?
-                .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta),
+                .get_type_option_entry::<RichTextTypeOption>(t_field_type)?
+                .decode_cell_data(encoded_data, s_field_type, field_meta),
             FieldType::Number => field_meta
-                .get_type_option_entry::<NumberTypeOption>(field_type)?
-                .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta),
+                .get_type_option_entry::<NumberTypeOption>(t_field_type)?
+                .decode_cell_data(encoded_data, s_field_type, field_meta),
             FieldType::DateTime => field_meta
-                .get_type_option_entry::<DateTypeOption>(field_type)?
-                .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta),
+                .get_type_option_entry::<DateTypeOption>(t_field_type)?
+                .decode_cell_data(encoded_data, s_field_type, field_meta),
             FieldType::SingleSelect => field_meta
-                .get_type_option_entry::<SingleSelectTypeOption>(field_type)?
-                .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta),
+                .get_type_option_entry::<SingleSelectTypeOption>(t_field_type)?
+                .decode_cell_data(encoded_data, s_field_type, field_meta),
             FieldType::MultiSelect => field_meta
-                .get_type_option_entry::<MultiSelectTypeOption>(field_type)?
-                .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta),
+                .get_type_option_entry::<MultiSelectTypeOption>(t_field_type)?
+                .decode_cell_data(encoded_data, s_field_type, field_meta),
             FieldType::Checkbox => field_meta
-                .get_type_option_entry::<CheckboxTypeOption>(field_type)?
-                .decode_cell_data(type_option_cell_data, decoded_field_type, field_meta),
+                .get_type_option_entry::<CheckboxTypeOption>(t_field_type)?
+                .decode_cell_data(encoded_data, s_field_type, field_meta),
         };
-        tracing::Span::current().record(
-            "content",
-            &format!("{:?}: {}", field_meta.field_type, s.content).as_str(),
-        );
-        Some(s)
-    } else {
-        Some(DecodedCellData::default())
+        Some(data)
+    };
+
+    match get_cell_data() {
+        Some(Ok(data)) => {
+            tracing::Span::current().record(
+                "content",
+                &format!("{:?}: {}", field_meta.field_type, data.content).as_str(),
+            );
+            Ok(data)
+        }
+        Some(Err(err)) => {
+            tracing::error!("{:?}", err);
+            Ok(DecodedCellData::default())
+        }
+        None => Ok(DecodedCellData::default()),
+    }
+}
+
+pub(crate) struct EncodedCellData<T>(pub Option<T>);
+
+impl<T> EncodedCellData<T> {
+    pub fn try_into_inner(self) -> FlowyResult<T> {
+        match self.0 {
+            None => Err(ErrorCode::InvalidData.into()),
+            Some(data) => Ok(data),
+        }
+    }
+}
+
+impl<T> std::convert::From<String> for EncodedCellData<T>
+where
+    T: FromStr<Err = FlowyError>,
+{
+    fn from(s: String) -> Self {
+        match T::from_str(&s) {
+            Ok(inner) => EncodedCellData(Some(inner)),
+            Err(e) => {
+                tracing::error!("Deserialize Cell Data failed: {}", e);
+                EncodedCellData(None)
+            }
+        }
     }
 }
 

+ 5 - 3
frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs

@@ -1,4 +1,4 @@
-use crate::services::row::decode_cell_data;
+use crate::services::row::decode_cell_data_from_type_option_cell_data;
 use flowy_error::FlowyResult;
 use flowy_grid_data_model::entities::{
     Cell, CellMeta, FieldMeta, GridBlock, GridBlockOrder, RepeatedGridBlock, Row, RowMeta, RowOrder,
@@ -31,14 +31,16 @@ pub fn make_cell_by_field_id(
     cell_meta: CellMeta,
 ) -> Option<(String, Cell)> {
     let field_meta = field_map.get(&field_id)?;
-    let (raw, content) = decode_cell_data(cell_meta.data, field_meta, &field_meta.field_type)?.split();
+    let (raw, content) =
+        decode_cell_data_from_type_option_cell_data(cell_meta.data, field_meta, &field_meta.field_type).split();
     let cell = Cell::new(&field_id, content, raw);
     Some((field_id, cell))
 }
 
 pub fn make_cell(field_id: &str, field_meta: &FieldMeta, row_meta: &RowMeta) -> Option<Cell> {
     let cell_meta = row_meta.cells.get(field_id)?.clone();
-    let (raw, content) = decode_cell_data(cell_meta.data, field_meta, &field_meta.field_type)?.split();
+    let (raw, content) =
+        decode_cell_data_from_type_option_cell_data(cell_meta.data, field_meta, &field_meta.field_type).split();
     Some(Cell::new(field_id, content, raw))
 }
 

+ 2 - 3
frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs

@@ -5,7 +5,7 @@ use flowy_grid::services::field::{
     DateCellContentChangeset, MultiSelectTypeOption, SelectOption, SelectOptionCellContentChangeset,
     SingleSelectTypeOption, SELECTION_IDS_SEPARATOR,
 };
-use flowy_grid::services::row::{decode_cell_data, CreateRowMetaBuilder};
+use flowy_grid::services::row::{decode_cell_data_from_type_option_cell_data, CreateRowMetaBuilder};
 use flowy_grid_data_model::entities::{
     CellChangeset, FieldChangesetParams, FieldType, GridBlockMeta, GridBlockMetaChangeset, RowMetaChangeset,
     TypeOptionDataEntry,
@@ -291,8 +291,7 @@ async fn grid_row_add_date_cell_test() {
     let date_field = date_field.unwrap();
     let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone();
     assert_eq!(
-        decode_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type)
-            .unwrap()
+        decode_cell_data_from_type_option_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type)
             .split()
             .1,
         "2022/03/16",

+ 28 - 0
shared-lib/flowy-grid-data-model/src/entities/grid.rs

@@ -912,6 +912,34 @@ impl FieldType {
             _ => 150,
         }
     }
+
+    pub fn is_number(&self) -> bool {
+        self == &FieldType::Number
+    }
+
+    pub fn is_text(&self) -> bool {
+        self == &FieldType::RichText
+    }
+
+    pub fn is_checkbox(&self) -> bool {
+        self == &FieldType::Checkbox
+    }
+
+    pub fn is_date(&self) -> bool {
+        self == &FieldType::DateTime
+    }
+
+    pub fn is_single_select(&self) -> bool {
+        self == &FieldType::SingleSelect
+    }
+
+    pub fn is_multi_select(&self) -> bool {
+        self == &FieldType::MultiSelect
+    }
+
+    pub fn is_select_option(&self) -> bool {
+        self == &FieldType::MultiSelect || self == &FieldType::SingleSelect
+    }
 }
 
 #[derive(Debug, Clone, Default, ProtoBuf)]

+ 9 - 14
shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs

@@ -153,27 +153,22 @@ pub fn check_pb_dart_plugin() {
             let output = Command::new("sh").arg("-c").arg("echo $PATH").output();
             let paths = String::from_utf8(output.unwrap().stdout)
                 .unwrap()
-                .split(":")
+                .split(':')
                 .map(|s| s.to_string())
                 .collect::<Vec<String>>();
 
             paths.iter().for_each(|s| msg.push_str(&format!("{}\n", s)));
 
-            match Command::new("sh").arg("-c").arg("which protoc-gen-dart").output() {
-                Ok(output) => {
-                    msg.push_str(&format!(
-                        "Installed protoc-gen-dart path: {:?}\n",
-                        String::from_utf8(output.stdout).unwrap()
-                    ));
-                }
-                Err(_) => {}
+            if let Ok(output) = Command::new("sh").arg("-c").arg("which protoc-gen-dart").output() {
+                msg.push_str(&format!(
+                    "Installed protoc-gen-dart path: {:?}\n",
+                    String::from_utf8(output.stdout).unwrap()
+                ));
             }
 
-            msg.push_str(&format!("✅ You can fix that by adding:"));
-            msg.push_str(&format!("\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n",));
-            msg.push_str(&format!(
-                "to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)"
-            ));
+            msg.push_str(&"✅ You can fix that by adding:".to_string());
+            msg.push_str(&"\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n".to_string());
+            msg.push_str(&"to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)".to_string());
             panic!("{}", msg)
         }
     }