mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: add checkbox & select option filter tests
This commit is contained in:
parent
e8e719b73f
commit
0d5f0d29d9
@ -1,4 +1,5 @@
|
||||
use crate::services::field::select_option::SelectOptionIds;
|
||||
#![allow(clippy::needless_collect)]
|
||||
use crate::services::field::select_option::{SelectOptionIds, SelectedSelectOptions};
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::ErrorCode;
|
||||
use flowy_grid_data_model::revision::GridFilterRevision;
|
||||
@ -9,13 +10,34 @@ pub struct GridSelectOptionFilter {
|
||||
#[pb(index = 1)]
|
||||
pub condition: SelectOptionCondition,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub content: Option<String>,
|
||||
#[pb(index = 2)]
|
||||
pub option_ids: Vec<String>,
|
||||
}
|
||||
|
||||
impl GridSelectOptionFilter {
|
||||
pub fn apply(&self, _ids: &SelectOptionIds) -> bool {
|
||||
false
|
||||
pub fn apply(&self, selected_options: &SelectedSelectOptions) -> bool {
|
||||
let selected_option_ids: Vec<&String> = selected_options.options.iter().map(|option| &option.id).collect();
|
||||
match self.condition {
|
||||
SelectOptionCondition::OptionIs => {
|
||||
let required_options = self
|
||||
.option_ids
|
||||
.iter()
|
||||
.filter(|id| selected_option_ids.contains(id))
|
||||
.collect::<Vec<_>>();
|
||||
// https://stackoverflow.com/questions/69413164/how-to-fix-this-clippy-warning-needless-collect
|
||||
required_options.is_empty()
|
||||
}
|
||||
SelectOptionCondition::OptionIsNot => {
|
||||
for option_id in selected_option_ids {
|
||||
if self.option_ids.contains(option_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
SelectOptionCondition::OptionIsEmpty => selected_option_ids.is_empty(),
|
||||
SelectOptionCondition::OptionIsNotEmpty => !selected_option_ids.is_empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,9 +78,42 @@ impl std::convert::TryFrom<u8> for SelectOptionCondition {
|
||||
|
||||
impl std::convert::From<Arc<GridFilterRevision>> for GridSelectOptionFilter {
|
||||
fn from(rev: Arc<GridFilterRevision>) -> Self {
|
||||
let ids = SelectOptionIds::from(rev.content.clone());
|
||||
GridSelectOptionFilter {
|
||||
condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs),
|
||||
content: rev.content.clone(),
|
||||
option_ids: ids.into_inner(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::all)]
|
||||
use crate::entities::{GridSelectOptionFilter, SelectOptionCondition};
|
||||
use crate::services::field::select_option::{SelectOption, SelectedSelectOptions};
|
||||
|
||||
#[test]
|
||||
fn select_option_filter_is_test() {
|
||||
let option_1 = SelectOption::new("A");
|
||||
let option_2 = SelectOption::new("B");
|
||||
|
||||
let filter_1 = GridSelectOptionFilter {
|
||||
condition: SelectOptionCondition::OptionIs,
|
||||
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
filter_1.apply(&SelectedSelectOptions {
|
||||
options: vec![option_1.clone(), option_2.clone()],
|
||||
}),
|
||||
false
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
filter_1.apply(&SelectedSelectOptions {
|
||||
options: vec![option_1.clone()],
|
||||
}),
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ use crate::services::field::select_option::*;
|
||||
use crate::services::field::{
|
||||
default_type_option_builder_from_type, type_option_builder_from_json_str, DateChangesetParams, DateChangesetPayload,
|
||||
};
|
||||
use crate::services::row::AnyCellData;
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_grid_data_model::revision::FieldRevision;
|
||||
use flowy_sync::entities::grid::{FieldChangesetParams, GridSettingChangesetParams};
|
||||
@ -364,7 +365,8 @@ pub(crate) async fn get_select_option_handler(
|
||||
Some(field_rev) => {
|
||||
let cell_rev = editor.get_cell_rev(¶ms.row_id, ¶ms.field_id).await?;
|
||||
let type_option = select_option_operation(&field_rev)?;
|
||||
let option_context = type_option.select_option_cell_data(&cell_rev);
|
||||
let any_cell_data: AnyCellData = cell_rev.try_into()?;
|
||||
let option_context = type_option.selected_select_option(any_cell_data);
|
||||
data_result(option_context)
|
||||
}
|
||||
}
|
||||
|
@ -4,10 +4,9 @@ use crate::services::row::AnyCellData;
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_grid_data_model::parser::NotEmptyStr;
|
||||
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataEntry};
|
||||
use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataEntry};
|
||||
use nanoid::nanoid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub const SELECTION_IDS_SEPARATOR: &str = ",";
|
||||
|
||||
@ -61,19 +60,17 @@ impl std::default::Default for SelectOptionColor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_select_context_from(cell_rev: &Option<CellRevision>, options: &[SelectOption]) -> Vec<SelectOption> {
|
||||
match cell_rev {
|
||||
None => vec![],
|
||||
Some(cell_rev) => {
|
||||
if let Ok(type_option_cell_data) = AnyCellData::from_str(&cell_rev.data) {
|
||||
select_option_ids(type_option_cell_data.cell_data)
|
||||
.into_iter()
|
||||
.flat_map(|option_id| options.iter().find(|option| option.id == option_id).cloned())
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
pub fn make_selected_select_options<T: TryInto<AnyCellData>>(
|
||||
any_cell_data: T,
|
||||
options: &[SelectOption],
|
||||
) -> Vec<SelectOption> {
|
||||
if let Ok(type_option_cell_data) = any_cell_data.try_into() {
|
||||
let ids = SelectOptionIds::from(type_option_cell_data.cell_data);
|
||||
ids.iter()
|
||||
.flat_map(|option_id| options.iter().find(|option| &option.id == option_id).cloned())
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +100,7 @@ pub trait SelectOptionOperation: TypeOptionDataEntry + Send + Sync {
|
||||
SelectOption::with_color(name, color)
|
||||
}
|
||||
|
||||
fn select_option_cell_data(&self, cell_rev: &Option<CellRevision>) -> SelectOptionCellData;
|
||||
fn selected_select_option(&self, any_cell_data: AnyCellData) -> SelectOptionCellData;
|
||||
|
||||
fn options(&self) -> &Vec<SelectOption>;
|
||||
|
||||
@ -143,22 +140,40 @@ pub fn select_option_color_from_index(index: usize) -> SelectOptionColor {
|
||||
}
|
||||
}
|
||||
pub struct SelectOptionIds(Vec<String>);
|
||||
|
||||
impl SelectOptionIds {
|
||||
pub fn into_inner(self) -> Vec<String> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<AnyCellData> for SelectOptionIds {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(value: AnyCellData) -> Result<Self, Self::Error> {
|
||||
let ids = select_option_ids(value.cell_data);
|
||||
Ok(Self(ids))
|
||||
Ok(Self::from(value.cell_data))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<String> for SelectOptionIds {
|
||||
fn from(s: String) -> Self {
|
||||
let ids = select_option_ids(s);
|
||||
let ids = s
|
||||
.split(SELECTION_IDS_SEPARATOR)
|
||||
.map(|id| id.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
Self(ids)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<Option<String>> for SelectOptionIds {
|
||||
fn from(s: Option<String>) -> Self {
|
||||
match s {
|
||||
None => Self { 0: vec![] },
|
||||
Some(s) => Self::from(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for SelectOptionIds {
|
||||
type Target = Vec<String>;
|
||||
|
||||
@ -173,12 +188,6 @@ impl std::ops::DerefMut for SelectOptionIds {
|
||||
}
|
||||
}
|
||||
|
||||
fn select_option_ids(data: String) -> Vec<String> {
|
||||
data.split(SELECTION_IDS_SEPARATOR)
|
||||
.map(|id| id.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, ProtoBuf)]
|
||||
pub struct SelectOptionCellChangesetPayload {
|
||||
#[pb(index = 1)]
|
||||
@ -314,3 +323,15 @@ impl TryInto<SelectOptionChangeset> for SelectOptionChangesetPayload {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SelectedSelectOptions {
|
||||
pub(crate) options: Vec<SelectOption>,
|
||||
}
|
||||
|
||||
impl std::convert::From<SelectOptionCellData> for SelectedSelectOptions {
|
||||
fn from(data: SelectOptionCellData) -> Self {
|
||||
Self {
|
||||
options: data.select_options,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ use crate::entities::{FieldType, GridSelectOptionFilter};
|
||||
|
||||
use crate::impl_type_option;
|
||||
use crate::services::field::select_option::{
|
||||
make_select_context_from, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData, SelectOptionIds,
|
||||
SelectOptionOperation, SELECTION_IDS_SEPARATOR,
|
||||
make_selected_select_options, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData,
|
||||
SelectOptionIds, SelectOptionOperation, SelectedSelectOptions, SELECTION_IDS_SEPARATOR,
|
||||
};
|
||||
use crate::services::field::type_options::util::get_cell_data;
|
||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||
@ -30,8 +30,8 @@ pub struct MultiSelectTypeOption {
|
||||
impl_type_option!(MultiSelectTypeOption, FieldType::MultiSelect);
|
||||
|
||||
impl SelectOptionOperation for MultiSelectTypeOption {
|
||||
fn select_option_cell_data(&self, cell_rev: &Option<CellRevision>) -> SelectOptionCellData {
|
||||
let select_options = make_select_context_from(cell_rev, &self.options);
|
||||
fn selected_select_option(&self, any_cell_data: AnyCellData) -> SelectOptionCellData {
|
||||
let select_options = make_selected_select_options(any_cell_data, &self.options);
|
||||
SelectOptionCellData {
|
||||
options: self.options.clone(),
|
||||
select_options,
|
||||
@ -47,12 +47,13 @@ impl SelectOptionOperation for MultiSelectTypeOption {
|
||||
}
|
||||
}
|
||||
impl CellFilterOperation<GridSelectOptionFilter> for MultiSelectTypeOption {
|
||||
fn apply_filter(&self, any_cell_data: AnyCellData, _filter: &GridSelectOptionFilter) -> FlowyResult<bool> {
|
||||
fn apply_filter(&self, any_cell_data: AnyCellData, filter: &GridSelectOptionFilter) -> FlowyResult<bool> {
|
||||
if !any_cell_data.is_multi_select() {
|
||||
return Ok(true);
|
||||
}
|
||||
let _ids: SelectOptionIds = any_cell_data.try_into()?;
|
||||
Ok(false)
|
||||
|
||||
let selected_options = SelectedSelectOptions::from(self.selected_select_option(any_cell_data));
|
||||
Ok(filter.apply(&selected_options))
|
||||
}
|
||||
}
|
||||
impl CellDataOperation<String> for MultiSelectTypeOption {
|
||||
|
@ -1,11 +1,9 @@
|
||||
use crate::entities::{FieldType, GridSelectOptionFilter};
|
||||
|
||||
use crate::impl_type_option;
|
||||
use crate::services::field::select_option::{
|
||||
make_select_context_from, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData, SelectOptionIds,
|
||||
SelectOptionOperation,
|
||||
make_selected_select_options, SelectOption, SelectOptionCellContentChangeset, SelectOptionCellData,
|
||||
SelectOptionIds, SelectOptionOperation,
|
||||
};
|
||||
|
||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||
use crate::services::row::{
|
||||
AnyCellData, CellContentChangeset, CellDataOperation, CellFilterOperation, DecodedCellData,
|
||||
@ -13,9 +11,7 @@ use crate::services::row::{
|
||||
use bytes::Bytes;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
|
||||
use flowy_grid_data_model::revision::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataEntry};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Single select
|
||||
@ -30,8 +26,8 @@ pub struct SingleSelectTypeOption {
|
||||
impl_type_option!(SingleSelectTypeOption, FieldType::SingleSelect);
|
||||
|
||||
impl SelectOptionOperation for SingleSelectTypeOption {
|
||||
fn select_option_cell_data(&self, cell_rev: &Option<CellRevision>) -> SelectOptionCellData {
|
||||
let select_options = make_select_context_from(cell_rev, &self.options);
|
||||
fn selected_select_option(&self, any_cell_data: AnyCellData) -> SelectOptionCellData {
|
||||
let select_options = make_selected_select_options(any_cell_data, &self.options);
|
||||
SelectOptionCellData {
|
||||
options: self.options.clone(),
|
||||
select_options,
|
||||
|
@ -83,6 +83,25 @@ impl std::convert::TryFrom<&CellRevision> for AnyCellData {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<&Option<CellRevision>> for AnyCellData {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(value: &Option<CellRevision>) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
None => Err(FlowyError::invalid_data().context("Expected CellRevision, but receive None")),
|
||||
Some(cell_rev) => AnyCellData::try_from(cell_rev),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<Option<CellRevision>> for AnyCellData {
|
||||
type Error = FlowyError;
|
||||
|
||||
fn try_from(value: Option<CellRevision>) -> Result<Self, Self::Error> {
|
||||
Self::try_from(&value)
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyCellData {
|
||||
pub fn new(content: String, field_type: FieldType) -> Self {
|
||||
AnyCellData {
|
||||
|
Loading…
Reference in New Issue
Block a user