mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: selectOption sort + fix logic in edge cases (#3109)
This commit is contained in:
parent
a5eaf15548
commit
58ecf62240
@ -791,6 +791,8 @@ class FieldInfo {
|
||||
case FieldType.Checkbox:
|
||||
case FieldType.Number:
|
||||
case FieldType.DateTime:
|
||||
case FieldType.SingleSelect:
|
||||
case FieldType.MultiSelect:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -14,7 +14,7 @@ pub fn make_plugins(
|
||||
) -> Vec<AFPlugin> {
|
||||
let store_preferences = user_session
|
||||
.upgrade()
|
||||
.and_then(|session| Some(session.get_store_preferences()))
|
||||
.map(|session| session.get_store_preferences())
|
||||
.unwrap();
|
||||
let user_plugin = flowy_user::event_map::init(user_session);
|
||||
let folder_plugin = flowy_folder2::event_map::init(folder_manager);
|
||||
|
@ -31,13 +31,12 @@ impl DatabaseLayoutDepsResolver {
|
||||
match self.database_layout {
|
||||
DatabaseLayout::Grid => (None, None),
|
||||
DatabaseLayout::Board => {
|
||||
if self
|
||||
if !self
|
||||
.database
|
||||
.lock()
|
||||
.get_fields(None)
|
||||
.into_iter()
|
||||
.find(|field| FieldType::from(field.field_type).can_be_group())
|
||||
.is_none()
|
||||
.any(|field| FieldType::from(field.field_type).can_be_group())
|
||||
{
|
||||
let select_field = self.create_select_field();
|
||||
(Some(select_field), None)
|
||||
|
@ -11,9 +11,10 @@ use flowy_error::FlowyResult;
|
||||
use crate::entities::{CheckboxFilterPB, FieldType};
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
|
||||
use crate::services::field::{
|
||||
default_order, CheckboxCellData, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
|
||||
TypeOptionCellDataFilter, TypeOptionTransform,
|
||||
CheckboxCellData, TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
|
||||
TypeOptionCellDataSerde, TypeOptionTransform,
|
||||
};
|
||||
use crate::services::sort::SortCondition;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct CheckboxTypeOption {
|
||||
@ -68,7 +69,7 @@ impl From<CheckboxTypeOption> for TypeOptionData {
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for CheckboxTypeOption {
|
||||
impl TypeOptionCellDataSerde for CheckboxTypeOption {
|
||||
fn protobuf_encode(
|
||||
&self,
|
||||
cell_data: <Self as TypeOption>::CellData,
|
||||
@ -135,16 +136,32 @@ impl TypeOptionCellDataCompare for CheckboxTypeOption {
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
match (cell_data.is_check(), other_cell_data.is_check()) {
|
||||
(true, true) => Ordering::Equal,
|
||||
(true, false) => Ordering::Greater,
|
||||
(false, true) => Ordering::Less,
|
||||
(false, false) => default_order(),
|
||||
}
|
||||
let order = cell_data.is_check().cmp(&other_cell_data.is_check());
|
||||
sort_condition.evaluate_order(order)
|
||||
}
|
||||
|
||||
fn exempt_from_cmp(&self, _: &<Self as TypeOption>::CellData) -> bool {
|
||||
false
|
||||
/// Compares two cell data using a specified sort condition and accounts for uninitialized cells.
|
||||
///
|
||||
/// This function checks if either `cell_data` or `other_cell_data` is checked (i.e., has the `is_check` property set).
|
||||
/// If the right cell is checked and the left cell isn't, the function will return `Ordering::Less`. Conversely, if the
|
||||
/// left cell is checked and the right one isn't, the function will return `Ordering::Greater`. In all other cases, it returns
|
||||
/// `Ordering::Equal`.
|
||||
fn apply_cmp_with_uninitialized(
|
||||
&self,
|
||||
cell_data: Option<&<Self as TypeOption>::CellData>,
|
||||
other_cell_data: Option<&<Self as TypeOption>::CellData>,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
match (cell_data, other_cell_data) {
|
||||
(None, Some(right_cell_data)) if right_cell_data.is_check() => {
|
||||
sort_condition.evaluate_order(Ordering::Less)
|
||||
},
|
||||
(Some(left_cell_data), None) if left_cell_data.is_check() => {
|
||||
sort_condition.evaluate_order(Ordering::Greater)
|
||||
},
|
||||
_ => Ordering::Equal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use flowy_error::{FlowyError, FlowyResult};
|
||||
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::{CellProtobufBlobParser, DecodedCellData, FromCellString};
|
||||
use crate::services::field::CELL_DATA;
|
||||
use crate::services::field::{TypeOptionCellData, CELL_DATA};
|
||||
|
||||
pub const CHECK: &str = "Yes";
|
||||
pub const UNCHECK: &str = "No";
|
||||
@ -31,6 +31,12 @@ impl CheckboxCellData {
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for CheckboxCellData {
|
||||
fn is_cell_empty(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for CheckboxCellData {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
|
@ -2,9 +2,10 @@ use crate::entities::{ChecklistCellDataPB, ChecklistFilterPB, FieldType, SelectO
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
|
||||
use crate::services::field::checklist_type_option::{ChecklistCellChangeset, ChecklistCellData};
|
||||
use crate::services::field::{
|
||||
SelectOption, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
|
||||
TypeOptionCellDataFilter, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
|
||||
SelectOption, TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
|
||||
TypeOptionCellDataSerde, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
|
||||
};
|
||||
use crate::services::sort::SortCondition;
|
||||
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
|
||||
use collab_database::rows::Cell;
|
||||
use flowy_error::FlowyResult;
|
||||
@ -32,7 +33,7 @@ impl From<ChecklistTypeOption> for TypeOptionData {
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for ChecklistTypeOption {
|
||||
impl TypeOptionCellDataSerde for ChecklistTypeOption {
|
||||
fn protobuf_encode(
|
||||
&self,
|
||||
cell_data: <Self as TypeOption>::CellData,
|
||||
@ -191,6 +192,7 @@ impl TypeOptionCellDataCompare for ChecklistTypeOption {
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
_sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
let left = cell_data.percentage_complete();
|
||||
let right = other_cell_data.percentage_complete();
|
||||
@ -202,10 +204,6 @@ impl TypeOptionCellDataCompare for ChecklistTypeOption {
|
||||
Ordering::Equal
|
||||
}
|
||||
}
|
||||
|
||||
fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool {
|
||||
cell_data.selected_option_ids.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionTransform for ChecklistTypeOption {}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::{FromCellChangeset, ToCellChangeset};
|
||||
use crate::services::field::{SelectOption, CELL_DATA};
|
||||
use crate::services::field::{SelectOption, TypeOptionCellData, CELL_DATA};
|
||||
use collab::core::any_map::AnyMapExtension;
|
||||
use collab_database::rows::{new_cell_builder, Cell};
|
||||
use flowy_error::{internal_error, FlowyResult};
|
||||
@ -19,6 +19,12 @@ impl ToString for ChecklistCellData {
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for ChecklistCellData {
|
||||
fn is_cell_empty(&self) -> bool {
|
||||
self.selected_option_ids.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl ChecklistCellData {
|
||||
pub fn selected_options(&self) -> Vec<SelectOption> {
|
||||
self
|
||||
|
@ -9,7 +9,8 @@ mod tests {
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
|
||||
use crate::services::field::{
|
||||
DateCellChangeset, DateFormat, DateTypeOption, FieldBuilder, TimeFormat, TypeOptionCellData,
|
||||
DateCellChangeset, DateFormat, DateTypeOption, FieldBuilder, TimeFormat,
|
||||
TypeOptionCellDataSerde,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
@ -2,9 +2,10 @@ use crate::entities::{DateCellDataPB, DateFilterPB, FieldType};
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
|
||||
use crate::services::field::{
|
||||
default_order, DateCellChangeset, DateCellData, DateCellDataWrapper, DateFormat, TimeFormat,
|
||||
TypeOption, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
|
||||
TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionCellDataSerde,
|
||||
TypeOptionTransform,
|
||||
};
|
||||
use crate::services::sort::SortCondition;
|
||||
use chrono::format::strftime::StrftimeItems;
|
||||
use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, NaiveTime, Offset, TimeZone};
|
||||
use chrono_tz::Tz;
|
||||
@ -80,7 +81,7 @@ impl From<DateTypeOption> for TypeOptionData {
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for DateTypeOption {
|
||||
impl TypeOptionCellDataSerde for DateTypeOption {
|
||||
fn protobuf_encode(
|
||||
&self,
|
||||
cell_data: <Self as TypeOption>::CellData,
|
||||
@ -306,16 +307,16 @@ impl TypeOptionCellDataCompare for DateTypeOption {
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
match (cell_data.timestamp, other_cell_data.timestamp) {
|
||||
(Some(left), Some(right)) => left.cmp(&right),
|
||||
(Some(_), None) => Ordering::Greater,
|
||||
(None, Some(_)) => Ordering::Less,
|
||||
(Some(left), Some(right)) => {
|
||||
let order = left.cmp(&right);
|
||||
sort_condition.evaluate_order(order)
|
||||
},
|
||||
(Some(_), None) => Ordering::Less,
|
||||
(None, Some(_)) => Ordering::Greater,
|
||||
(None, None) => default_order(),
|
||||
}
|
||||
}
|
||||
|
||||
fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool {
|
||||
cell_data.timestamp.is_none()
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ use crate::entities::{DateCellDataPB, FieldType};
|
||||
use crate::services::cell::{
|
||||
CellProtobufBlobParser, DecodedCellData, FromCellChangeset, FromCellString, ToCellChangeset,
|
||||
};
|
||||
use crate::services::field::CELL_DATA;
|
||||
use crate::services::field::{TypeOptionCellData, CELL_DATA};
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct DateCellChangeset {
|
||||
@ -62,6 +62,12 @@ impl DateCellData {
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for DateCellData {
|
||||
fn is_cell_empty(&self) -> bool {
|
||||
self.timestamp.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Cell> for DateCellData {
|
||||
fn from(cell: &Cell) -> Self {
|
||||
let timestamp = cell
|
||||
|
@ -18,8 +18,9 @@ use crate::services::field::type_options::number_type_option::format::*;
|
||||
use crate::services::field::type_options::util::ProtobufStr;
|
||||
use crate::services::field::{
|
||||
NumberCellFormat, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
|
||||
TypeOptionCellDataFilter, TypeOptionTransform, CELL_DATA,
|
||||
TypeOptionCellDataFilter, TypeOptionCellDataSerde, TypeOptionTransform, CELL_DATA,
|
||||
};
|
||||
use crate::services::sort::SortCondition;
|
||||
|
||||
// Number
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@ -33,6 +34,12 @@ pub struct NumberTypeOption {
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct NumberCellData(pub String);
|
||||
|
||||
impl TypeOptionCellData for NumberCellData {
|
||||
fn is_cell_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Cell> for NumberCellData {
|
||||
fn from(cell: &Cell) -> Self {
|
||||
Self(cell.get_str_value(CELL_DATA).unwrap_or_default())
|
||||
@ -95,7 +102,7 @@ impl From<NumberTypeOption> for TypeOptionData {
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for NumberTypeOption {
|
||||
impl TypeOptionCellDataSerde for NumberTypeOption {
|
||||
fn protobuf_encode(
|
||||
&self,
|
||||
cell_data: <Self as TypeOption>::CellData,
|
||||
@ -244,27 +251,41 @@ impl TypeOptionCellDataFilter for NumberTypeOption {
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataCompare for NumberTypeOption {
|
||||
/// Compares two cell data using a specified sort condition.
|
||||
///
|
||||
/// The function checks if either `cell_data` or `other_cell_data` is empty (using the `is_empty` method) and:
|
||||
/// - If both are empty, it returns `Ordering::Equal`.
|
||||
/// - If only the left cell is empty, it returns `Ordering::Greater`.
|
||||
/// - If only the right cell is empty, it returns `Ordering::Less`.
|
||||
/// - If neither is empty, the cell data is converted into `NumberCellFormat` and compared based on the decimal value.
|
||||
///
|
||||
fn apply_cmp(
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
let left = NumberCellFormat::from_format_str(&cell_data.0, &self.format);
|
||||
let right = NumberCellFormat::from_format_str(&other_cell_data.0, &self.format);
|
||||
match (left, right) {
|
||||
(Ok(left), Ok(right)) => {
|
||||
return left.decimal().cmp(right.decimal());
|
||||
match (cell_data.is_cell_empty(), other_cell_data.is_cell_empty()) {
|
||||
(true, true) => Ordering::Equal,
|
||||
(true, false) => Ordering::Greater,
|
||||
(false, true) => Ordering::Less,
|
||||
(false, false) => {
|
||||
let left = NumberCellFormat::from_format_str(&cell_data.0, &self.format);
|
||||
let right = NumberCellFormat::from_format_str(&other_cell_data.0, &self.format);
|
||||
match (left, right) {
|
||||
(Ok(left), Ok(right)) => {
|
||||
let order = left.decimal().cmp(right.decimal());
|
||||
sort_condition.evaluate_order(order)
|
||||
},
|
||||
(Ok(_), Err(_)) => Ordering::Less,
|
||||
(Err(_), Ok(_)) => Ordering::Greater,
|
||||
(Err(_), Err(_)) => Ordering::Equal,
|
||||
}
|
||||
},
|
||||
(Ok(_), Err(_)) => Ordering::Greater,
|
||||
(Err(_), Ok(_)) => Ordering::Less,
|
||||
(Err(_), Err(_)) => Ordering::Equal,
|
||||
}
|
||||
}
|
||||
|
||||
fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool {
|
||||
cell_data.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::default::Default for NumberTypeOption {
|
||||
fn default() -> Self {
|
||||
let format = NumberFormat::default();
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::cmp::{min, Ordering};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use collab::core::any_map::AnyMapExtension;
|
||||
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
|
||||
@ -11,9 +11,10 @@ use crate::entities::{FieldType, SelectOptionCellDataPB, SelectOptionFilterPB};
|
||||
use crate::services::cell::CellDataChangeset;
|
||||
use crate::services::field::{
|
||||
default_order, SelectOption, SelectOptionCellChangeset, SelectOptionIds,
|
||||
SelectTypeOptionSharedAction, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
|
||||
TypeOptionCellDataFilter,
|
||||
SelectTypeOptionSharedAction, TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
|
||||
TypeOptionCellDataSerde,
|
||||
};
|
||||
use crate::services::sort::SortCondition;
|
||||
|
||||
// Multiple select
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
@ -47,7 +48,7 @@ impl From<MultiSelectTypeOption> for TypeOptionData {
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for MultiSelectTypeOption {
|
||||
impl TypeOptionCellDataSerde for MultiSelectTypeOption {
|
||||
fn protobuf_encode(
|
||||
&self,
|
||||
cell_data: <Self as TypeOption>::CellData,
|
||||
@ -136,35 +137,46 @@ impl TypeOptionCellDataFilter for MultiSelectTypeOption {
|
||||
}
|
||||
|
||||
impl TypeOptionCellDataCompare for MultiSelectTypeOption {
|
||||
/// Orders two cell values to ensure non-empty cells are moved to the front and empty ones to the back.
|
||||
///
|
||||
/// This function compares the two provided cell values (`left` and `right`) to determine their
|
||||
/// relative ordering:
|
||||
///
|
||||
/// - If both cells are empty (`None`), they are considered equal.
|
||||
/// - If the left cell is empty and the right is not, the left cell is ordered to come after the right.
|
||||
/// - If the right cell is empty and the left is not, the left cell is ordered to come before the right.
|
||||
/// - If both cells are non-empty, they are ordered based on their names. If there is an additional sort condition,
|
||||
/// this condition will further evaluate their order.
|
||||
///
|
||||
fn apply_cmp(
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
for i in 0..min(cell_data.len(), other_cell_data.len()) {
|
||||
let order = match (
|
||||
cell_data
|
||||
.get(i)
|
||||
.and_then(|id| self.options.iter().find(|option| &option.id == id)),
|
||||
other_cell_data
|
||||
.get(i)
|
||||
.and_then(|id| self.options.iter().find(|option| &option.id == id)),
|
||||
) {
|
||||
(Some(left), Some(right)) => left.name.cmp(&right.name),
|
||||
(Some(_), None) => Ordering::Greater,
|
||||
(None, Some(_)) => Ordering::Less,
|
||||
(None, None) => default_order(),
|
||||
};
|
||||
match cell_data.len().cmp(&other_cell_data.len()) {
|
||||
Ordering::Equal => {
|
||||
for (left_id, right_id) in cell_data.iter().zip(other_cell_data.iter()) {
|
||||
let left = self.options.iter().find(|option| &option.id == left_id);
|
||||
let right = self.options.iter().find(|option| &option.id == right_id);
|
||||
let order = match (left, right) {
|
||||
(None, None) => Ordering::Equal,
|
||||
(None, Some(_)) => Ordering::Greater,
|
||||
(Some(_), None) => Ordering::Less,
|
||||
(Some(left_option), Some(right_option)) => {
|
||||
let name_order = left_option.name.cmp(&right_option.name);
|
||||
sort_condition.evaluate_order(name_order)
|
||||
},
|
||||
};
|
||||
|
||||
if order.is_ne() {
|
||||
return order;
|
||||
}
|
||||
if order.is_ne() {
|
||||
return order;
|
||||
}
|
||||
}
|
||||
default_order()
|
||||
},
|
||||
order => sort_condition.evaluate_order(order),
|
||||
}
|
||||
default_order()
|
||||
}
|
||||
|
||||
fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool {
|
||||
cell_data.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ use flowy_error::FlowyResult;
|
||||
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::{DecodedCellData, FromCellString};
|
||||
use crate::services::field::CELL_DATA;
|
||||
use crate::services::field::{TypeOptionCellData, CELL_DATA};
|
||||
|
||||
pub const SELECTION_IDS_SEPARATOR: &str = ",";
|
||||
|
||||
@ -32,6 +32,12 @@ impl SelectOptionIds {
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for SelectOptionIds {
|
||||
fn is_cell_empty(&self) -> bool {
|
||||
self.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromCellString for SelectOptionIds {
|
||||
fn from_cell_str(s: &str) -> FlowyResult<Self>
|
||||
where
|
||||
|
@ -13,7 +13,7 @@ use crate::services::field::selection_type_option::type_option_transform::Select
|
||||
use crate::services::field::{
|
||||
make_selected_options, CheckboxCellData, MultiSelectTypeOption, SelectOption,
|
||||
SelectOptionCellData, SelectOptionColor, SelectOptionIds, SingleSelectTypeOption, TypeOption,
|
||||
TypeOptionCellData, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
|
||||
TypeOptionCellDataSerde, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
|
||||
};
|
||||
|
||||
/// Defines the shared actions used by SingleSelect or Multi-Select.
|
||||
@ -122,7 +122,8 @@ where
|
||||
|
||||
impl<T> CellDataDecoder for T
|
||||
where
|
||||
T: SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds> + TypeOptionCellData,
|
||||
T:
|
||||
SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds> + TypeOptionCellDataSerde,
|
||||
{
|
||||
fn decode_cell(
|
||||
&self,
|
||||
|
@ -1,12 +1,13 @@
|
||||
use crate::entities::{FieldType, SelectOptionCellDataPB, SelectOptionFilterPB};
|
||||
use crate::services::cell::CellDataChangeset;
|
||||
use crate::services::field::{
|
||||
default_order, SelectOption, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
|
||||
TypeOptionCellDataFilter,
|
||||
default_order, SelectOption, TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
|
||||
TypeOptionCellDataSerde,
|
||||
};
|
||||
use crate::services::field::{
|
||||
SelectOptionCellChangeset, SelectOptionIds, SelectTypeOptionSharedAction,
|
||||
};
|
||||
use crate::services::sort::SortCondition;
|
||||
use collab::core::any_map::AnyMapExtension;
|
||||
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
|
||||
use collab_database::rows::Cell;
|
||||
@ -46,7 +47,7 @@ impl From<SingleSelectTypeOption> for TypeOptionData {
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for SingleSelectTypeOption {
|
||||
impl TypeOptionCellDataSerde for SingleSelectTypeOption {
|
||||
fn protobuf_encode(
|
||||
&self,
|
||||
cell_data: <Self as TypeOption>::CellData,
|
||||
@ -131,6 +132,7 @@ impl TypeOptionCellDataCompare for SingleSelectTypeOption {
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
match (
|
||||
cell_data
|
||||
@ -140,16 +142,15 @@ impl TypeOptionCellDataCompare for SingleSelectTypeOption {
|
||||
.first()
|
||||
.and_then(|id| self.options.iter().find(|option| &option.id == id)),
|
||||
) {
|
||||
(Some(left), Some(right)) => left.name.cmp(&right.name),
|
||||
(Some(_), None) => Ordering::Greater,
|
||||
(None, Some(_)) => Ordering::Less,
|
||||
(Some(left), Some(right)) => {
|
||||
let order = left.name.cmp(&right.name);
|
||||
sort_condition.evaluate_order(order)
|
||||
},
|
||||
(Some(_), None) => Ordering::Less,
|
||||
(None, Some(_)) => Ordering::Greater,
|
||||
(None, None) => default_order(),
|
||||
}
|
||||
}
|
||||
|
||||
fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool {
|
||||
cell_data.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -222,6 +223,6 @@ mod tests {
|
||||
// delete
|
||||
let changeset = SelectOptionCellChangeset::from_delete_options(option_ids);
|
||||
let select_option_ids = single_select.apply_changeset(changeset, None).unwrap().1;
|
||||
assert!(select_option_ids.is_empty());
|
||||
assert!(select_option_ids.is_cell_empty());
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,9 @@ use crate::services::cell::{
|
||||
use crate::services::field::type_options::util::ProtobufStr;
|
||||
use crate::services::field::{
|
||||
TypeOption, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
|
||||
TypeOptionTransform, CELL_DATA,
|
||||
TypeOptionCellDataSerde, TypeOptionTransform, CELL_DATA,
|
||||
};
|
||||
use crate::services::sort::SortCondition;
|
||||
|
||||
/// For the moment, the `RichTextTypeOptionPB` is empty. The `data` property is not
|
||||
/// used yet.
|
||||
@ -85,7 +86,7 @@ impl TypeOptionTransform for RichTextTypeOption {
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for RichTextTypeOption {
|
||||
impl TypeOptionCellDataSerde for RichTextTypeOption {
|
||||
fn protobuf_encode(
|
||||
&self,
|
||||
cell_data: <Self as TypeOption>::CellData,
|
||||
@ -152,12 +153,17 @@ impl TypeOptionCellDataCompare for RichTextTypeOption {
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
cell_data.0.cmp(&other_cell_data.0)
|
||||
}
|
||||
|
||||
fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool {
|
||||
cell_data.0.trim().is_empty()
|
||||
match (cell_data.is_cell_empty(), other_cell_data.is_cell_empty()) {
|
||||
(true, true) => Ordering::Equal,
|
||||
(true, false) => Ordering::Greater,
|
||||
(false, true) => Ordering::Less,
|
||||
(false, false) => {
|
||||
let order = cell_data.0.cmp(&other_cell_data.0);
|
||||
sort_condition.evaluate_order(order)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,6 +227,12 @@ impl std::ops::Deref for StrCellData {
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for StrCellData {
|
||||
fn is_cell_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Cell> for StrCellData {
|
||||
fn from(cell: &Cell) -> Self {
|
||||
Self(cell.get_str_value("data").unwrap_or_default())
|
||||
|
@ -20,6 +20,7 @@ use crate::services::field::{
|
||||
RichTextTypeOption, SingleSelectTypeOption, TimeFormat, URLTypeOption,
|
||||
};
|
||||
use crate::services::filter::FromFilterString;
|
||||
use crate::services::sort::SortCondition;
|
||||
|
||||
pub trait TypeOption {
|
||||
/// `CellData` represents as the decoded model for current type option. Each of them impl the
|
||||
@ -32,7 +33,7 @@ pub trait TypeOption {
|
||||
///
|
||||
/// Uses `StrCellData` for any `TypeOption` if their cell data is pure `String`.
|
||||
///
|
||||
type CellData: ToString + Default + Send + Sync + Clone + Debug + 'static;
|
||||
type CellData: TypeOptionCellData + ToString + Default + Send + Sync + Clone + Debug + 'static;
|
||||
|
||||
/// Represents as the corresponding field type cell changeset.
|
||||
/// The changeset must implements the `FromCellChangesetString` and the `ToCellChangesetString` trait.
|
||||
@ -52,8 +53,11 @@ pub trait TypeOption {
|
||||
/// Represents as the filter configuration for this type option.
|
||||
type CellFilter: FromFilterString + Send + Sync + 'static;
|
||||
}
|
||||
|
||||
pub trait TypeOptionCellData: TypeOption {
|
||||
/// This trait providing serialization and deserialization methods for cell data.
|
||||
///
|
||||
/// This trait ensures that a type which implements both `TypeOption` and `TypeOptionCellDataSerde` can
|
||||
/// be converted to and from a corresponding `Protobuf struct`, and can be parsed from an opaque [Cell] structure.
|
||||
pub trait TypeOptionCellDataSerde: TypeOption {
|
||||
/// Encode the cell data into corresponding `Protobuf struct`.
|
||||
/// For example:
|
||||
/// FieldType::URL => URLCellDataPB
|
||||
@ -69,6 +73,18 @@ pub trait TypeOptionCellData: TypeOption {
|
||||
fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData>;
|
||||
}
|
||||
|
||||
/// This trait that provides methods to extend the [TypeOption::CellData] functionalities.
|
||||
///
|
||||
pub trait TypeOptionCellData {
|
||||
/// Checks if the cell content is considered empty.
|
||||
///
|
||||
/// Even if a cell is initialized, its content might still be considered empty
|
||||
/// based on certain criteria. e.g. empty text, date, select option, etc.
|
||||
fn is_cell_empty(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TypeOptionTransform: TypeOption {
|
||||
/// Returns true if the current `TypeOption` provides custom type option transformation
|
||||
fn transformable(&self) -> bool {
|
||||
@ -127,13 +143,28 @@ pub fn default_order() -> Ordering {
|
||||
}
|
||||
|
||||
pub trait TypeOptionCellDataCompare: TypeOption {
|
||||
/// Compares the cell contents of two cells that are both not
|
||||
/// None. However, the cell contents might still be empty
|
||||
fn apply_cmp(
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering;
|
||||
|
||||
fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool;
|
||||
/// Compares the two cells where one of the cells is None
|
||||
fn apply_cmp_with_uninitialized(
|
||||
&self,
|
||||
cell_data: Option<&<Self as TypeOption>::CellData>,
|
||||
other_cell_data: Option<&<Self as TypeOption>::CellData>,
|
||||
_sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
match (cell_data, other_cell_data) {
|
||||
(None, Some(cell_data)) if !cell_data.is_cell_empty() => Ordering::Greater,
|
||||
(Some(cell_data), None) if !cell_data.is_cell_empty() => Ordering::Less,
|
||||
_ => Ordering::Equal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_option_data_from_pb_or_default<T: Into<Bytes>>(
|
||||
|
@ -16,8 +16,8 @@ use crate::services::cell::{
|
||||
use crate::services::field::checklist_type_option::ChecklistTypeOption;
|
||||
use crate::services::field::{
|
||||
CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RichTextTypeOption,
|
||||
SingleSelectTypeOption, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
|
||||
TypeOptionCellDataFilter, TypeOptionTransform, URLTypeOption,
|
||||
SingleSelectTypeOption, TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
|
||||
TypeOptionCellDataSerde, TypeOptionTransform, URLTypeOption,
|
||||
};
|
||||
use crate::services::sort::SortCondition;
|
||||
|
||||
@ -48,8 +48,8 @@ pub trait TypeOptionCellDataHandler: Send + Sync + 'static {
|
||||
|
||||
fn handle_cell_compare(
|
||||
&self,
|
||||
left_cell: &Cell,
|
||||
right_cell: &Cell,
|
||||
left_cell: Option<&Cell>,
|
||||
right_cell: Option<&Cell>,
|
||||
field: &Field,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering;
|
||||
@ -105,7 +105,7 @@ where
|
||||
T: TypeOption
|
||||
+ CellDataDecoder
|
||||
+ CellDataChangeset
|
||||
+ TypeOptionCellData
|
||||
+ TypeOptionCellDataSerde
|
||||
+ TypeOptionTransform
|
||||
+ TypeOptionCellDataFilter
|
||||
+ TypeOptionCellDataCompare
|
||||
@ -208,7 +208,7 @@ where
|
||||
T: TypeOption
|
||||
+ CellDataDecoder
|
||||
+ CellDataChangeset
|
||||
+ TypeOptionCellData
|
||||
+ TypeOptionCellDataSerde
|
||||
+ TypeOptionTransform
|
||||
+ TypeOptionCellDataFilter
|
||||
+ TypeOptionCellDataCompare
|
||||
@ -241,32 +241,61 @@ where
|
||||
Ok(cell)
|
||||
}
|
||||
|
||||
/// Compares two cell data values given their optional references, field information, and sorting condition.
|
||||
///
|
||||
/// This function is designed to handle the comparison of cells that might not be initialized. The cells are
|
||||
/// first decoded based on the provided field type, and then compared according to the specified sort condition.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `left_cell`: An optional reference to the left cell's data.
|
||||
/// - `right_cell`: An optional reference to the right cell's data.
|
||||
/// - `field`: A reference to the field information, which includes details about the field type.
|
||||
/// - `sort_condition`: The condition that dictates the sort order based on the results of the comparison.
|
||||
///
|
||||
/// # Returns
|
||||
/// An `Ordering` indicating:
|
||||
/// - `Ordering::Equal` if both cells are `None` or if their decoded values are equal.
|
||||
/// - `Ordering::Less` or `Ordering::Greater` based on the `apply_cmp_with_uninitialized` or `apply_cmp`
|
||||
/// method results and the specified `sort_condition`.
|
||||
///
|
||||
/// # Note
|
||||
/// - If only one of the cells is `None`, the other cell is decoded, and the comparison is made using
|
||||
/// the `apply_cmp_with_uninitialized` method.
|
||||
/// - If both cells are present, they are decoded, and the comparison is made using the `apply_cmp` method.
|
||||
fn handle_cell_compare(
|
||||
&self,
|
||||
left_cell: &Cell,
|
||||
right_cell: &Cell,
|
||||
left_cell: Option<&Cell>,
|
||||
right_cell: Option<&Cell>,
|
||||
field: &Field,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
let field_type = FieldType::from(field.field_type);
|
||||
let left = self
|
||||
.get_decoded_cell_data(left_cell, &field_type, field)
|
||||
.unwrap_or_default();
|
||||
let right = self
|
||||
.get_decoded_cell_data(right_cell, &field_type, field)
|
||||
.unwrap_or_default();
|
||||
|
||||
match (self.exempt_from_cmp(&left), self.exempt_from_cmp(&right)) {
|
||||
(true, true) => Ordering::Equal,
|
||||
(true, false) => Ordering::Greater,
|
||||
(false, true) => Ordering::Less,
|
||||
(false, false) => {
|
||||
let order = self.apply_cmp(&left, &right);
|
||||
// The order is calculated by Ascending. So reverse the order if the SortCondition is descending.
|
||||
match sort_condition {
|
||||
SortCondition::Ascending => order,
|
||||
SortCondition::Descending => order.reverse(),
|
||||
}
|
||||
match (left_cell, right_cell) {
|
||||
(None, None) => Ordering::Equal,
|
||||
(None, Some(right_cell)) => {
|
||||
let right_cell_data = self
|
||||
.get_decoded_cell_data(right_cell, &field_type, field)
|
||||
.unwrap_or_default();
|
||||
|
||||
self.apply_cmp_with_uninitialized(None, Some(right_cell_data).as_ref(), sort_condition)
|
||||
},
|
||||
(Some(left_cell), None) => {
|
||||
let left_cell_data = self
|
||||
.get_decoded_cell_data(left_cell, &field_type, field)
|
||||
.unwrap_or_default();
|
||||
|
||||
self.apply_cmp_with_uninitialized(Some(left_cell_data).as_ref(), None, sort_condition)
|
||||
},
|
||||
(Some(left_cell), Some(right_cell)) => {
|
||||
let left_cell_data: <T as TypeOption>::CellData = self
|
||||
.get_decoded_cell_data(left_cell, &field_type, field)
|
||||
.unwrap_or_default();
|
||||
let right_cell_data = self
|
||||
.get_decoded_cell_data(right_cell, &field_type, field)
|
||||
.unwrap_or_default();
|
||||
|
||||
self.apply_cmp(&left_cell_data, &right_cell_data, sort_condition)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
use crate::entities::{FieldType, TextFilterPB, URLCellDataPB};
|
||||
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
|
||||
use crate::services::field::{
|
||||
TypeOption, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
|
||||
TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionCellDataSerde,
|
||||
TypeOptionTransform, URLCellData,
|
||||
};
|
||||
use crate::services::sort::SortCondition;
|
||||
|
||||
use collab::core::any_map::AnyMapExtension;
|
||||
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
|
||||
@ -46,7 +47,7 @@ impl From<URLTypeOption> for TypeOptionData {
|
||||
|
||||
impl TypeOptionTransform for URLTypeOption {}
|
||||
|
||||
impl TypeOptionCellData for URLTypeOption {
|
||||
impl TypeOptionCellDataSerde for URLTypeOption {
|
||||
fn protobuf_encode(
|
||||
&self,
|
||||
cell_data: <Self as TypeOption>::CellData,
|
||||
@ -123,12 +124,19 @@ impl TypeOptionCellDataCompare for URLTypeOption {
|
||||
&self,
|
||||
cell_data: &<Self as TypeOption>::CellData,
|
||||
other_cell_data: &<Self as TypeOption>::CellData,
|
||||
sort_condition: SortCondition,
|
||||
) -> Ordering {
|
||||
cell_data.data.cmp(&other_cell_data.data)
|
||||
}
|
||||
|
||||
fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool {
|
||||
cell_data.data.is_empty()
|
||||
let is_left_empty = cell_data.data.is_empty();
|
||||
let is_right_empty = other_cell_data.data.is_empty();
|
||||
match (is_left_empty, is_right_empty) {
|
||||
(true, true) => Ordering::Equal,
|
||||
(true, false) => Ordering::Greater,
|
||||
(false, true) => Ordering::Less,
|
||||
(false, false) => {
|
||||
let order = cell_data.data.cmp(&other_cell_data.data);
|
||||
sort_condition.evaluate_order(order)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ use flowy_error::{internal_error, FlowyResult};
|
||||
|
||||
use crate::entities::{FieldType, URLCellDataPB};
|
||||
use crate::services::cell::{CellProtobufBlobParser, DecodedCellData, FromCellString};
|
||||
use crate::services::field::CELL_DATA;
|
||||
use crate::services::field::{TypeOptionCellData, CELL_DATA};
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct URLCellData {
|
||||
@ -28,6 +28,12 @@ impl URLCellData {
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeOptionCellData for URLCellData {
|
||||
fn is_cell_empty(&self) -> bool {
|
||||
self.data.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Cell> for URLCellData {
|
||||
fn from(cell: &Cell) -> Self {
|
||||
let url = cell.get_str_value("url").unwrap_or_default();
|
||||
|
@ -246,46 +246,20 @@ fn cmp_row(
|
||||
.find(|field_rev| field_rev.id == sort.field_id)
|
||||
{
|
||||
None => default_order(),
|
||||
Some(field_rev) => match (
|
||||
Some(field_rev) => cmp_cell(
|
||||
left.cells.get(&sort.field_id),
|
||||
right.cells.get(&sort.field_id),
|
||||
) {
|
||||
(Some(left_cell), Some(right_cell)) => cmp_cell(
|
||||
left_cell,
|
||||
right_cell,
|
||||
field_rev,
|
||||
field_type,
|
||||
cell_data_cache,
|
||||
sort.condition,
|
||||
),
|
||||
(Some(_), None) => {
|
||||
if field_type.is_checkbox() {
|
||||
match sort.condition {
|
||||
SortCondition::Ascending => Ordering::Greater,
|
||||
SortCondition::Descending => Ordering::Less,
|
||||
}
|
||||
} else {
|
||||
Ordering::Less
|
||||
}
|
||||
},
|
||||
(None, Some(_)) => {
|
||||
if field_type.is_checkbox() {
|
||||
match sort.condition {
|
||||
SortCondition::Ascending => Ordering::Less,
|
||||
SortCondition::Descending => Ordering::Greater,
|
||||
}
|
||||
} else {
|
||||
Ordering::Greater
|
||||
}
|
||||
},
|
||||
_ => default_order(),
|
||||
},
|
||||
field_rev,
|
||||
field_type,
|
||||
cell_data_cache,
|
||||
sort.condition,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn cmp_cell(
|
||||
left_cell: &Cell,
|
||||
right_cell: &Cell,
|
||||
left_cell: Option<&Cell>,
|
||||
right_cell: Option<&Cell>,
|
||||
field: &Arc<Field>,
|
||||
field_type: FieldType,
|
||||
cell_data_cache: &CellCache,
|
||||
@ -306,6 +280,7 @@ fn cmp_cell(
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
enum SortEvent {
|
||||
SortDidChanged,
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use anyhow::bail;
|
||||
use collab::core::any_map::AnyMapExtension;
|
||||
use collab_database::rows::RowId;
|
||||
@ -67,6 +69,16 @@ impl SortCondition {
|
||||
pub fn value(&self) -> i64 {
|
||||
*self as i64
|
||||
}
|
||||
|
||||
/// Given an [Ordering] resulting from a comparison,
|
||||
/// reverse it if the sort condition is descending rather than
|
||||
/// the default ascending
|
||||
pub fn evaluate_order(&self, order: Ordering) -> Ordering {
|
||||
match self {
|
||||
SortCondition::Ascending => order,
|
||||
SortCondition::Descending => order.reverse(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SortCondition {
|
||||
|
@ -22,7 +22,7 @@ async fn grid_filter_checkbox_is_check_test() {
|
||||
#[tokio::test]
|
||||
async fn grid_filter_checkbox_is_uncheck_test() {
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let expected = 3;
|
||||
let expected = 4;
|
||||
let row_count = test.row_details.len();
|
||||
let scripts = vec![
|
||||
CreateCheckboxFilter {
|
||||
|
@ -6,7 +6,7 @@ use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}
|
||||
#[tokio::test]
|
||||
async fn grid_filter_checklist_is_incomplete_test() {
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let expected = 5;
|
||||
let expected = 6;
|
||||
let row_count = test.row_details.len();
|
||||
let scripts = vec![
|
||||
UpdateChecklistCell {
|
||||
|
@ -84,7 +84,7 @@ async fn grid_filter_number_is_less_than_or_equal_test() {
|
||||
async fn grid_filter_number_is_empty_test() {
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let row_count = test.row_details.len();
|
||||
let expected = 1;
|
||||
let expected = 2;
|
||||
let scripts = vec![
|
||||
CreateNumberFilter {
|
||||
condition: NumberFilterConditionPB::NumberIsEmpty,
|
||||
|
@ -11,7 +11,7 @@ async fn grid_filter_multi_select_is_empty_test() {
|
||||
condition: SelectOptionConditionPB::OptionIsEmpty,
|
||||
option_ids: vec![],
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected: 3 },
|
||||
AssertNumberOfVisibleRows { expected: 2 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
@ -24,7 +24,7 @@ async fn grid_filter_multi_select_is_not_empty_test() {
|
||||
condition: SelectOptionConditionPB::OptionIsNotEmpty,
|
||||
option_ids: vec![],
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected: 3 },
|
||||
AssertNumberOfVisibleRows { expected: 5 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
@ -39,7 +39,7 @@ async fn grid_filter_multi_select_is_test() {
|
||||
condition: SelectOptionConditionPB::OptionIs,
|
||||
option_ids: vec![options.remove(0).id, options.remove(0).id],
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected: 3 },
|
||||
AssertNumberOfVisibleRows { expected: 5 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
@ -54,7 +54,7 @@ async fn grid_filter_multi_select_is_test2() {
|
||||
condition: SelectOptionConditionPB::OptionIs,
|
||||
option_ids: vec![options.remove(1).id],
|
||||
},
|
||||
AssertNumberOfVisibleRows { expected: 2 },
|
||||
AssertNumberOfVisibleRows { expected: 4 },
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
@ -62,7 +62,7 @@ async fn grid_filter_multi_select_is_test2() {
|
||||
#[tokio::test]
|
||||
async fn grid_filter_single_select_is_empty_test() {
|
||||
let mut test = DatabaseFilterTest::new().await;
|
||||
let expected = 2;
|
||||
let expected = 3;
|
||||
let row_count = test.row_details.len();
|
||||
let scripts = vec![
|
||||
CreateSingleSelectFilter {
|
||||
|
@ -213,7 +213,7 @@ async fn grid_filter_delete_test() {
|
||||
changed: None,
|
||||
},
|
||||
AssertFilterCount { count: 0 },
|
||||
AssertNumberOfVisibleRows { expected: 6 },
|
||||
AssertNumberOfVisibleRows { expected: 7 },
|
||||
])
|
||||
.await;
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ pub fn make_test_grid() -> DatabaseData {
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..6 {
|
||||
for i in 0..7 {
|
||||
let mut row_builder = TestRowBuilder::new(gen_row_id(), &fields);
|
||||
match i {
|
||||
0 => {
|
||||
@ -166,9 +166,9 @@ pub fn make_test_grid() -> DatabaseData {
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(0))
|
||||
},
|
||||
FieldType::MultiSelect => {
|
||||
row_builder.insert_multi_select_cell(|mut options| vec![options.remove(1)])
|
||||
},
|
||||
FieldType::MultiSelect => row_builder.insert_multi_select_cell(|mut options| {
|
||||
vec![options.remove(1), options.remove(0), options.remove(0)]
|
||||
}),
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
@ -201,7 +201,8 @@ pub fn make_test_grid() -> DatabaseData {
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(1))
|
||||
},
|
||||
|
||||
FieldType::MultiSelect => row_builder
|
||||
.insert_multi_select_cell(|mut options| vec![options.remove(1), options.remove(1)]),
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
@ -218,11 +219,17 @@ pub fn make_test_grid() -> DatabaseData {
|
||||
FieldType::SingleSelect => {
|
||||
row_builder.insert_single_select_cell(|mut options| options.remove(1))
|
||||
},
|
||||
FieldType::MultiSelect => {
|
||||
row_builder.insert_multi_select_cell(|mut options| vec![options.remove(1)])
|
||||
},
|
||||
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
|
||||
_ => "".to_owned(),
|
||||
};
|
||||
}
|
||||
},
|
||||
6 => {
|
||||
row_builder.insert_text_cell("CB");
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
|
@ -30,10 +30,11 @@ async fn export_csv_test() {
|
||||
let expected = r#"Name,Price,Time,Status,Platform,is urgent,link,TODO,Updated At,Created At
|
||||
A,$1,2022/03/14,,"Google,Facebook",Yes,AppFlowy website - https://www.appflowy.io,,2022/03/14,2022/03/14
|
||||
,$2,2022/03/14,,"Google,Twitter",Yes,,,2022/03/14,2022/03/14
|
||||
C,$3,2022/03/14,Completed,Facebook,No,,,2022/03/14,2022/03/14
|
||||
C,$3,2022/03/14,Completed,"Facebook,Google,Twitter",No,,,2022/03/14,2022/03/14
|
||||
DA,$14,2022/11/17,Completed,,No,,,2022/11/17,2022/11/17
|
||||
AE,,2022/11/13,Planned,,No,,,2022/11/13,2022/11/13
|
||||
AE,$5,2022/12/25,Planned,,Yes,,,2022/12/25,2022/12/25
|
||||
AE,,2022/11/13,Planned,"Facebook,Twitter",No,,,2022/11/13,2022/11/13
|
||||
AE,$5,2022/12/25,Planned,Facebook,Yes,,,2022/12/25,2022/12/25
|
||||
CB,,,,,,,,,
|
||||
"#;
|
||||
println!("{}", s);
|
||||
assert_eq!(s, expected);
|
||||
|
@ -1,49 +0,0 @@
|
||||
use flowy_database2::entities::FieldType;
|
||||
use flowy_database2::services::sort::SortCondition;
|
||||
|
||||
use crate::database::sort_test::script::DatabaseSortTest;
|
||||
use crate::database::sort_test::script::SortScript::{AssertCellContentOrder, InsertSort};
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_checkbox_and_then_text_by_descending_test() {
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let checkbox_field = test.get_first_field(FieldType::Checkbox);
|
||||
let text_field = test.get_first_field(FieldType::RichText);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"],
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "", "C", "DA", "AE", "AE"],
|
||||
},
|
||||
// Insert checkbox sort
|
||||
InsertSort {
|
||||
field: checkbox_field.clone(),
|
||||
condition: SortCondition::Descending,
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"],
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "", "AE", "C", "DA", "AE"],
|
||||
},
|
||||
// Insert text sort
|
||||
InsertSort {
|
||||
field: text_field.clone(),
|
||||
condition: SortCondition::Ascending,
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"],
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "AE", "", "AE", "C", "DA"],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
mod checkbox_and_text_test;
|
||||
mod multi_sort_test;
|
||||
mod script;
|
||||
mod single_sort_test;
|
||||
|
@ -5,46 +5,44 @@ use crate::database::sort_test::script::DatabaseSortTest;
|
||||
use crate::database::sort_test::script::SortScript::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_text_with_checkbox_by_ascending_test() {
|
||||
async fn sort_checkbox_and_then_text_by_descending_test() {
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let text_field = test.get_first_field(FieldType::RichText).clone();
|
||||
let checkbox_field = test.get_first_field(FieldType::Checkbox).clone();
|
||||
let checkbox_field = test.get_first_field(FieldType::Checkbox);
|
||||
let text_field = test.get_first_field(FieldType::RichText);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "", "C", "DA", "AE", "AE"],
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"],
|
||||
},
|
||||
InsertSort {
|
||||
field: text_field.clone(),
|
||||
condition: SortCondition::Ascending,
|
||||
orders: vec!["Yes", "Yes", "No", "No", "No", "Yes", ""],
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "AE", "AE", "C", "DA", ""],
|
||||
orders: vec!["A", "", "C", "DA", "AE", "AE", "CB"],
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
orders: vec!["Yes", "No", "Yes", "No", "No", "Yes"],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
|
||||
let scripts = vec![
|
||||
// Insert checkbox sort
|
||||
InsertSort {
|
||||
field: checkbox_field.clone(),
|
||||
condition: SortCondition::Descending,
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
orders: vec!["Yes", "Yes", "Yes", "No", "No", "No", ""],
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "AE", "AE", "C", "DA", ""],
|
||||
orders: vec!["A", "", "AE", "C", "DA", "AE", "CB"],
|
||||
},
|
||||
// Insert text sort
|
||||
InsertSort {
|
||||
field: text_field.clone(),
|
||||
condition: SortCondition::Ascending,
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"],
|
||||
orders: vec!["Yes", "Yes", "Yes", "No", "No", "", "No"],
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "AE", "", "AE", "C", "CB", "DA"],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
|
@ -10,7 +10,7 @@ async fn sort_text_by_ascending_test() {
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "", "C", "DA", "AE", "AE"],
|
||||
orders: vec!["A", "", "C", "DA", "AE", "AE", "CB"],
|
||||
},
|
||||
InsertSort {
|
||||
field: text_field.clone(),
|
||||
@ -18,7 +18,28 @@ async fn sort_text_by_ascending_test() {
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "AE", "AE", "C", "DA", ""],
|
||||
orders: vec!["A", "AE", "AE", "C", "CB", "DA", ""],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_text_by_descending_test() {
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let text_field = test.get_first_field(FieldType::RichText);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "", "C", "DA", "AE", "AE", "CB"],
|
||||
},
|
||||
InsertSort {
|
||||
field: text_field.clone(),
|
||||
condition: SortCondition::Descending,
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["DA", "CB", "C", "AE", "AE", "A", ""],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
@ -29,13 +50,17 @@ async fn sort_change_notification_by_update_text_test() {
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let text_field = test.get_first_field(FieldType::RichText).clone();
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "", "C", "DA", "AE", "AE", "CB"],
|
||||
},
|
||||
InsertSort {
|
||||
field: text_field.clone(),
|
||||
condition: SortCondition::Ascending,
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "AE", "AE", "C", "DA", ""],
|
||||
orders: vec!["A", "AE", "AE", "C", "CB", "DA", ""],
|
||||
},
|
||||
// Wait the insert task to finish. The cost of time should be less than 200 milliseconds.
|
||||
Wait { millis: 200 },
|
||||
@ -49,8 +74,8 @@ async fn sort_change_notification_by_update_text_test() {
|
||||
text: "E".to_string(),
|
||||
},
|
||||
AssertSortChanged {
|
||||
old_row_orders: vec!["A", "E", "AE", "C", "DA", ""],
|
||||
new_row_orders: vec!["A", "AE", "C", "DA", "E", ""],
|
||||
old_row_orders: vec!["A", "E", "AE", "C", "CB", "DA", ""],
|
||||
new_row_orders: vec!["A", "AE", "C", "CB", "DA", "E", ""],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
@ -73,28 +98,7 @@ async fn sort_text_by_ascending_and_delete_sort_test() {
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "", "C", "DA", "AE"],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_text_by_descending_test() {
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let text_field = test.get_first_field(FieldType::RichText);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["A", "", "C", "DA", "AE", "AE"],
|
||||
},
|
||||
InsertSort {
|
||||
field: text_field.clone(),
|
||||
condition: SortCondition::Descending,
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: text_field.id.clone(),
|
||||
orders: vec!["DA", "C", "AE", "AE", "A", ""],
|
||||
orders: vec!["A", "", "C", "DA", "AE", "AE", "CB"],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
@ -107,12 +111,16 @@ async fn sort_checkbox_by_ascending_test() {
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
orders: vec!["Yes", "Yes", "No", "No", "No"],
|
||||
orders: vec!["Yes", "Yes", "No", "No", "No", "Yes", ""],
|
||||
},
|
||||
InsertSort {
|
||||
field: checkbox_field.clone(),
|
||||
condition: SortCondition::Ascending,
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
orders: vec!["No", "No", "No", "", "Yes", "Yes", "Yes"],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
@ -124,7 +132,7 @@ async fn sort_checkbox_by_descending_test() {
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"],
|
||||
orders: vec!["Yes", "Yes", "No", "No", "No", "Yes", ""],
|
||||
},
|
||||
InsertSort {
|
||||
field: checkbox_field.clone(),
|
||||
@ -132,7 +140,7 @@ async fn sort_checkbox_by_descending_test() {
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: checkbox_field.id.clone(),
|
||||
orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"],
|
||||
orders: vec!["Yes", "Yes", "Yes", "No", "No", "No", ""],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
@ -151,6 +159,8 @@ async fn sort_date_by_ascending_test() {
|
||||
"2022/03/14",
|
||||
"2022/11/17",
|
||||
"2022/11/13",
|
||||
"2022/12/25",
|
||||
"",
|
||||
],
|
||||
},
|
||||
InsertSort {
|
||||
@ -165,6 +175,8 @@ async fn sort_date_by_ascending_test() {
|
||||
"2022/03/14",
|
||||
"2022/11/13",
|
||||
"2022/11/17",
|
||||
"2022/12/25",
|
||||
"",
|
||||
],
|
||||
},
|
||||
];
|
||||
@ -185,6 +197,7 @@ async fn sort_date_by_descending_test() {
|
||||
"2022/11/17",
|
||||
"2022/11/13",
|
||||
"2022/12/25",
|
||||
"",
|
||||
],
|
||||
},
|
||||
InsertSort {
|
||||
@ -200,12 +213,34 @@ async fn sort_date_by_descending_test() {
|
||||
"2022/03/14",
|
||||
"2022/03/14",
|
||||
"2022/03/14",
|
||||
"",
|
||||
],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_number_by_ascending_test() {
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let number_field = test.get_first_field(FieldType::Number);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
field_id: number_field.id.clone(),
|
||||
orders: vec!["$1", "$2", "$3", "$14", "", "$5", ""],
|
||||
},
|
||||
InsertSort {
|
||||
field: number_field.clone(),
|
||||
condition: SortCondition::Ascending,
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: number_field.id.clone(),
|
||||
orders: vec!["$1", "$2", "$3", "$5", "$14", "", ""],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_number_by_descending_test() {
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
@ -213,7 +248,7 @@ async fn sort_number_by_descending_test() {
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
field_id: number_field.id.clone(),
|
||||
orders: vec!["$1", "$2", "$3", "$14", "", "$5"],
|
||||
orders: vec!["$1", "$2", "$3", "$14", "", "$5", ""],
|
||||
},
|
||||
InsertSort {
|
||||
field: number_field.clone(),
|
||||
@ -221,7 +256,28 @@ async fn sort_number_by_descending_test() {
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: number_field.id.clone(),
|
||||
orders: vec!["$14", "$5", "$3", "$2", "$1", ""],
|
||||
orders: vec!["$14", "$5", "$3", "$2", "$1", "", ""],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_single_select_by_ascending_test() {
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let single_select = test.get_first_field(FieldType::SingleSelect);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
field_id: single_select.id.clone(),
|
||||
orders: vec!["", "", "Completed", "Completed", "Planned", "Planned", ""],
|
||||
},
|
||||
InsertSort {
|
||||
field: single_select.clone(),
|
||||
condition: SortCondition::Ascending,
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: single_select.id.clone(),
|
||||
orders: vec!["Completed", "Completed", "Planned", "Planned", "", "", ""],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
@ -234,7 +290,7 @@ async fn sort_single_select_by_descending_test() {
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
field_id: single_select.id.clone(),
|
||||
orders: vec!["", "", "Completed", "Completed", "Planned", "Planned"],
|
||||
orders: vec!["", "", "Completed", "Completed", "Planned", "Planned", ""],
|
||||
},
|
||||
InsertSort {
|
||||
field: single_select.clone(),
|
||||
@ -242,7 +298,7 @@ async fn sort_single_select_by_descending_test() {
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: single_select.id.clone(),
|
||||
orders: vec!["Planned", "Planned", "Completed", "Completed", "", ""],
|
||||
orders: vec!["Planned", "Planned", "Completed", "Completed", "", "", ""],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
@ -255,7 +311,15 @@ async fn sort_multi_select_by_ascending_test() {
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
field_id: multi_select.id.clone(),
|
||||
orders: vec!["Google,Facebook", "Google,Twitter", "Facebook", "", "", ""],
|
||||
orders: vec![
|
||||
"Google,Facebook",
|
||||
"Google,Twitter",
|
||||
"Facebook,Google,Twitter",
|
||||
"",
|
||||
"Facebook,Twitter",
|
||||
"Facebook",
|
||||
"",
|
||||
],
|
||||
},
|
||||
InsertSort {
|
||||
field: multi_select.clone(),
|
||||
@ -263,7 +327,52 @@ async fn sort_multi_select_by_ascending_test() {
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: multi_select.id.clone(),
|
||||
orders: vec!["Facebook", "Google,Facebook", "Google,Twitter", "", "", ""],
|
||||
orders: vec![
|
||||
"Facebook",
|
||||
"Facebook,Twitter",
|
||||
"Google,Facebook",
|
||||
"Google,Twitter",
|
||||
"Facebook,Google,Twitter",
|
||||
"",
|
||||
"",
|
||||
],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sort_multi_select_by_descending_test() {
|
||||
let mut test = DatabaseSortTest::new().await;
|
||||
let multi_select = test.get_first_field(FieldType::MultiSelect);
|
||||
let scripts = vec![
|
||||
AssertCellContentOrder {
|
||||
field_id: multi_select.id.clone(),
|
||||
orders: vec![
|
||||
"Google,Facebook",
|
||||
"Google,Twitter",
|
||||
"Facebook,Google,Twitter",
|
||||
"",
|
||||
"Facebook,Twitter",
|
||||
"Facebook",
|
||||
"",
|
||||
],
|
||||
},
|
||||
InsertSort {
|
||||
field: multi_select.clone(),
|
||||
condition: SortCondition::Descending,
|
||||
},
|
||||
AssertCellContentOrder {
|
||||
field_id: multi_select.id.clone(),
|
||||
orders: vec![
|
||||
"Facebook,Google,Twitter",
|
||||
"Google,Twitter",
|
||||
"Google,Facebook",
|
||||
"Facebook,Twitter",
|
||||
"Facebook",
|
||||
"",
|
||||
"",
|
||||
],
|
||||
},
|
||||
];
|
||||
test.run_scripts(scripts).await;
|
||||
|
@ -21,9 +21,9 @@ impl std::convert::From<ViewIconTypePB> for IconType {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<ViewIconTypePB> for IconType {
|
||||
fn into(self) -> ViewIconTypePB {
|
||||
match self {
|
||||
impl From<IconType> for ViewIconTypePB {
|
||||
fn from(val: IconType) -> Self {
|
||||
match val {
|
||||
IconType::Emoji => ViewIconTypePB::Emoji,
|
||||
IconType::Url => ViewIconTypePB::Url,
|
||||
IconType::Icon => ViewIconTypePB::Icon,
|
||||
@ -48,11 +48,11 @@ impl std::convert::From<ViewIconPB> for ViewIcon {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<ViewIconPB> for ViewIcon {
|
||||
fn into(self) -> ViewIconPB {
|
||||
impl From<ViewIcon> for ViewIconPB {
|
||||
fn from(val: ViewIcon) -> Self {
|
||||
ViewIconPB {
|
||||
ty: self.ty.into(),
|
||||
value: self.value,
|
||||
ty: val.ty.into(),
|
||||
value: val.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ pub struct FlowyCoreTest {
|
||||
|
||||
impl Default for FlowyCoreTest {
|
||||
fn default() -> Self {
|
||||
let temp_dir = PathBuf::from(temp_dir()).join(nanoid!(6));
|
||||
let temp_dir = temp_dir().join(nanoid!(6));
|
||||
std::fs::create_dir_all(&temp_dir).unwrap();
|
||||
Self::new_with_user_data_path(temp_dir, nanoid!(6))
|
||||
}
|
||||
@ -57,7 +57,7 @@ impl FlowyCoreTest {
|
||||
}
|
||||
|
||||
pub fn new_with_user_data_path(path: PathBuf, name: String) -> Self {
|
||||
let config = AppFlowyCoreConfig::new(path.clone().to_str().unwrap(), name).log_filter(
|
||||
let config = AppFlowyCoreConfig::new(path.to_str().unwrap(), name).log_filter(
|
||||
"info",
|
||||
vec!["flowy_test".to_string(), "lib_dispatch".to_string()],
|
||||
);
|
||||
|
@ -102,7 +102,7 @@ async fn update_view_subscription_test() {
|
||||
|
||||
let cloned_test = test.clone();
|
||||
let view = workspace.views.pop().unwrap();
|
||||
assert_eq!(view.is_favorite, false);
|
||||
assert!(!view.is_favorite);
|
||||
|
||||
let update_view_id = view.id.clone();
|
||||
tokio::spawn(async move {
|
||||
@ -123,5 +123,5 @@ async fn update_view_subscription_test() {
|
||||
let expected_view = update.update_child_views.first().unwrap();
|
||||
assert_eq!(expected_view.id, view.id);
|
||||
assert_eq!(expected_view.name, "hello world".to_string());
|
||||
assert_eq!(expected_view.is_favorite, true);
|
||||
assert!(expected_view.is_favorite);
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ use crate::{errors::FlowyError, services::UserSession};
|
||||
pub fn init(user_session: Weak<UserSession>) -> AFPlugin {
|
||||
let store_preferences = user_session
|
||||
.upgrade()
|
||||
.and_then(|session| Some(session.get_store_preferences()))
|
||||
.map(|session| session.get_store_preferences())
|
||||
.unwrap();
|
||||
AFPlugin::new()
|
||||
.name("Flowy-User")
|
||||
|
Loading…
Reference in New Issue
Block a user