Fix/grid group (#1787)

* ci: config rust log

* chore: rename flowy-sdk to appflowy-core

* fix: create group after editing the url

* fix: start listen on new group

* chore: add tests

* refactor: mock data

* ci: update command
This commit is contained in:
Nathan.fooo 2023-02-02 23:02:49 +08:00 committed by GitHub
parent d09574951b
commit 069519589e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 1569 additions and 1900 deletions

View File

@ -59,7 +59,7 @@ jobs:
- name: Build FlowySDK
working-directory: frontend
run: |
cargo make --profile development-linux-x86_64 appflowy-sdk-dev
cargo make --profile development-linux-x86_64 appflowy-core-dev
- name: rustfmt rust-lib
run: cargo fmt --all -- --check

View File

@ -23,7 +23,7 @@
"type": "dart",
"preLaunchTask": "AF: build_flowy_sdk",
"env": {
"RUST_LOG": "info"
"RUST_LOG": "debug"
},
"cwd": "${workspaceRoot}/app_flowy"
},

View File

@ -48,7 +48,7 @@
{
"label": "AF: build_flowy_sdk_for_android",
"type": "shell",
"command": "cargo make --profile development-android appflowy-sdk-dev-android",
"command": "cargo make --profile development-android appflowy-core-dev-android",
"group": "build",
"options": {
"cwd": "${workspaceFolder}"

View File

@ -18,7 +18,7 @@ on_error_task = "catch"
run_task = { name = ["restore-crate-type"] }
[env]
RUST_LOG = "info"
RUST_LOG = "debug"
CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi"

View File

@ -186,43 +186,20 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
return super.close();
}
void initializeGroups(List<GroupPB> groupsData) {
void initializeGroups(List<GroupPB> groups) {
for (var controller in groupControllers.values) {
controller.dispose();
}
groupControllers.clear();
boardController.clear();
//
List<AppFlowyGroupData> groups = groupsData
boardController.addGroups(groups
.where((group) => fieldController.getField(group.fieldId) != null)
.map((group) {
return AppFlowyGroupData(
id: group.groupId,
name: group.desc,
items: _buildGroupItems(group),
customData: GroupData(
group: group,
fieldInfo: fieldController.getField(group.fieldId)!,
),
);
}).toList();
boardController.addGroups(groups);
.map((group) => initializeGroupData(group))
.toList());
for (final group in groupsData) {
final delegate = GroupControllerDelegateImpl(
controller: boardController,
fieldController: fieldController,
onNewColumnItem: (groupId, row, index) {
add(BoardEvent.didCreateRow(group, row, index));
},
);
final controller = GroupController(
databaseId: state.databaseId,
group: group,
delegate: delegate,
);
controller.startListening();
for (final group in groups) {
final controller = initializeGroupController(group);
groupControllers[controller.group.groupId] = (controller);
}
}
@ -245,11 +222,15 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
},
onDeletedGroup: (groupIds) {
if (isClosed) return;
//
boardController.removeGroups(groupIds);
},
onInsertedGroup: (insertedGroups) {
onInsertedGroup: (insertedGroup) {
if (isClosed) return;
//
final group = insertedGroup.group;
final newGroup = initializeGroupData(group);
final controller = initializeGroupController(group);
groupControllers[controller.group.groupId] = (controller);
boardController.addGroup(newGroup);
},
onUpdatedGroup: (updatedGroups) {
if (isClosed) return;
@ -294,6 +275,35 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
),
);
}
GroupController initializeGroupController(GroupPB group) {
final delegate = GroupControllerDelegateImpl(
controller: boardController,
fieldController: fieldController,
onNewColumnItem: (groupId, row, index) {
add(BoardEvent.didCreateRow(group, row, index));
},
);
final controller = GroupController(
databaseId: state.databaseId,
group: group,
delegate: delegate,
);
controller.startListening();
return controller;
}
AppFlowyGroupData initializeGroupData(GroupPB group) {
return AppFlowyGroupData(
id: group.groupId,
name: group.desc,
items: _buildGroupItems(group),
customData: GroupData(
group: group,
fieldInfo: fieldController.getField(group.fieldId)!,
),
);
}
}
@freezed

View File

@ -17,7 +17,7 @@ typedef OnGridChanged = void Function(DatabasePB);
typedef DidLoadGroups = void Function(List<GroupPB>);
typedef OnUpdatedGroup = void Function(List<GroupPB>);
typedef OnDeletedGroup = void Function(List<String>);
typedef OnInsertedGroup = void Function(List<InsertedGroupPB>);
typedef OnInsertedGroup = void Function(InsertedGroupPB);
typedef OnResetGroups = void Function(List<GroupPB>);
typedef OnRowsChanged = void Function(
@ -90,8 +90,8 @@ class BoardDataController {
onDeletedGroup.call(changeset.deletedGroups);
}
if (changeset.insertedGroups.isNotEmpty) {
onInsertedGroup.call(changeset.insertedGroups);
for (final insertedGroup in changeset.insertedGroups) {
onInsertedGroup.call(insertedGroup);
}
},
(e) => _onError?.call(e),

View File

@ -46,7 +46,7 @@ class BoardListener {
case DatabaseNotification.DidGroupByNewField:
result.fold(
(payload) => _groupByNewFieldNotifier?.value =
left(GroupViewChangesetPB.fromBuffer(payload).newGroups),
left(GroupViewChangesetPB.fromBuffer(payload).initialGroups),
(error) => _groupByNewFieldNotifier?.value = right(error),
);
break;

View File

@ -270,7 +270,7 @@ class GridCellController<T, D> extends Equatable {
/// You can set [deduplicate] to true (default is false) to reduce the save operation.
/// It's useful when you call this method when user editing the [TextField].
/// The default debounce interval is 300 milliseconds.
void saveCellData(
Future<void> saveCellData(
D data, {
bool deduplicate = false,
void Function(Option<FlowyError>)? onFinish,

View File

@ -14,13 +14,16 @@ class URLCellEditorBloc extends Bloc<URLCellEditorEvent, URLCellEditorState> {
}) : super(URLCellEditorState.initial(cellController)) {
on<URLCellEditorEvent>(
(event, emit) async {
event.when(
await event.when(
initial: () {
_startListening();
},
updateText: (text) {
cellController.saveCellData(text, deduplicate: true);
emit(state.copyWith(content: text));
updateText: (text) async {
await cellController.saveCellData(text);
emit(state.copyWith(
content: text,
isFinishEditing: true,
));
},
didReceiveCellUpdate: (cellData) {
emit(state.copyWith(content: cellData?.content ?? ""));
@ -63,12 +66,14 @@ class URLCellEditorEvent with _$URLCellEditorEvent {
class URLCellEditorState with _$URLCellEditorState {
const factory URLCellEditorState({
required String content,
required bool isFinishEditing,
}) = _URLCellEditorState;
factory URLCellEditorState.initial(GridURLCellController context) {
final cellData = context.getCellData();
return URLCellEditorState(
content: cellData?.content ?? "",
isFinishEditing: true,
);
}
}

View File

@ -6,9 +6,13 @@ import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
class URLCellEditor extends StatefulWidget {
final VoidCallback onExit;
final GridURLCellController cellController;
const URLCellEditor({required this.cellController, Key? key})
: super(key: key);
const URLCellEditor({
required this.cellController,
required this.onExit,
Key? key,
}) : super(key: key);
@override
State<URLCellEditor> createState() => _URLCellEditorState();
@ -36,12 +40,17 @@ class _URLCellEditorState extends State<URLCellEditor> {
if (_controller.text != state.content) {
_controller.text = state.content;
}
if (state.isFinishEditing) {
widget.onExit();
}
},
child: TextField(
autofocus: true,
controller: _controller,
onChanged: (value) => focusChanged(),
maxLines: null,
onSubmitted: (value) => focusChanged(),
onEditingComplete: () => focusChanged(),
maxLines: 1,
style: Theme.of(context).textTheme.bodyMedium,
decoration: const InputDecoration(
contentPadding: EdgeInsets.zero,
@ -57,11 +66,10 @@ class _URLCellEditorState extends State<URLCellEditor> {
@override
Future<void> dispose() async {
_cellBloc.close();
super.dispose();
}
Future<void> focusChanged() async {
void focusChanged() {
if (mounted) {
if (_cellBloc.isClosed == false &&
_controller.text != _cellBloc.state.content) {
@ -72,9 +80,13 @@ class _URLCellEditorState extends State<URLCellEditor> {
}
class URLEditorPopover extends StatelessWidget {
final VoidCallback onExit;
final GridURLCellController cellController;
const URLEditorPopover({required this.cellController, Key? key})
: super(key: key);
const URLEditorPopover({
required this.cellController,
required this.onExit,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -84,6 +96,7 @@ class URLEditorPopover extends StatelessWidget {
padding: const EdgeInsets.all(6),
child: URLCellEditor(
cellController: cellController,
onExit: onExit,
),
),
);

View File

@ -142,6 +142,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
return URLEditorPopover(
cellController: widget.cellControllerBuilder.build()
as GridURLCellController,
onExit: () => _popoverController.close(),
);
},
onClose: () {
@ -219,6 +220,7 @@ class _EditURLAccessoryState extends State<_EditURLAccessory>
return URLEditorPopover(
cellController:
widget.cellControllerBuilder.build() as GridURLCellController,
onExit: () => _popoverController.close(),
);
},
);

View File

@ -1,10 +1,10 @@
use flowy_core::{get_client_server_configuration, FlowySDK, FlowySDKConfig};
use flowy_core::{get_client_server_configuration, AppFlowyCore, AppFlowyCoreConfig};
pub fn init_flowy_core() -> FlowySDK {
pub fn init_flowy_core() -> AppFlowyCore {
let data_path = tauri::api::path::data_dir().unwrap();
let path = format!("{}/AppFlowy", data_path.to_str().unwrap());
let server_config = get_client_server_configuration().unwrap();
let config = FlowySDKConfig::new(&path, "AppFlowy".to_string(), server_config)
let config = AppFlowyCoreConfig::new(&path, "AppFlowy".to_string(), server_config)
.log_filter("trace", vec!["appflowy_tauri".to_string()]);
FlowySDK::new(config)
AppFlowyCore::new(config)
}

View File

@ -1,4 +1,4 @@
use flowy_core::FlowySDK;
use flowy_core::AppFlowyCore;
use lib_dispatch::prelude::{
AFPluginDispatcher, AFPluginEventResponse, AFPluginRequest, StatusCode,
};
@ -39,7 +39,7 @@ pub async fn invoke_request(
app_handler: AppHandle<Wry>,
) -> AFTauriResponse {
let request: AFPluginRequest = request.into();
let state: State<FlowySDK> = app_handler.state();
let state: State<AppFlowyCore> = app_handler.state();
let dispatcher = state.inner().dispatcher();
let response = AFPluginDispatcher::async_send(dispatcher, request).await;
response.into()

View File

@ -20,7 +20,7 @@ use parking_lot::RwLock;
use std::{ffi::CStr, os::raw::c_char};
lazy_static! {
static ref FLOWY_SDK: RwLock<Option<FlowySDK>> = RwLock::new(None);
static ref APPFLOWY_CORE: RwLock<Option<AppFlowyCore>> = RwLock::new(None);
}
#[no_mangle]
@ -30,8 +30,8 @@ pub extern "C" fn init_sdk(path: *mut c_char) -> i64 {
let server_config = get_client_server_configuration().unwrap();
let log_crates = vec!["flowy-ffi".to_string()];
let config = FlowySDKConfig::new(path, "appflowy".to_string(), server_config).log_filter("info", log_crates);
*FLOWY_SDK.write() = Some(FlowySDK::new(config));
let config = AppFlowyCoreConfig::new(path, "appflowy".to_string(), server_config).log_filter("info", log_crates);
*APPFLOWY_CORE.write() = Some(AppFlowyCore::new(config));
0
}
@ -46,7 +46,7 @@ pub extern "C" fn async_event(port: i64, input: *const u8, len: usize) {
port
);
let dispatcher = match FLOWY_SDK.read().as_ref() {
let dispatcher = match APPFLOWY_CORE.read().as_ref() {
None => {
log::error!("sdk not init yet.");
return;
@ -64,7 +64,7 @@ pub extern "C" fn sync_event(input: *const u8, len: usize) -> *const u8 {
let request: AFPluginRequest = FFIRequest::from_u8_pointer(input, len).into();
log::trace!("[FFI]: {} Sync Event: {:?}", &request.id, &request.event,);
let dispatcher = match FLOWY_SDK.read().as_ref() {
let dispatcher = match APPFLOWY_CORE.read().as_ref() {
None => {
log::error!("sdk not init yet.");
return forget_rust(Vec::default());

View File

@ -34,31 +34,31 @@ use user_model::UserProfile;
static INIT_LOG: AtomicBool = AtomicBool::new(false);
#[derive(Clone)]
pub struct FlowySDKConfig {
/// Different `FlowySDK` instance should have different name
pub struct AppFlowyCoreConfig {
/// Different `AppFlowyCoreConfig` instance should have different name
name: String,
/// Panics if the `root` path is not existing
root: String,
storage_path: String,
log_filter: String,
server_config: ClientServerConfiguration,
pub document: DocumentConfig,
}
impl fmt::Debug for FlowySDKConfig {
impl fmt::Debug for AppFlowyCoreConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FlowySDKConfig")
.field("root", &self.root)
f.debug_struct("AppFlowyCoreConfig")
.field("storage_path", &self.storage_path)
.field("server-config", &self.server_config)
.field("document-config", &self.document)
.finish()
}
}
impl FlowySDKConfig {
impl AppFlowyCoreConfig {
pub fn new(root: &str, name: String, server_config: ClientServerConfiguration) -> Self {
FlowySDKConfig {
AppFlowyCoreConfig {
name,
root: root.to_owned(),
storage_path: root.to_owned(),
log_filter: create_log_filter("info".to_owned(), vec![]),
server_config,
document: DocumentConfig::default(),
@ -106,9 +106,9 @@ fn create_log_filter(level: String, with_crates: Vec<String>) -> String {
}
#[derive(Clone)]
pub struct FlowySDK {
pub struct AppFlowyCore {
#[allow(dead_code)]
pub config: FlowySDKConfig,
pub config: AppFlowyCoreConfig,
pub user_session: Arc<UserSession>,
pub document_manager: Arc<DocumentManager>,
pub folder_manager: Arc<FolderManager>,
@ -119,10 +119,10 @@ pub struct FlowySDK {
pub task_dispatcher: Arc<RwLock<TaskDispatcher>>,
}
impl FlowySDK {
pub fn new(config: FlowySDKConfig) -> Self {
impl AppFlowyCore {
pub fn new(config: AppFlowyCoreConfig) -> Self {
init_log(&config);
init_kv(&config.root);
init_kv(&config.storage_path);
tracing::debug!("🔥 {:?}", config);
let runtime = tokio_default_runtime().unwrap();
let task_scheduler = TaskDispatcher::new(Duration::from_secs(2));
@ -257,22 +257,22 @@ fn init_kv(root: &str) {
}
}
fn init_log(config: &FlowySDKConfig) {
fn init_log(config: &AppFlowyCoreConfig) {
if !INIT_LOG.load(Ordering::SeqCst) {
INIT_LOG.store(true, Ordering::SeqCst);
let _ = lib_log::Builder::new("AppFlowy-Client", &config.root)
let _ = lib_log::Builder::new("AppFlowy-Client", &config.storage_path)
.env_filter(&config.log_filter)
.build();
}
}
fn mk_user_session(
config: &FlowySDKConfig,
config: &AppFlowyCoreConfig,
local_server: &Option<Arc<LocalServer>>,
server_config: &ClientServerConfiguration,
) -> Arc<UserSession> {
let user_config = UserSessionConfig::new(&config.name, &config.root);
let user_config = UserSessionConfig::new(&config.name, &config.storage_path);
let cloud_service = UserDepsResolver::resolve(local_server, server_config);
Arc::new(UserSession::new(user_config, cloud_service))
}
@ -282,7 +282,7 @@ struct UserStatusListener {
folder_manager: Arc<FolderManager>,
grid_manager: Arc<DatabaseManager>,
ws_conn: Arc<FlowyWebSocketConnect>,
config: FlowySDKConfig,
config: AppFlowyCoreConfig,
}
impl UserStatusListener {

View File

@ -135,7 +135,7 @@ pub struct GroupViewChangesetPB {
pub inserted_groups: Vec<InsertedGroupPB>,
#[pb(index = 3)]
pub new_groups: Vec<GroupPB>,
pub initial_groups: Vec<GroupPB>,
#[pb(index = 4)]
pub deleted_groups: Vec<String>,
@ -146,7 +146,7 @@ pub struct GroupViewChangesetPB {
impl GroupViewChangesetPB {
pub fn is_empty(&self) -> bool {
self.new_groups.is_empty()
self.initial_groups.is_empty()
&& self.inserted_groups.is_empty()
&& self.deleted_groups.is_empty()
&& self.update_groups.is_empty()

View File

@ -76,7 +76,7 @@ pub fn apply_cell_data_changeset<C: ToCellChangesetString, T: AsRef<FieldRevisio
Ok(TypeCellData::new(cell_str, field_type).to_json())
}
pub fn decode_type_cell_data<T: TryInto<TypeCellData, Error = FlowyError> + Debug>(
pub fn get_type_cell_protobuf<T: TryInto<TypeCellData, Error = FlowyError> + Debug>(
data: T,
field_rev: &FieldRevision,
cell_data_cache: Option<AtomicCellDataCache>,
@ -85,7 +85,13 @@ pub fn decode_type_cell_data<T: TryInto<TypeCellData, Error = FlowyError> + Debu
match data.try_into() {
Ok(type_cell_data) => {
let TypeCellData { cell_str, field_type } = type_cell_data;
match try_decode_cell_str(cell_str, &field_type, &to_field_type, field_rev, cell_data_cache) {
match try_decode_cell_str_to_cell_protobuf(
cell_str,
&field_type,
&to_field_type,
field_rev,
cell_data_cache,
) {
Ok(cell_bytes) => (field_type, cell_bytes),
Err(e) => {
tracing::error!("Decode cell data failed, {:?}", e);
@ -97,12 +103,30 @@ pub fn decode_type_cell_data<T: TryInto<TypeCellData, Error = FlowyError> + Debu
// It's okay to ignore this error, because it's okay that the current cell can't
// display the existing cell data. For example, the UI of the text cell will be blank if
// the type of the data of cell is Number.
(to_field_type, CellProtobufBlob::default())
}
}
}
pub fn get_type_cell_data<CellData, Output>(
data: CellData,
field_rev: &FieldRevision,
cell_data_cache: Option<AtomicCellDataCache>,
) -> Option<Output>
where
CellData: TryInto<TypeCellData, Error = FlowyError> + Debug,
Output: Default + 'static,
{
let to_field_type = field_rev.ty.into();
match data.try_into() {
Ok(type_cell_data) => {
let TypeCellData { cell_str, field_type } = type_cell_data;
try_decode_cell_str_to_cell_data(cell_str, &field_type, &to_field_type, field_rev, cell_data_cache)
}
Err(_err) => None,
}
}
/// Decode the opaque cell data from one field type to another using the corresponding `TypeOption`
///
/// The cell data might become an empty string depends on the to_field_type's `TypeOption`
@ -120,7 +144,7 @@ pub fn decode_type_cell_data<T: TryInto<TypeCellData, Error = FlowyError> + Debu
///
/// returns: CellBytes
///
pub fn try_decode_cell_str(
pub fn try_decode_cell_str_to_cell_protobuf(
cell_str: String,
from_field_type: &FieldType,
to_field_type: &FieldType,
@ -135,6 +159,20 @@ pub fn try_decode_cell_str(
}
}
pub fn try_decode_cell_str_to_cell_data<T: Default + 'static>(
cell_str: String,
from_field_type: &FieldType,
to_field_type: &FieldType,
field_rev: &FieldRevision,
cell_data_cache: Option<AtomicCellDataCache>,
) -> Option<T> {
let handler = TypeOptionCellExt::new_with_cell_data_cache(field_rev, cell_data_cache)
.get_type_option_cell_data_handler(to_field_type)?;
handler
.get_cell_data(cell_str, from_field_type, field_rev)
.ok()?
.unbox_or_none::<T>()
}
/// Returns a string that represents the current field_type's cell data.
/// For example, The string of the Multi-Select cell will be a list of the option's name
/// separated by a comma.

View File

@ -497,7 +497,7 @@ impl BoxCellData {
}
}
fn unbox_or_none<T>(self) -> Option<T>
pub(crate) fn unbox_or_none<T>(self) -> Option<T>
where
T: Default + 'static,
{

View File

@ -49,6 +49,15 @@ impl URLCellData {
}
}
impl From<URLCellDataPB> for URLCellData {
fn from(data: URLCellDataPB) -> Self {
Self {
url: data.url,
content: data.content,
}
}
}
impl AsRef<str> for URLCellData {
fn as_ref(&self) -> &str {
&self.url

View File

@ -4,7 +4,7 @@ use crate::manager::DatabaseUser;
use crate::notification::{send_notification, DatabaseNotification};
use crate::services::block_manager::DatabaseBlockManager;
use crate::services::cell::{
apply_cell_data_changeset, decode_type_cell_data, stringify_cell_data, AnyTypeCache, AtomicCellDataCache,
apply_cell_data_changeset, get_type_cell_protobuf, stringify_cell_data, AnyTypeCache, AtomicCellDataCache,
CellProtobufBlob, ToCellChangesetString, TypeCellData,
};
use crate::services::field::{
@ -392,8 +392,9 @@ impl DatabaseRevisionEditor {
pub async fn update_row(&self, changeset: RowChangeset) -> FlowyResult<()> {
let row_id = changeset.row_id.clone();
let old_row = self.get_row_rev(&row_id).await?;
self.block_manager.update_row(changeset).await?;
self.view_manager.did_update_cell(&row_id).await;
self.view_manager.did_update_row(old_row, &row_id).await;
Ok(())
}
@ -440,7 +441,7 @@ impl DatabaseRevisionEditor {
/// Returns the cell data that encoded in protobuf.
pub async fn get_cell(&self, params: &CellPathParams) -> Option<CellPB> {
let (field_type, cell_bytes) = self.decode_cell_data_from(params).await?;
let (field_type, cell_bytes) = self.get_type_cell_protobuf(params).await?;
Some(CellPB::new(
&params.field_id,
&params.row_id,
@ -473,15 +474,15 @@ impl DatabaseRevisionEditor {
}
pub async fn get_cell_protobuf(&self, params: &CellPathParams) -> Option<CellProtobufBlob> {
let (_, cell_data) = self.decode_cell_data_from(params).await?;
let (_, cell_data) = self.get_type_cell_protobuf(params).await?;
Some(cell_data)
}
async fn decode_cell_data_from(&self, params: &CellPathParams) -> Option<(FieldType, CellProtobufBlob)> {
async fn get_type_cell_protobuf(&self, params: &CellPathParams) -> Option<(FieldType, CellProtobufBlob)> {
let field_rev = self.get_field_rev(&params.field_id).await?;
let (_, row_rev) = self.block_manager.get_row_rev(&params.row_id).await.ok()??;
let cell_rev = row_rev.cells.get(&params.field_id)?.clone();
Some(decode_type_cell_data(
Some(get_type_cell_protobuf(
cell_rev.type_cell_data,
&field_rev,
Some(self.cell_data_cache.clone()),
@ -513,11 +514,12 @@ impl DatabaseRevisionEditor {
) -> FlowyResult<()> {
match self.database_pad.read().await.get_field_rev(field_id) {
None => {
let msg = format!("Field:{} not found", &field_id);
let msg = format!("Field with id:{} not found", &field_id);
Err(FlowyError::internal().context(msg))
}
Some((_, field_rev)) => {
tracing::trace!("Cell changeset: id:{} / value:{:?}", &field_id, cell_changeset);
let old_row_rev = self.get_row_rev(row_id).await?.clone();
let cell_rev = self.get_cell_rev(row_id, field_id).await?;
// Update the changeset.data property with the return value.
let type_cell_data =
@ -529,7 +531,7 @@ impl DatabaseRevisionEditor {
type_cell_data,
};
self.block_manager.update_cell(cell_changeset).await?;
self.view_manager.did_update_cell(row_id).await;
self.view_manager.did_update_row(old_row_rev, row_id).await;
Ok(())
}
}

View File

@ -1,4 +1,4 @@
use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB};
use crate::entities::{GroupPB, GroupRowsNotificationPB, GroupViewChangesetPB, InsertedGroupPB};
use crate::services::cell::DecodedCellData;
use crate::services::group::controller::MoveGroupRowContext;
use crate::services::group::Group;
@ -10,42 +10,53 @@ use std::sync::Arc;
///
/// For example, the `CheckboxGroupController` implements this trait to provide custom behavior.
///
pub trait GroupControllerCustomActions: Send + Sync {
type CellDataType: DecodedCellData;
/// Returns the a value of the cell, default value is None
pub trait GroupCustomize: Send + Sync {
type CellData: DecodedCellData;
/// Returns the a value of the cell if the cell data is not exist.
/// The default value is `None`
///
/// Determine which group the row is placed in based on the data of the cell. If the cell data
/// is None. The row will be put in to the `No status` group
///
fn default_cell_rev(&self) -> Option<CellRevision> {
fn placeholder_cell(&self) -> Option<CellRevision> {
None
}
/// Returns a bool value to determine whether the group should contain this cell or not.
fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool;
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool;
fn create_or_delete_group_when_cell_changed(
&mut self,
_row_rev: &RowRevision,
_old_cell_data: Option<&Self::CellData>,
_cell_data: &Self::CellData,
) -> FlowyResult<(Option<InsertedGroupPB>, Option<GroupPB>)> {
Ok((None, None))
}
/// Adds or removes a row if the cell data match the group filter.
/// It gets called after editing the cell or row
///
fn add_or_remove_row_in_groups_if_match(
fn add_or_remove_row_when_cell_changed(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
cell_data: &Self::CellData,
) -> Vec<GroupRowsNotificationPB>;
/// Deletes the row from the group
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB>;
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB>;
/// Move row from one group to another
fn move_row(
&mut self,
cell_data: &Self::CellDataType,
context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB>;
fn move_row(&mut self, cell_data: &Self::CellData, context: MoveGroupRowContext) -> Vec<GroupRowsNotificationPB>;
/// Returns None if there is no need to delete the group when corresponding row get removed
fn delete_group_when_move_row(&mut self, _row_rev: &RowRevision, _cell_data: &Self::CellData) -> Option<GroupPB> {
None
}
}
/// Defines the shared actions any group controller can perform.
pub trait GroupControllerSharedActions: Send + Sync {
pub trait GroupControllerActions: Send + Sync {
/// The field that is used for grouping the rows
fn field_id(&self) -> &str;
@ -64,20 +75,34 @@ pub trait GroupControllerSharedActions: Send + Sync {
/// Insert/Remove the row to the group if the corresponding cell data is changed
fn did_update_group_row(
&mut self,
old_row_rev: &Option<Arc<RowRevision>>,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsNotificationPB>>;
) -> FlowyResult<DidUpdateGroupRowResult>;
/// Remove the row from the group if the row gets deleted
fn did_delete_delete_row(
&mut self,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsNotificationPB>>;
) -> FlowyResult<DidMoveGroupRowResult>;
/// Move the row from one group to another group
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsNotificationPB>>;
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<DidMoveGroupRowResult>;
/// Update the group if the corresponding field is changed
fn did_update_group_field(&mut self, field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>>;
}
#[derive(Debug)]
pub struct DidUpdateGroupRowResult {
pub(crate) inserted_group: Option<InsertedGroupPB>,
pub(crate) deleted_group: Option<GroupPB>,
pub(crate) row_changesets: Vec<GroupRowsNotificationPB>,
}
#[derive(Debug)]
pub struct DidMoveGroupRowResult {
pub(crate) deleted_group: Option<GroupPB>,
pub(crate) row_changesets: Vec<GroupRowsNotificationPB>,
}

View File

@ -1,4 +1,4 @@
use crate::entities::{GroupPB, GroupViewChangesetPB};
use crate::entities::{GroupPB, GroupViewChangesetPB, InsertedGroupPB};
use crate::services::field::RowSingleCellData;
use crate::services::group::{default_group_configuration, GeneratedGroupContext, Group};
use flowy_error::{FlowyError, FlowyResult};
@ -101,7 +101,7 @@ where
/// Returns the no `status` group
///
/// We take the `id` of the `field` as the default group id
/// We take the `id` of the `field` as the no status group id
pub(crate) fn get_no_status_group(&self) -> Option<&Group> {
self.groups_map.get(&self.field_rev.id)
}
@ -140,6 +140,37 @@ where
each(group);
});
}
#[tracing::instrument(level = "trace", skip(self), err)]
pub(crate) fn add_new_group(&mut self, group_rev: GroupRevision) -> FlowyResult<InsertedGroupPB> {
let group = Group::new(
group_rev.id.clone(),
self.field_rev.id.clone(),
group_rev.name.clone(),
group_rev.id.clone(),
);
self.groups_map.insert(group_rev.id.clone(), group);
let (index, group) = self.get_group(&group_rev.id).unwrap();
let insert_group = InsertedGroupPB {
group: GroupPB::from(group.clone()),
index: index as i32,
};
self.mut_configuration(|configuration| {
configuration.groups.push(group_rev);
true
})?;
Ok(insert_group)
}
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn delete_group(&mut self, deleted_group_id: &str) -> FlowyResult<()> {
self.groups_map.remove(deleted_group_id);
self.mut_configuration(|configuration| {
configuration.groups.retain(|group| group.id != deleted_group_id);
true
})?;
Ok(())
}
pub(crate) fn move_group(&mut self, from_id: &str, to_id: &str) -> FlowyResult<()> {
let from_index = self.groups_map.get_index_of(from_id);
@ -211,18 +242,12 @@ where
old_groups.clear();
}
// The `No status` group index is initialized to 0
if let Some(no_status_group) = no_status_group {
old_groups.insert(0, no_status_group);
}
// The `all_group_revs` is the combination of the new groups and old groups
let MergeGroupResult {
mut all_group_revs,
new_group_revs,
updated_group_revs: _,
deleted_group_revs,
} = merge_groups(old_groups, new_groups);
} = merge_groups(no_status_group, old_groups, new_groups);
let deleted_group_ids = deleted_group_revs
.into_iter()
@ -231,13 +256,10 @@ where
self.mut_configuration(|configuration| {
let mut is_changed = !deleted_group_ids.is_empty();
// Remove the groups
if !deleted_group_ids.is_empty() {
configuration
.groups
.retain(|group| !deleted_group_ids.contains(&group.id));
}
configuration
.groups
.retain(|group| !deleted_group_ids.contains(&group.id));
// Update/Insert new groups
for group_rev in &mut all_group_revs {
@ -276,7 +298,7 @@ where
self.groups_map.insert(group.id.clone(), group);
});
let new_groups = new_group_revs
let initial_groups = new_group_revs
.into_iter()
.flat_map(|group_rev| {
let filter_content = filter_content_map.get(&group_rev.id)?;
@ -292,7 +314,7 @@ where
let changeset = GroupViewChangesetPB {
view_id: self.view_id.clone(),
new_groups,
initial_groups,
deleted_groups: deleted_group_ids,
update_groups: vec![],
inserted_groups: vec![],
@ -366,7 +388,11 @@ where
/// Merge the new groups into old groups while keeping the order in the old groups
///
fn merge_groups(old_groups: Vec<GroupRevision>, new_groups: Vec<GroupRevision>) -> MergeGroupResult {
fn merge_groups(
no_status_group: Option<GroupRevision>,
old_groups: Vec<GroupRevision>,
new_groups: Vec<GroupRevision>,
) -> MergeGroupResult {
let mut merge_result = MergeGroupResult::new();
// group_map is a helper map is used to filter out the new groups.
let mut new_group_map: IndexMap<String, GroupRevision> = IndexMap::new();
@ -378,11 +404,8 @@ fn merge_groups(old_groups: Vec<GroupRevision>, new_groups: Vec<GroupRevision>)
for old in old_groups {
if let Some(new) = new_group_map.remove(&old.id) {
merge_result.all_group_revs.push(new.clone());
if is_group_changed(&new, &old) {
merge_result.updated_group_revs.push(new);
}
} else {
merge_result.all_group_revs.push(old);
merge_result.deleted_group_revs.push(old);
}
}
@ -392,6 +415,11 @@ fn merge_groups(old_groups: Vec<GroupRevision>, new_groups: Vec<GroupRevision>)
merge_result.all_group_revs.push(group.clone());
merge_result.new_group_revs.push(group);
}
// The `No status` group index is initialized to 0
if let Some(no_status_group) = no_status_group {
merge_result.all_group_revs.insert(0, no_status_group);
}
merge_result
}
@ -399,7 +427,6 @@ fn is_group_changed(new: &GroupRevision, old: &GroupRevision) -> bool {
if new.name != old.name {
return true;
}
false
}
@ -407,7 +434,6 @@ struct MergeGroupResult {
// Contains the new groups and the updated groups
all_group_revs: Vec<GroupRevision>,
new_group_revs: Vec<GroupRevision>,
updated_group_revs: Vec<GroupRevision>,
deleted_group_revs: Vec<GroupRevision>,
}
@ -416,7 +442,6 @@ impl MergeGroupResult {
Self {
all_group_revs: vec![],
new_group_revs: vec![],
updated_group_revs: vec![],
deleted_group_revs: vec![],
}
}

View File

@ -1,11 +1,15 @@
use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB, InsertedRowPB, RowPB};
use crate::services::cell::{decode_type_cell_data, CellProtobufBlobParser, DecodedCellData};
use crate::services::group::action::{GroupControllerCustomActions, GroupControllerSharedActions};
use crate::services::cell::{get_type_cell_protobuf, CellProtobufBlobParser, DecodedCellData};
use crate::services::group::action::{
DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerActions, GroupCustomize,
};
use crate::services::group::configuration::GroupContext;
use crate::services::group::entities::Group;
use flowy_error::FlowyResult;
use grid_model::{
FieldRevision, GroupConfigurationContentSerde, GroupRevision, RowChangeset, RowRevision, TypeOptionDataDeserializer,
CellRevision, FieldRevision, GroupConfigurationContentSerde, GroupRevision, RowChangeset, RowRevision,
TypeOptionDataDeserializer,
};
use std::marker::PhantomData;
use std::sync::Arc;
@ -18,7 +22,7 @@ use std::sync::Arc;
/// If the [FieldType] doesn't implement its group controller, then the [DefaultGroupController] will
/// be used.
///
pub trait GroupController: GroupControllerSharedActions + Send + Sync {
pub trait GroupController: GroupControllerActions + Send + Sync {
fn will_create_row(&mut self, row_rev: &mut RowRevision, field_rev: &FieldRevision, group_id: &str);
fn did_create_row(&mut self, row_pb: &RowPB, group_id: &str);
}
@ -86,12 +90,12 @@ where
// https://stackoverflow.com/questions/69413164/how-to-fix-this-clippy-warning-needless-collect
#[allow(clippy::needless_collect)]
fn update_default_group(
fn update_no_status_group(
&mut self,
row_rev: &RowRevision,
other_group_changesets: &[GroupRowsNotificationPB],
) -> Option<GroupRowsNotificationPB> {
let default_group = self.group_ctx.get_mut_no_status_group()?;
let no_status_group = self.group_ctx.get_mut_no_status_group()?;
// [other_group_inserted_row] contains all the inserted rows except the default group.
let other_group_inserted_row = other_group_changesets
@ -100,7 +104,7 @@ where
.collect::<Vec<&InsertedRowPB>>();
// Calculate the inserted_rows of the default_group
let default_group_inserted_row = other_group_changesets
let no_status_group_rows = other_group_changesets
.iter()
.flat_map(|changeset| &changeset.deleted_rows)
.cloned()
@ -113,10 +117,10 @@ where
})
.collect::<Vec<String>>();
let mut changeset = GroupRowsNotificationPB::new(default_group.id.clone());
if !default_group_inserted_row.is_empty() {
let mut changeset = GroupRowsNotificationPB::new(no_status_group.id.clone());
if !no_status_group_rows.is_empty() {
changeset.inserted_rows.push(InsertedRowPB::new(row_rev.into()));
default_group.add_row(row_rev.into());
no_status_group.add_row(row_rev.into());
}
// [other_group_delete_rows] contains all the deleted rows except the default group.
@ -138,7 +142,7 @@ where
.collect::<Vec<&InsertedRowPB>>();
let mut deleted_row_ids = vec![];
for row in &default_group.rows {
for row in &no_status_group.rows {
if default_group_deleted_rows
.iter()
.any(|deleted_row| deleted_row.row.id == row.id)
@ -146,20 +150,20 @@ where
deleted_row_ids.push(row.id.clone());
}
}
default_group.rows.retain(|row| !deleted_row_ids.contains(&row.id));
no_status_group.rows.retain(|row| !deleted_row_ids.contains(&row.id));
changeset.deleted_rows.extend(deleted_row_ids);
Some(changeset)
}
}
impl<C, T, G, P> GroupControllerSharedActions for GenericGroupController<C, T, G, P>
impl<C, T, G, P> GroupControllerActions for GenericGroupController<C, T, G, P>
where
P: CellProtobufBlobParser,
C: GroupConfigurationContentSerde,
T: TypeOptionDataDeserializer,
G: GroupGenerator<Context = GroupContext<C>, TypeOptionType = T>,
Self: GroupControllerCustomActions<CellDataType = P::Object>,
Self: GroupCustomize<CellData = P::Object>,
{
fn field_id(&self) -> &str {
&self.field_id
@ -178,13 +182,13 @@ where
fn fill_groups(&mut self, row_revs: &[Arc<RowRevision>], field_rev: &FieldRevision) -> FlowyResult<()> {
for row_rev in row_revs {
let cell_rev = match row_rev.cells.get(&self.field_id) {
None => self.default_cell_rev(),
None => self.placeholder_cell(),
Some(cell_rev) => Some(cell_rev.clone()),
};
if let Some(cell_rev) = cell_rev {
let mut grouped_rows: Vec<GroupedRow> = vec![];
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data, field_rev, None).1;
let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data, field_rev, None).1;
let cell_data = cell_bytes.parser::<P>()?;
for group in self.group_ctx.groups() {
if self.can_group(&group.filter_content, &cell_data) {
@ -206,7 +210,7 @@ where
}
match self.group_ctx.get_mut_no_status_group() {
None => {}
Some(default_group) => default_group.add_row(row_rev.into()),
Some(no_status_group) => no_status_group.add_row(row_rev.into()),
}
}
@ -220,73 +224,99 @@ where
fn did_update_group_row(
&mut self,
old_row_rev: &Option<Arc<RowRevision>>,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data.clone(), field_rev, None).1;
let cell_data = cell_bytes.parser::<P>()?;
let mut changesets = self.add_or_remove_row_in_groups_if_match(row_rev, &cell_data);
) -> FlowyResult<DidUpdateGroupRowResult> {
// let cell_data = row_rev.cells.get(&self.field_id).and_then(|cell_rev| {
// let cell_data: Option<P> = get_type_cell_data(cell_rev, field_rev, None);
// cell_data
// });
let mut result = DidUpdateGroupRowResult {
inserted_group: None,
deleted_group: None,
row_changesets: vec![],
};
if let Some(default_group_changeset) = self.update_default_group(row_rev, &changesets) {
tracing::trace!("default_group_changeset: {}", default_group_changeset);
if !default_group_changeset.is_empty() {
changesets.push(default_group_changeset);
if let Some(cell_data) = get_cell_data_from_row_rev::<P>(Some(row_rev), field_rev) {
let old_row_rev = old_row_rev.as_ref().map(|old| old.as_ref());
let old_cell_data = get_cell_data_from_row_rev::<P>(old_row_rev, field_rev);
if let Ok((insert, delete)) =
self.create_or_delete_group_when_cell_changed(row_rev, old_cell_data.as_ref(), &cell_data)
{
result.inserted_group = insert;
result.deleted_group = delete;
}
let mut changesets = self.add_or_remove_row_when_cell_changed(row_rev, &cell_data);
if let Some(changeset) = self.update_no_status_group(row_rev, &changesets) {
if !changeset.is_empty() {
changesets.push(changeset);
}
}
Ok(changesets)
} else {
Ok(vec![])
result.row_changesets = changesets;
}
Ok(result)
}
fn did_delete_delete_row(
&mut self,
row_rev: &RowRevision,
field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
) -> FlowyResult<DidMoveGroupRowResult> {
// if the cell_rev is none, then the row must in the default group.
let mut result = DidMoveGroupRowResult {
deleted_group: None,
row_changesets: vec![],
};
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data.clone(), field_rev, None).1;
let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data.clone(), field_rev, None).1;
let cell_data = cell_bytes.parser::<P>()?;
if !cell_data.is_empty() {
tracing::error!("did_delete_delete_row {:?}", cell_rev.type_cell_data);
return Ok(self.delete_row(row_rev, &cell_data));
result.row_changesets = self.delete_row(row_rev, &cell_data);
return Ok(result);
}
}
match self.group_ctx.get_no_status_group() {
None => {
tracing::error!("Unexpected None value. It should have the no status group");
Ok(vec![])
}
Some(no_status_group) => {
if !no_status_group.contains_row(&row_rev.id) {
tracing::error!("The row: {} should be in the no status group", row_rev.id);
}
Ok(vec![GroupRowsNotificationPB::delete(
result.row_changesets = vec![GroupRowsNotificationPB::delete(
no_status_group.id.clone(),
vec![row_rev.id.clone()],
)])
)];
}
}
Ok(result)
}
#[tracing::instrument(level = "trace", skip_all, err)]
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<DidMoveGroupRowResult> {
let mut result = DidMoveGroupRowResult {
deleted_group: None,
row_changesets: vec![],
};
let cell_rev = match context.row_rev.cells.get(&self.field_id) {
Some(cell_rev) => Some(cell_rev.clone()),
None => self.default_cell_rev(),
None => self.placeholder_cell(),
};
if let Some(cell_rev) = cell_rev {
let cell_bytes = decode_type_cell_data(cell_rev.type_cell_data, context.field_rev, None).1;
let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data, context.field_rev, None).1;
let cell_data = cell_bytes.parser::<P>()?;
Ok(self.move_row(&cell_data, context))
result.deleted_group = self.delete_group_when_move_row(context.row_rev, &cell_data);
result.row_changesets = self.move_row(&cell_data, context);
} else {
tracing::warn!("Unexpected moving group row, changes should not be empty");
Ok(vec![])
}
Ok(result)
}
fn did_update_group_field(&mut self, _field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>> {
@ -298,3 +328,12 @@ struct GroupedRow {
row: RowPB,
group_id: String,
}
fn get_cell_data_from_row_rev<P: CellProtobufBlobParser>(
row_rev: Option<&RowRevision>,
field_rev: &FieldRevision,
) -> Option<P::Object> {
let cell_rev: &CellRevision = row_rev.and_then(|row_rev| row_rev.cells.get(&field_rev.id))?;
let cell_bytes = get_type_cell_protobuf(cell_rev.type_cell_data.clone(), field_rev, None).1;
cell_bytes.parser::<P>().ok()
}

View File

@ -1,6 +1,6 @@
use crate::entities::{GroupRowsNotificationPB, InsertedRowPB, RowPB};
use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK};
use crate::services::group::action::GroupControllerCustomActions;
use crate::services::group::action::GroupCustomize;
use crate::services::group::configuration::GroupContext;
use crate::services::group::controller::{
GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
@ -19,13 +19,13 @@ pub type CheckboxGroupController = GenericGroupController<
pub type CheckboxGroupContext = GroupContext<CheckboxGroupConfigurationRevision>;
impl GroupControllerCustomActions for CheckboxGroupController {
type CellDataType = CheckboxCellData;
fn default_cell_rev(&self) -> Option<CellRevision> {
impl GroupCustomize for CheckboxGroupController {
type CellData = CheckboxCellData;
fn placeholder_cell(&self) -> Option<CellRevision> {
Some(CellRevision::new(UNCHECK.to_string()))
}
fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool {
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool {
if cell_data.is_check() {
content == CHECK
} else {
@ -33,10 +33,10 @@ impl GroupControllerCustomActions for CheckboxGroupController {
}
}
fn add_or_remove_row_in_groups_if_match(
fn add_or_remove_row_when_cell_changed(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
cell_data: &Self::CellData,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.group_ctx.iter_mut_status_groups(|group| {
@ -79,7 +79,7 @@ impl GroupControllerCustomActions for CheckboxGroupController {
changesets
}
fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.group_ctx.iter_mut_groups(|group| {
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
@ -97,7 +97,7 @@ impl GroupControllerCustomActions for CheckboxGroupController {
fn move_row(
&mut self,
_cell_data: &Self::CellDataType,
_cell_data: &Self::CellData,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![];

View File

@ -1,5 +1,5 @@
use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB, RowPB};
use crate::services::group::action::GroupControllerSharedActions;
use crate::entities::{GroupViewChangesetPB, RowPB};
use crate::services::group::action::{DidMoveGroupRowResult, DidUpdateGroupRowResult, GroupControllerActions};
use crate::services::group::{Group, GroupController, MoveGroupRowContext};
use flowy_error::FlowyResult;
use grid_model::{FieldRevision, RowRevision};
@ -31,7 +31,7 @@ impl DefaultGroupController {
}
}
impl GroupControllerSharedActions for DefaultGroupController {
impl GroupControllerActions for DefaultGroupController {
fn field_id(&self) -> &str {
&self.field_id
}
@ -57,22 +57,33 @@ impl GroupControllerSharedActions for DefaultGroupController {
fn did_update_group_row(
&mut self,
_old_row_rev: &Option<Arc<RowRevision>>,
_row_rev: &RowRevision,
_field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
Ok(vec![])
) -> FlowyResult<DidUpdateGroupRowResult> {
Ok(DidUpdateGroupRowResult {
inserted_group: None,
deleted_group: None,
row_changesets: vec![],
})
}
fn did_delete_delete_row(
&mut self,
_row_rev: &RowRevision,
_field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
Ok(vec![])
) -> FlowyResult<DidMoveGroupRowResult> {
Ok(DidMoveGroupRowResult {
deleted_group: None,
row_changesets: vec![],
})
}
fn move_group_row(&mut self, _context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
todo!()
fn move_group_row(&mut self, _context: MoveGroupRowContext) -> FlowyResult<DidMoveGroupRowResult> {
Ok(DidMoveGroupRowResult {
deleted_group: None,
row_changesets: vec![],
})
}
fn did_update_group_field(&mut self, _field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>> {

View File

@ -1,7 +1,7 @@
use crate::entities::{GroupRowsNotificationPB, RowPB};
use crate::services::cell::insert_select_option_cell;
use crate::services::field::{MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser};
use crate::services::group::action::GroupControllerCustomActions;
use crate::services::group::action::GroupCustomize;
use crate::services::group::controller::{
GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
@ -19,17 +19,17 @@ pub type MultiSelectGroupController = GenericGroupController<
SelectOptionCellDataParser,
>;
impl GroupControllerCustomActions for MultiSelectGroupController {
type CellDataType = SelectOptionCellDataPB;
impl GroupCustomize for MultiSelectGroupController {
type CellData = SelectOptionCellDataPB;
fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool {
cell_data.select_options.iter().any(|option| option.id == content)
}
fn add_or_remove_row_in_groups_if_match(
fn add_or_remove_row_when_cell_changed(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
cell_data: &Self::CellData,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.group_ctx.iter_mut_status_groups(|group| {
@ -40,7 +40,7 @@ impl GroupControllerCustomActions for MultiSelectGroupController {
changesets
}
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.group_ctx.iter_mut_status_groups(|group| {
if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) {
@ -52,7 +52,7 @@ impl GroupControllerCustomActions for MultiSelectGroupController {
fn move_row(
&mut self,
_cell_data: &Self::CellDataType,
_cell_data: &Self::CellData,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![];

View File

@ -1,7 +1,7 @@
use crate::entities::{GroupRowsNotificationPB, RowPB};
use crate::services::cell::insert_select_option_cell;
use crate::services::field::{SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB};
use crate::services::group::action::GroupControllerCustomActions;
use crate::services::group::action::GroupCustomize;
use crate::services::group::controller::{
GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
@ -20,16 +20,16 @@ pub type SingleSelectGroupController = GenericGroupController<
SelectOptionCellDataParser,
>;
impl GroupControllerCustomActions for SingleSelectGroupController {
type CellDataType = SelectOptionCellDataPB;
impl GroupCustomize for SingleSelectGroupController {
type CellData = SelectOptionCellDataPB;
fn can_group(&self, content: &str, cell_data: &SelectOptionCellDataPB) -> bool {
cell_data.select_options.iter().any(|option| option.id == content)
}
fn add_or_remove_row_in_groups_if_match(
fn add_or_remove_row_when_cell_changed(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
cell_data: &Self::CellData,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.group_ctx.iter_mut_status_groups(|group| {
@ -40,7 +40,7 @@ impl GroupControllerCustomActions for SingleSelectGroupController {
changesets
}
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.group_ctx.iter_mut_status_groups(|group| {
if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) {
@ -52,7 +52,7 @@ impl GroupControllerCustomActions for SingleSelectGroupController {
fn move_row(
&mut self,
_cell_data: &Self::CellDataType,
_cell_data: &Self::CellData,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![];

View File

@ -1,12 +1,13 @@
use crate::entities::{GroupRowsNotificationPB, InsertedRowPB, RowPB};
use crate::entities::{GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB, RowPB};
use crate::services::cell::insert_url_cell;
use crate::services::field::{URLCellDataPB, URLCellDataParser, URLTypeOptionPB};
use crate::services::group::action::GroupControllerCustomActions;
use crate::services::field::{URLCellData, URLCellDataPB, URLCellDataParser, URLTypeOptionPB};
use crate::services::group::action::GroupCustomize;
use crate::services::group::configuration::GroupContext;
use crate::services::group::controller::{
GenericGroupController, GroupController, GroupGenerator, MoveGroupRowContext,
};
use crate::services::group::{make_no_status_group, move_group_row, GeneratedGroupConfig, GeneratedGroupContext};
use flowy_error::FlowyResult;
use grid_model::{CellRevision, FieldRevision, GroupRevision, RowRevision, URLGroupConfigurationRevision};
pub type URLGroupController =
@ -14,21 +15,61 @@ pub type URLGroupController =
pub type URLGroupContext = GroupContext<URLGroupConfigurationRevision>;
impl GroupControllerCustomActions for URLGroupController {
type CellDataType = URLCellDataPB;
impl GroupCustomize for URLGroupController {
type CellData = URLCellDataPB;
fn default_cell_rev(&self) -> Option<CellRevision> {
fn placeholder_cell(&self) -> Option<CellRevision> {
Some(CellRevision::new("".to_string()))
}
fn can_group(&self, content: &str, cell_data: &Self::CellDataType) -> bool {
fn can_group(&self, content: &str, cell_data: &Self::CellData) -> bool {
cell_data.content == content
}
fn add_or_remove_row_in_groups_if_match(
fn create_or_delete_group_when_cell_changed(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellDataType,
old_cell_data: Option<&Self::CellData>,
cell_data: &Self::CellData,
) -> FlowyResult<(Option<InsertedGroupPB>, Option<GroupPB>)> {
// Just return if the group with this url already exists
let mut inserted_group = None;
if self.group_ctx.get_group(&cell_data.url).is_none() {
let cell_data: URLCellData = cell_data.clone().into();
let group_revision = make_group_from_url_cell(&cell_data);
let mut new_group = self.group_ctx.add_new_group(group_revision)?;
new_group.group.rows.push(RowPB::from(row_rev));
inserted_group = Some(new_group);
}
// Delete the old url group if there are no rows in that group
let deleted_group =
match old_cell_data.and_then(|old_cell_data| self.group_ctx.get_group(&old_cell_data.content)) {
None => None,
Some((_, group)) => {
if group.rows.len() == 1 {
Some(group.clone())
} else {
None
}
}
};
let deleted_group = match deleted_group {
None => None,
Some(group) => {
self.group_ctx.delete_group(&group.id)?;
Some(GroupPB::from(group.clone()))
}
};
Ok((inserted_group, deleted_group))
}
fn add_or_remove_row_when_cell_changed(
&mut self,
row_rev: &RowRevision,
cell_data: &Self::CellData,
) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.group_ctx.iter_mut_status_groups(|group| {
@ -51,7 +92,7 @@ impl GroupControllerCustomActions for URLGroupController {
changesets
}
fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellData) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![];
self.group_ctx.iter_mut_groups(|group| {
let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
@ -69,7 +110,7 @@ impl GroupControllerCustomActions for URLGroupController {
fn move_row(
&mut self,
_cell_data: &Self::CellDataType,
_cell_data: &Self::CellData,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![];
@ -80,6 +121,19 @@ impl GroupControllerCustomActions for URLGroupController {
});
group_changeset
}
fn delete_group_when_move_row(&mut self, _row_rev: &RowRevision, cell_data: &Self::CellData) -> Option<GroupPB> {
let mut deleted_group = None;
if let Some((_, group)) = self.group_ctx.get_group(&cell_data.content) {
if group.rows.len() == 1 {
deleted_group = Some(GroupPB::from(group.clone()));
}
}
if deleted_group.is_some() {
let _ = self.group_ctx.delete_group(&deleted_group.as_ref().unwrap().group_id);
}
deleted_group
}
}
impl GroupController for URLGroupController {
@ -117,13 +171,9 @@ impl GroupGenerator for URLGroupGenerator {
let group_configs = cells
.into_iter()
.flat_map(|value| value.into_url_field_cell_data())
.map(|cell| {
let group_id = cell.content.clone();
let group_name = cell.content.clone();
GeneratedGroupConfig {
group_rev: GroupRevision::new(group_id, group_name),
filter_content: cell.content,
}
.map(|cell| GeneratedGroupConfig {
group_rev: make_group_from_url_cell(&cell),
filter_content: cell.content,
})
.collect();
@ -134,3 +184,9 @@ impl GroupGenerator for URLGroupGenerator {
}
}
}
fn make_group_from_url_cell(cell: &URLCellData) -> GroupRevision {
let group_id = cell.content.clone();
let group_name = cell.content.clone();
GroupRevision::new(group_id, group_name)
}

View File

@ -1,6 +1,6 @@
use crate::entities::RowPB;
#[derive(Clone, PartialEq, Eq)]
#[derive(Clone, PartialEq, Debug, Eq)]
pub struct Group {
pub id: String,
pub field_id: String,

View File

@ -240,29 +240,44 @@ impl DatabaseViewRevisionEditor {
#[tracing::instrument(level = "trace", skip_all)]
pub async fn did_delete_view_row(&self, row_rev: &RowRevision) {
// Send the group notification if the current view has groups;
let changesets = self
let result = self
.mut_group_controller(|group_controller, field_rev| {
group_controller.did_delete_delete_row(row_rev, &field_rev)
})
.await;
tracing::trace!("Delete row in view changeset: {:?}", changesets);
if let Some(changesets) = changesets {
for changeset in changesets {
if let Some(result) = result {
tracing::trace!("Delete row in view changeset: {:?}", result.row_changesets);
for changeset in result.row_changesets {
self.notify_did_update_group_rows(changeset).await;
}
}
}
pub async fn did_update_view_cell(&self, row_rev: &RowRevision) {
let changesets = self
pub async fn did_update_view_row(&self, old_row_rev: Option<Arc<RowRevision>>, row_rev: &RowRevision) {
let result = self
.mut_group_controller(|group_controller, field_rev| {
group_controller.did_update_group_row(row_rev, &field_rev)
Ok(group_controller.did_update_group_row(&old_row_rev, row_rev, &field_rev))
})
.await;
if let Some(changesets) = changesets {
for changeset in changesets {
if let Some(Ok(result)) = result {
let mut changeset = GroupViewChangesetPB {
view_id: self.view_id.clone(),
..Default::default()
};
if let Some(inserted_group) = result.inserted_group {
tracing::trace!("Create group after editing the row: {:?}", inserted_group);
changeset.inserted_groups.push(inserted_group);
}
if let Some(delete_group) = result.deleted_group {
tracing::trace!("Delete group after editing the row: {:?}", delete_group);
changeset.deleted_groups.push(delete_group.group_id);
}
self.notify_did_update_view(changeset).await;
tracing::trace!("Group changesets after editing the row: {:?}", result.row_changesets);
for changeset in result.row_changesets {
self.notify_did_update_group_rows(changeset).await;
}
}
@ -282,8 +297,8 @@ impl DatabaseViewRevisionEditor {
row_changeset: &mut RowChangeset,
to_group_id: &str,
to_row_id: Option<String>,
) -> Vec<GroupRowsNotificationPB> {
let changesets = self
) {
let result = self
.mut_group_controller(|group_controller, field_rev| {
let move_row_context = MoveGroupRowContext {
row_rev,
@ -292,13 +307,25 @@ impl DatabaseViewRevisionEditor {
to_group_id,
to_row_id,
};
let changesets = group_controller.move_group_row(move_row_context)?;
Ok(changesets)
group_controller.move_group_row(move_row_context)
})
.await;
changesets.unwrap_or_default()
if let Some(result) = result {
let mut changeset = GroupViewChangesetPB {
view_id: self.view_id.clone(),
..Default::default()
};
if let Some(delete_group) = result.deleted_group {
tracing::info!("Delete group after moving the row: {:?}", delete_group);
changeset.deleted_groups.push(delete_group.group_id);
}
self.notify_did_update_view(changeset).await;
for changeset in result.row_changesets {
self.notify_did_update_group_rows(changeset).await;
}
}
}
/// Only call once after grid view editor initialized
#[tracing::instrument(level = "trace", skip(self))]
@ -334,7 +361,7 @@ impl DatabaseViewRevisionEditor {
inserted_groups: vec![inserted_group],
deleted_groups: vec![params.from_group_id.clone()],
update_groups: vec![],
new_groups: vec![],
initial_groups: vec![],
};
self.notify_did_update_view(changeset).await;
@ -610,7 +637,7 @@ impl DatabaseViewRevisionEditor {
*self.group_controller.write().await = new_group_controller;
let changeset = GroupViewChangesetPB {
view_id: self.view_id.clone(),
new_groups,
initial_groups: new_groups,
..Default::default()
};

View File

@ -88,14 +88,14 @@ impl DatabaseViewManager {
}
/// Insert/Delete the group's row if the corresponding cell data was changed.
pub async fn did_update_cell(&self, row_id: &str) {
pub async fn did_update_row(&self, old_row_rev: Option<Arc<RowRevision>>, row_id: &str) {
match self.delegate.get_row_rev(row_id).await {
None => {
tracing::warn!("Can not find the row in grid view");
}
Some((_, row_rev)) => {
for view_editor in self.view_editors.read().await.values() {
view_editor.did_update_view_cell(&row_rev).await;
view_editor.did_update_view_row(old_row_rev.clone(), &row_rev).await;
}
}
}
@ -192,7 +192,7 @@ impl DatabaseViewManager {
) -> FlowyResult<()> {
let mut row_changeset = RowChangeset::new(row_rev.id.clone());
let view_editor = self.get_default_view_editor().await?;
let group_changesets = view_editor
view_editor
.move_view_group_row(&row_rev, &mut row_changeset, &to_group_id, to_row_id.clone())
.await;
@ -200,10 +200,6 @@ impl DatabaseViewManager {
recv_row_changeset(row_changeset).await;
}
for group_changeset in group_changesets {
view_editor.notify_did_update_group_rows(group_changeset).await;
}
Ok(())
}

View File

@ -1,4 +1,4 @@
use crate::grid::block_test::script::GridRowTest;
use crate::grid::block_test::script::DatabaseRowTest;
use crate::grid::block_test::script::RowScript::*;
use grid_model::{GridBlockMetaRevision, GridBlockMetaRevisionChangeset};
@ -11,7 +11,7 @@ async fn grid_create_block() {
CreateBlock { block: block_meta_rev },
AssertBlockCount(2),
];
GridRowTest::new().await.run_scripts(scripts).await;
DatabaseRowTest::new().await.run_scripts(scripts).await;
}
#[tokio::test]
@ -37,5 +37,5 @@ async fn grid_update_block() {
block: cloned_grid_block,
},
];
GridRowTest::new().await.run_scripts(scripts).await;
DatabaseRowTest::new().await.run_scripts(scripts).await;
}

View File

@ -1,13 +1,13 @@
use crate::grid::block_test::script::RowScript::*;
use crate::grid::block_test::script::{CreateRowScriptBuilder, GridRowTest};
use crate::grid::grid_editor::{COMPLETED, FACEBOOK, GOOGLE, PAUSED, TWITTER};
use crate::grid::block_test::script::{CreateRowScriptBuilder, DatabaseRowTest};
use crate::grid::mock_data::{COMPLETED, FACEBOOK, GOOGLE, PAUSED, TWITTER};
use flowy_database::entities::FieldType;
use flowy_database::services::field::{SELECTION_IDS_SEPARATOR, UNCHECK};
use grid_model::RowChangeset;
#[tokio::test]
async fn grid_create_row_count_test() {
let mut test = GridRowTest::new().await;
let mut test = DatabaseRowTest::new().await;
let scripts = vec![
AssertRowCount(6),
CreateEmptyRow,
@ -22,7 +22,7 @@ async fn grid_create_row_count_test() {
#[tokio::test]
async fn grid_update_row() {
let mut test = GridRowTest::new().await;
let mut test = DatabaseRowTest::new().await;
let row_rev = test.row_builder().build();
let changeset = RowChangeset {
row_id: row_rev.id.clone(),
@ -41,7 +41,7 @@ async fn grid_update_row() {
#[tokio::test]
async fn grid_delete_row() {
let mut test = GridRowTest::new().await;
let mut test = DatabaseRowTest::new().await;
let row_1 = test.row_builder().build();
let row_2 = test.row_builder().build();
let row_ids = vec![row_1.id.clone(), row_2.id.clone()];
@ -67,7 +67,7 @@ async fn grid_delete_row() {
#[tokio::test]
async fn grid_row_add_cells_test() {
let mut test = GridRowTest::new().await;
let mut test = DatabaseRowTest::new().await;
let mut builder = CreateRowScriptBuilder::new(&test);
builder.insert(FieldType::RichText, "hello world", "hello world");
builder.insert(FieldType::DateTime, "1647251762", "2022/03/14");
@ -85,7 +85,7 @@ async fn grid_row_add_cells_test() {
#[tokio::test]
async fn grid_row_insert_number_test() {
let mut test = GridRowTest::new().await;
let mut test = DatabaseRowTest::new().await;
for (val, expected) in &[("1647251762", "2022/03/14"), ("2022/03/14", ""), ("", "")] {
let mut builder = CreateRowScriptBuilder::new(&test);
builder.insert(FieldType::DateTime, val, expected);
@ -96,7 +96,7 @@ async fn grid_row_insert_number_test() {
#[tokio::test]
async fn grid_row_insert_date_test() {
let mut test = GridRowTest::new().await;
let mut test = DatabaseRowTest::new().await;
for (val, expected) in &[
("18,443", "$18,443.00"),
("0", "$0.00"),
@ -112,7 +112,7 @@ async fn grid_row_insert_date_test() {
}
#[tokio::test]
async fn grid_row_insert_single_select_test() {
let mut test = GridRowTest::new().await;
let mut test = DatabaseRowTest::new().await;
let mut builder = CreateRowScriptBuilder::new(&test);
builder.insert_single_select_cell(|mut options| options.pop().unwrap(), PAUSED);
let scripts = builder.build();
@ -121,7 +121,7 @@ async fn grid_row_insert_single_select_test() {
#[tokio::test]
async fn grid_row_insert_multi_select_test() {
let mut test = GridRowTest::new().await;
let mut test = DatabaseRowTest::new().await;
let mut builder = CreateRowScriptBuilder::new(&test);
builder.insert_multi_select_cell(
|mut options| {

View File

@ -1,6 +1,6 @@
use crate::grid::block_test::script::RowScript::{AssertCell, CreateRow};
use crate::grid::block_test::util::GridRowTestBuilder;
use crate::grid::grid_editor::GridEditorTest;
use crate::grid::database_editor::DatabaseEditorTest;
use flowy_database::entities::{CellPathParams, CreateRowParams, DatabaseViewLayout, FieldType, RowPB};
use flowy_database::services::field::*;
use flowy_database::services::row::DatabaseBlockRow;
@ -48,13 +48,13 @@ pub enum RowScript {
},
}
pub struct GridRowTest {
inner: GridEditorTest,
pub struct DatabaseRowTest {
inner: DatabaseEditorTest,
}
impl GridRowTest {
impl DatabaseRowTest {
pub async fn new() -> Self {
let editor_test = GridEditorTest::new_table().await;
let editor_test = DatabaseEditorTest::new_table().await;
Self { inner: editor_test }
}
@ -282,15 +282,15 @@ fn block_from_row_pbs(row_orders: Vec<RowPB>) -> Vec<DatabaseBlockRow> {
map.into_values().collect::<Vec<_>>()
}
impl std::ops::Deref for GridRowTest {
type Target = GridEditorTest;
impl std::ops::Deref for DatabaseRowTest {
type Target = DatabaseEditorTest;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for GridRowTest {
impl std::ops::DerefMut for DatabaseRowTest {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
@ -303,7 +303,7 @@ pub struct CreateRowScriptBuilder<'a> {
}
impl<'a> CreateRowScriptBuilder<'a> {
pub fn new(test: &'a GridRowTest) -> Self {
pub fn new(test: &'a DatabaseRowTest) -> Self {
Self {
builder: test.row_builder(),
data_by_field_type: HashMap::new(),

View File

@ -1,17 +1,17 @@
use crate::grid::grid_editor::GridEditorTest;
use crate::grid::database_editor::DatabaseEditorTest;
use flowy_database::entities::CellChangesetPB;
pub enum CellScript {
UpdateCell { changeset: CellChangesetPB, is_err: bool },
}
pub struct GridCellTest {
inner: GridEditorTest,
pub struct DatabaseCellTest {
inner: DatabaseEditorTest,
}
impl GridCellTest {
impl DatabaseCellTest {
pub async fn new() -> Self {
let inner = GridEditorTest::new_table().await;
let inner = DatabaseEditorTest::new_table().await;
Self { inner }
}
@ -48,15 +48,15 @@ impl GridCellTest {
}
}
impl std::ops::Deref for GridCellTest {
type Target = GridEditorTest;
impl std::ops::Deref for DatabaseCellTest {
type Target = DatabaseEditorTest;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for GridCellTest {
impl std::ops::DerefMut for DatabaseCellTest {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}

View File

@ -1,5 +1,5 @@
use crate::grid::cell_test::script::CellScript::*;
use crate::grid::cell_test::script::GridCellTest;
use crate::grid::cell_test::script::DatabaseCellTest;
use crate::grid::field_test::util::make_date_cell_string;
use flowy_database::entities::{CellChangesetPB, FieldType};
use flowy_database::services::cell::ToCellChangesetString;
@ -8,7 +8,7 @@ use flowy_database::services::field::{ChecklistTypeOptionPB, MultiSelectTypeOpti
#[tokio::test]
async fn grid_cell_update() {
let mut test = GridCellTest::new().await;
let mut test = DatabaseCellTest::new().await;
let field_revs = &test.field_revs;
let row_revs = &test.row_revs;
let grid_blocks = &test.block_meta_revs;
@ -60,7 +60,7 @@ async fn grid_cell_update() {
#[tokio::test]
async fn text_cell_date_test() {
let test = GridCellTest::new().await;
let test = DatabaseCellTest::new().await;
let text_field = test.get_first_field_rev(FieldType::RichText);
let cells = test
.editor
@ -84,7 +84,7 @@ async fn text_cell_date_test() {
#[tokio::test]
async fn url_cell_date_test() {
let test = GridCellTest::new().await;
let test = DatabaseCellTest::new().await;
let url_field = test.get_first_field_rev(FieldType::URL);
let cells = test
.editor

View File

@ -0,0 +1,193 @@
use crate::grid::mock_data::*;
use bytes::Bytes;
use flowy_database::entities::*;
use flowy_database::services::cell::ToCellChangesetString;
use flowy_database::services::field::SelectOptionPB;
use flowy_database::services::field::*;
use flowy_database::services::grid_editor::DatabaseRevisionEditor;
use flowy_test::helper::ViewTest;
use flowy_test::FlowySDKTest;
use grid_model::*;
use std::collections::HashMap;
use std::sync::Arc;
use strum::EnumCount;
pub struct DatabaseEditorTest {
pub sdk: FlowySDKTest,
pub view_id: String,
pub editor: Arc<DatabaseRevisionEditor>,
pub field_revs: Vec<Arc<FieldRevision>>,
pub block_meta_revs: Vec<Arc<GridBlockMetaRevision>>,
pub row_revs: Vec<Arc<RowRevision>>,
pub field_count: usize,
pub row_by_row_id: HashMap<String, RowPB>,
}
impl DatabaseEditorTest {
pub async fn new_table() -> Self {
Self::new(DatabaseViewLayout::Grid).await
}
pub async fn new_board() -> Self {
Self::new(DatabaseViewLayout::Board).await
}
pub async fn new(layout: DatabaseViewLayout) -> Self {
let sdk = FlowySDKTest::default();
let _ = sdk.init_user().await;
let test = match layout {
DatabaseViewLayout::Grid => {
let build_context = make_test_grid();
let view_data: Bytes = build_context.into();
ViewTest::new_grid_view(&sdk, view_data.to_vec()).await
}
DatabaseViewLayout::Board => {
let build_context = make_test_board();
let view_data: Bytes = build_context.into();
ViewTest::new_board_view(&sdk, view_data.to_vec()).await
}
DatabaseViewLayout::Calendar => {
let build_context = make_test_calendar();
let view_data: Bytes = build_context.into();
ViewTest::new_calendar_view(&sdk, view_data.to_vec()).await
}
};
let editor = sdk.grid_manager.open_database(&test.view.id).await.unwrap();
let field_revs = editor.get_field_revs(None).await.unwrap();
let block_meta_revs = editor.get_block_meta_revs().await.unwrap();
let row_pbs = editor.get_all_row_revs(&test.view.id).await.unwrap();
assert_eq!(block_meta_revs.len(), 1);
// It seems like you should add the field in the make_test_grid() function.
// Because we assert the initialize count of the fields is equal to FieldType::COUNT.
assert_eq!(field_revs.len(), FieldType::COUNT);
let grid_id = test.view.id;
Self {
sdk,
view_id: grid_id,
editor,
field_revs,
block_meta_revs,
row_revs: row_pbs,
field_count: FieldType::COUNT,
row_by_row_id: HashMap::default(),
}
}
pub async fn get_row_revs(&self) -> Vec<Arc<RowRevision>> {
self.editor.get_all_row_revs(&self.view_id).await.unwrap()
}
pub async fn grid_filters(&self) -> Vec<FilterPB> {
self.editor.get_all_filters().await.unwrap()
}
pub fn get_field_rev(&self, field_id: &str, field_type: FieldType) -> &Arc<FieldRevision> {
self.field_revs
.iter()
.filter(|field_rev| {
let t_field_type: FieldType = field_rev.ty.into();
field_rev.id == field_id && t_field_type == field_type
})
.collect::<Vec<_>>()
.pop()
.unwrap()
}
/// returns the first `FieldRevision` in the build-in test grid.
/// Not support duplicate `FieldType` in test grid yet.
pub fn get_first_field_rev(&self, field_type: FieldType) -> &Arc<FieldRevision> {
self.field_revs
.iter()
.filter(|field_rev| {
let t_field_type: FieldType = field_rev.ty.into();
t_field_type == field_type
})
.collect::<Vec<_>>()
.pop()
.unwrap()
}
pub fn get_multi_select_type_option(&self, field_id: &str) -> Vec<SelectOptionPB> {
let field_type = FieldType::MultiSelect;
let field_rev = self.get_field_rev(field_id, field_type.clone());
let type_option = field_rev
.get_type_option::<MultiSelectTypeOptionPB>(field_type.into())
.unwrap();
type_option.options
}
pub fn get_single_select_type_option(&self, field_id: &str) -> SingleSelectTypeOptionPB {
let field_type = FieldType::SingleSelect;
let field_rev = self.get_field_rev(field_id, field_type.clone());
field_rev
.get_type_option::<SingleSelectTypeOptionPB>(field_type.into())
.unwrap()
}
#[allow(dead_code)]
pub fn get_checklist_type_option(&self, field_id: &str) -> ChecklistTypeOptionPB {
let field_type = FieldType::Checklist;
let field_rev = self.get_field_rev(field_id, field_type.clone());
field_rev
.get_type_option::<ChecklistTypeOptionPB>(field_type.into())
.unwrap()
}
#[allow(dead_code)]
pub fn get_checkbox_type_option(&self, field_id: &str) -> CheckboxTypeOptionPB {
let field_type = FieldType::Checkbox;
let field_rev = self.get_field_rev(field_id, field_type.clone());
field_rev
.get_type_option::<CheckboxTypeOptionPB>(field_type.into())
.unwrap()
}
pub fn block_id(&self) -> &str {
&self.block_meta_revs.last().unwrap().block_id
}
pub async fn update_cell<T: ToCellChangesetString>(&mut self, field_id: &str, row_id: String, cell_changeset: T) {
let field_rev = self
.field_revs
.iter()
.find(|field_rev| field_rev.id == field_id)
.unwrap();
self.editor
.update_cell_with_changeset(&row_id, &field_rev.id, cell_changeset)
.await
.unwrap();
}
pub(crate) async fn update_text_cell(&mut self, row_id: String, content: &str) {
let field_rev = self
.field_revs
.iter()
.find(|field_rev| {
let field_type: FieldType = field_rev.ty.into();
field_type == FieldType::RichText
})
.unwrap()
.clone();
self.update_cell(&field_rev.id, row_id, content.to_string()).await;
}
pub(crate) async fn update_single_select_cell(&mut self, row_id: String, option_id: &str) {
let field_rev = self
.field_revs
.iter()
.find(|field_rev| {
let field_type: FieldType = field_rev.ty.into();
field_type == FieldType::SingleSelect
})
.unwrap()
.clone();
let cell_changeset = SelectOptionCellChangeset::from_insert_option_id(option_id);
self.update_cell(&field_rev.id, row_id, cell_changeset).await;
}
}

View File

@ -1,4 +1,4 @@
use crate::grid::grid_editor::GridEditorTest;
use crate::grid::database_editor::DatabaseEditorTest;
use flowy_database::entities::{CreateFieldParams, FieldChangesetParams, FieldType};
use flowy_database::services::cell::{stringify_cell_data, TypeCellData};
use grid_model::FieldRevision;
@ -38,13 +38,13 @@ pub enum FieldScript {
},
}
pub struct GridFieldTest {
inner: GridEditorTest,
pub struct DatabaseFieldTest {
inner: DatabaseEditorTest,
}
impl GridFieldTest {
impl DatabaseFieldTest {
pub async fn new() -> Self {
let editor_test = GridEditorTest::new_table().await;
let editor_test = DatabaseEditorTest::new_table().await;
Self { inner: editor_test }
}
@ -144,15 +144,15 @@ impl GridFieldTest {
}
}
impl std::ops::Deref for GridFieldTest {
type Target = GridEditorTest;
impl std::ops::Deref for DatabaseFieldTest {
type Target = DatabaseEditorTest;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for GridFieldTest {
impl std::ops::DerefMut for DatabaseFieldTest {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}

View File

@ -1,5 +1,5 @@
use crate::grid::field_test::script::DatabaseFieldTest;
use crate::grid::field_test::script::FieldScript::*;
use crate::grid::field_test::script::GridFieldTest;
use crate::grid::field_test::util::*;
use bytes::Bytes;
use flowy_database::entities::{FieldChangesetParams, FieldType};
@ -8,7 +8,7 @@ use flowy_database::services::field::{gen_option_id, SingleSelectTypeOptionPB, C
#[tokio::test]
async fn grid_create_field() {
let mut test = GridFieldTest::new().await;
let mut test = DatabaseFieldTest::new().await;
let (params, field_rev) = create_text_field(&test.view_id());
let scripts = vec![
@ -33,7 +33,7 @@ async fn grid_create_field() {
#[tokio::test]
async fn grid_create_duplicate_field() {
let mut test = GridFieldTest::new().await;
let mut test = DatabaseFieldTest::new().await;
let (params, _) = create_text_field(&test.view_id());
let field_count = test.field_count();
let expected_field_count = field_count + 1;
@ -46,7 +46,7 @@ async fn grid_create_duplicate_field() {
#[tokio::test]
async fn grid_update_field_with_empty_change() {
let mut test = GridFieldTest::new().await;
let mut test = DatabaseFieldTest::new().await;
let (params, _) = create_single_select_field(&test.view_id());
let create_field_index = test.field_count();
let scripts = vec![CreateField { params }];
@ -71,7 +71,7 @@ async fn grid_update_field_with_empty_change() {
#[tokio::test]
async fn grid_update_field() {
let mut test = GridFieldTest::new().await;
let mut test = DatabaseFieldTest::new().await;
let (params, _) = create_single_select_field(&test.view_id());
let scripts = vec![CreateField { params }];
let create_field_index = test.field_count();
@ -107,7 +107,7 @@ async fn grid_update_field() {
#[tokio::test]
async fn grid_delete_field() {
let mut test = GridFieldTest::new().await;
let mut test = DatabaseFieldTest::new().await;
let original_field_count = test.field_count();
let (params, _) = create_text_field(&test.view_id());
let scripts = vec![CreateField { params }];
@ -125,7 +125,7 @@ async fn grid_delete_field() {
#[tokio::test]
async fn grid_switch_from_select_option_to_checkbox_test() {
let mut test = GridFieldTest::new().await;
let mut test = DatabaseFieldTest::new().await;
let field_rev = test.get_first_field_rev(FieldType::SingleSelect);
// Update the type option data of single select option
@ -160,7 +160,7 @@ async fn grid_switch_from_select_option_to_checkbox_test() {
#[tokio::test]
async fn grid_switch_from_checkbox_to_select_option_test() {
let mut test = GridFieldTest::new().await;
let mut test = DatabaseFieldTest::new().await;
let field_rev = test.get_first_field_rev(FieldType::Checkbox).clone();
let scripts = vec![
// switch to single-select field type
@ -203,7 +203,7 @@ async fn grid_switch_from_checkbox_to_select_option_test() {
// option1, option2 -> "option1.name, option2.name"
#[tokio::test]
async fn grid_switch_from_multi_select_to_text_test() {
let mut test = GridFieldTest::new().await;
let mut test = DatabaseFieldTest::new().await;
let field_rev = test.get_first_field_rev(FieldType::MultiSelect).clone();
let multi_select_type_option = test.get_multi_select_type_option(&field_rev.id);
@ -235,7 +235,7 @@ async fn grid_switch_from_multi_select_to_text_test() {
// unchecked -> ""
#[tokio::test]
async fn grid_switch_from_checkbox_to_text_test() {
let mut test = GridFieldTest::new().await;
let mut test = DatabaseFieldTest::new().await;
let field_rev = test.get_first_field_rev(FieldType::Checkbox);
let scripts = vec![
@ -265,7 +265,7 @@ async fn grid_switch_from_checkbox_to_text_test() {
// "" -> unchecked
#[tokio::test]
async fn grid_switch_from_text_to_checkbox_test() {
let mut test = GridFieldTest::new().await;
let mut test = DatabaseFieldTest::new().await;
let field_rev = test.get_first_field_rev(FieldType::RichText).clone();
let scripts = vec![
@ -288,7 +288,7 @@ async fn grid_switch_from_text_to_checkbox_test() {
// 1647251762 -> Mar 14,2022 (This string will be different base on current data setting)
#[tokio::test]
async fn grid_switch_from_date_to_text_test() {
let mut test = GridFieldTest::new().await;
let mut test = DatabaseFieldTest::new().await;
let field_rev = test.get_first_field_rev(FieldType::DateTime).clone();
let scripts = vec![
SwitchToField {
@ -316,7 +316,7 @@ async fn grid_switch_from_date_to_text_test() {
// $1 -> "$1"(This string will be different base on current data setting)
#[tokio::test]
async fn grid_switch_from_number_to_text_test() {
let mut test = GridFieldTest::new().await;
let mut test = DatabaseFieldTest::new().await;
let field_rev = test.get_first_field_rev(FieldType::Number).clone();
let scripts = vec![

View File

@ -1,10 +1,10 @@
use crate::grid::filter_test::script::FilterScript::*;
use crate::grid::filter_test::script::{FilterRowChanged, GridFilterTest};
use crate::grid::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
use flowy_database::entities::CheckboxFilterConditionPB;
#[tokio::test]
async fn grid_filter_checkbox_is_check_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let row_count = test.row_revs.len();
// The initial number of unchecked is 3
// The initial number of checked is 2
@ -20,7 +20,7 @@ async fn grid_filter_checkbox_is_check_test() {
#[tokio::test]
async fn grid_filter_checkbox_is_uncheck_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let expected = 3;
let row_count = test.row_revs.len();
let scripts = vec![

View File

@ -1,10 +1,10 @@
use crate::grid::filter_test::script::FilterScript::*;
use crate::grid::filter_test::script::{FilterRowChanged, GridFilterTest};
use crate::grid::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
use flowy_database::entities::ChecklistFilterConditionPB;
#[tokio::test]
async fn grid_filter_checklist_is_incomplete_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let expected = 5;
let row_count = test.row_revs.len();
let scripts = vec![
@ -22,7 +22,7 @@ async fn grid_filter_checklist_is_incomplete_test() {
#[tokio::test]
async fn grid_filter_checklist_is_complete_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let expected = 1;
let row_count = test.row_revs.len();
let scripts = vec![

View File

@ -1,10 +1,10 @@
use crate::grid::filter_test::script::FilterScript::*;
use crate::grid::filter_test::script::{FilterRowChanged, GridFilterTest};
use crate::grid::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
use flowy_database::entities::DateFilterConditionPB;
#[tokio::test]
async fn grid_filter_date_is_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let row_count = test.row_revs.len();
let expected = 3;
let scripts = vec![
@ -25,7 +25,7 @@ async fn grid_filter_date_is_test() {
#[tokio::test]
async fn grid_filter_date_after_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let row_count = test.row_revs.len();
let expected = 3;
let scripts = vec![
@ -46,7 +46,7 @@ async fn grid_filter_date_after_test() {
#[tokio::test]
async fn grid_filter_date_on_or_after_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let row_count = test.row_revs.len();
let expected = 3;
let scripts = vec![
@ -67,7 +67,7 @@ async fn grid_filter_date_on_or_after_test() {
#[tokio::test]
async fn grid_filter_date_on_or_before_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let row_count = test.row_revs.len();
let expected = 4;
let scripts = vec![
@ -88,7 +88,7 @@ async fn grid_filter_date_on_or_before_test() {
#[tokio::test]
async fn grid_filter_date_within_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let row_count = test.row_revs.len();
let expected = 5;
let scripts = vec![

View File

@ -1,10 +1,10 @@
use crate::grid::filter_test::script::FilterScript::*;
use crate::grid::filter_test::script::{FilterRowChanged, GridFilterTest};
use crate::grid::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
use flowy_database::entities::NumberFilterConditionPB;
#[tokio::test]
async fn grid_filter_number_is_equal_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let row_count = test.row_revs.len();
let expected = 1;
let scripts = vec![
@ -23,7 +23,7 @@ async fn grid_filter_number_is_equal_test() {
#[tokio::test]
async fn grid_filter_number_is_less_than_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let row_count = test.row_revs.len();
let expected = 2;
let scripts = vec![
@ -43,7 +43,7 @@ async fn grid_filter_number_is_less_than_test() {
#[tokio::test]
#[should_panic]
async fn grid_filter_number_is_less_than_test2() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let row_count = test.row_revs.len();
let expected = 2;
let scripts = vec![
@ -62,7 +62,7 @@ async fn grid_filter_number_is_less_than_test2() {
#[tokio::test]
async fn grid_filter_number_is_less_than_or_equal_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let row_count = test.row_revs.len();
let expected = 3;
let scripts = vec![
@ -81,7 +81,7 @@ async fn grid_filter_number_is_less_than_or_equal_test() {
#[tokio::test]
async fn grid_filter_number_is_empty_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let row_count = test.row_revs.len();
let expected = 1;
let scripts = vec![
@ -100,7 +100,7 @@ async fn grid_filter_number_is_empty_test() {
#[tokio::test]
async fn grid_filter_number_is_not_empty_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let row_count = test.row_revs.len();
let expected = 5;
let scripts = vec![

View File

@ -15,7 +15,7 @@ use flowy_sqlite::schema::view_table::dsl::view_table;
use flowy_database::services::cell::insert_select_option_cell;
use flowy_database::services::filter::FilterType;
use flowy_database::services::view_editor::GridViewChanged;
use crate::grid::grid_editor::GridEditorTest;
use crate::grid::database_editor::DatabaseEditorTest;
pub struct FilterRowChanged {
pub(crate) showing_num_of_rows: usize,
@ -99,14 +99,14 @@ pub enum FilterScript {
Wait { millisecond: u64 }
}
pub struct GridFilterTest {
inner: GridEditorTest,
pub struct DatabaseFilterTest {
inner: DatabaseEditorTest,
recv: Option<Receiver<GridViewChanged>>,
}
impl GridFilterTest {
impl DatabaseFilterTest {
pub async fn new() -> Self {
let editor_test = GridEditorTest::new_table().await;
let editor_test = DatabaseEditorTest::new_table().await;
Self {
inner: editor_test,
recv: None,
@ -298,15 +298,15 @@ impl GridFilterTest {
}
impl std::ops::Deref for GridFilterTest {
type Target = GridEditorTest;
impl std::ops::Deref for DatabaseFilterTest {
type Target = DatabaseEditorTest;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for GridFilterTest {
impl std::ops::DerefMut for DatabaseFilterTest {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}

View File

@ -1,10 +1,10 @@
use crate::grid::filter_test::script::FilterScript::*;
use crate::grid::filter_test::script::{FilterRowChanged, GridFilterTest};
use crate::grid::filter_test::script::{DatabaseFilterTest, FilterRowChanged};
use flowy_database::entities::{FieldType, SelectOptionConditionPB};
#[tokio::test]
async fn grid_filter_multi_select_is_empty_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let scripts = vec![
CreateMultiSelectFilter {
condition: SelectOptionConditionPB::OptionIsEmpty,
@ -17,7 +17,7 @@ async fn grid_filter_multi_select_is_empty_test() {
#[tokio::test]
async fn grid_filter_multi_select_is_not_empty_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let scripts = vec![
CreateMultiSelectFilter {
condition: SelectOptionConditionPB::OptionIsNotEmpty,
@ -30,7 +30,7 @@ async fn grid_filter_multi_select_is_not_empty_test() {
#[tokio::test]
async fn grid_filter_multi_select_is_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let field_rev = test.get_first_field_rev(FieldType::MultiSelect);
let mut options = test.get_multi_select_type_option(&field_rev.id);
let scripts = vec![
@ -45,7 +45,7 @@ async fn grid_filter_multi_select_is_test() {
#[tokio::test]
async fn grid_filter_multi_select_is_test2() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let field_rev = test.get_first_field_rev(FieldType::MultiSelect);
let mut options = test.get_multi_select_type_option(&field_rev.id);
let scripts = vec![
@ -60,7 +60,7 @@ async fn grid_filter_multi_select_is_test2() {
#[tokio::test]
async fn grid_filter_single_select_is_empty_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let expected = 2;
let row_count = test.row_revs.len();
let scripts = vec![
@ -79,7 +79,7 @@ async fn grid_filter_single_select_is_empty_test() {
#[tokio::test]
async fn grid_filter_single_select_is_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let field_rev = test.get_first_field_rev(FieldType::SingleSelect);
let mut options = test.get_single_select_type_option(&field_rev.id).options;
let expected = 2;
@ -100,7 +100,7 @@ async fn grid_filter_single_select_is_test() {
#[tokio::test]
async fn grid_filter_single_select_is_test2() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let field_rev = test.get_first_field_rev(FieldType::SingleSelect);
let row_revs = test.get_row_revs().await;
let mut options = test.get_single_select_type_option(&field_rev.id).options;

View File

@ -5,7 +5,7 @@ use flowy_database::services::filter::FilterType;
#[tokio::test]
async fn grid_filter_text_is_empty_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let scripts = vec![
CreateTextFilter {
condition: TextFilterConditionPB::TextIsEmpty,
@ -22,7 +22,7 @@ async fn grid_filter_text_is_empty_test() {
#[tokio::test]
async fn grid_filter_text_is_not_empty_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
// Only one row's text of the initial rows is ""
let scripts = vec![
CreateTextFilter {
@ -55,7 +55,7 @@ async fn grid_filter_text_is_not_empty_test() {
#[tokio::test]
async fn grid_filter_is_text_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
// Only one row's text of the initial rows is "A"
let scripts = vec![CreateTextFilter {
condition: TextFilterConditionPB::Is,
@ -70,7 +70,7 @@ async fn grid_filter_is_text_test() {
#[tokio::test]
async fn grid_filter_contain_text_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let scripts = vec![CreateTextFilter {
condition: TextFilterConditionPB::Contains,
content: "A".to_string(),
@ -84,7 +84,7 @@ async fn grid_filter_contain_text_test() {
#[tokio::test]
async fn grid_filter_contain_text_test2() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let row_revs = test.row_revs.clone();
let scripts = vec![
@ -110,7 +110,7 @@ async fn grid_filter_contain_text_test2() {
#[tokio::test]
async fn grid_filter_does_not_contain_text_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
// None of the initial rows contains the text "AB"
let scripts = vec![CreateTextFilter {
condition: TextFilterConditionPB::DoesNotContain,
@ -125,7 +125,7 @@ async fn grid_filter_does_not_contain_text_test() {
#[tokio::test]
async fn grid_filter_start_with_text_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let scripts = vec![CreateTextFilter {
condition: TextFilterConditionPB::StartsWith,
content: "A".to_string(),
@ -139,7 +139,7 @@ async fn grid_filter_start_with_text_test() {
#[tokio::test]
async fn grid_filter_ends_with_text_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let scripts = vec![
CreateTextFilter {
condition: TextFilterConditionPB::EndsWith,
@ -153,7 +153,7 @@ async fn grid_filter_ends_with_text_test() {
#[tokio::test]
async fn grid_update_text_filter_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let scripts = vec![
CreateTextFilter {
condition: TextFilterConditionPB::EndsWith,
@ -187,7 +187,7 @@ async fn grid_update_text_filter_test() {
#[tokio::test]
async fn grid_filter_delete_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let field_rev = test.get_first_field_rev(FieldType::RichText).clone();
let text_filter = TextFilterPB {
condition: TextFilterConditionPB::TextIsEmpty,
@ -216,7 +216,7 @@ async fn grid_filter_delete_test() {
#[tokio::test]
async fn grid_filter_update_empty_text_cell_test() {
let mut test = GridFilterTest::new().await;
let mut test = DatabaseFilterTest::new().await;
let row_revs = test.row_revs.clone();
let scripts = vec![
CreateTextFilter {

View File

@ -1,573 +0,0 @@
#![allow(clippy::all)]
#![allow(dead_code)]
#![allow(unused_imports)]
use crate::grid::block_test::util::GridRowTestBuilder;
use bytes::Bytes;
use flowy_client_sync::client_database::DatabaseBuilder;
use flowy_database::entities::*;
use flowy_database::services::cell::ToCellChangesetString;
use flowy_database::services::field::SelectOptionPB;
use flowy_database::services::field::*;
use flowy_database::services::grid_editor::{DatabaseRevisionEditor, GridRevisionSerde};
use flowy_database::services::row::{CreateRowRevisionPayload, RowRevisionBuilder};
use flowy_database::services::setting::GridSettingChangesetBuilder;
use flowy_error::FlowyResult;
use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
use flowy_test::helper::ViewTest;
use flowy_test::FlowySDKTest;
use grid_model::*;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use strum::EnumCount;
use strum::IntoEnumIterator;
use tokio::time::sleep;
pub struct GridEditorTest {
pub sdk: FlowySDKTest,
pub view_id: String,
pub editor: Arc<DatabaseRevisionEditor>,
pub field_revs: Vec<Arc<FieldRevision>>,
pub block_meta_revs: Vec<Arc<GridBlockMetaRevision>>,
pub row_revs: Vec<Arc<RowRevision>>,
pub field_count: usize,
pub row_by_row_id: HashMap<String, RowPB>,
}
impl GridEditorTest {
pub async fn new_table() -> Self {
Self::new(DatabaseViewLayout::Grid).await
}
pub async fn new_board() -> Self {
Self::new(DatabaseViewLayout::Board).await
}
pub async fn new(layout: DatabaseViewLayout) -> Self {
let sdk = FlowySDKTest::default();
let _ = sdk.init_user().await;
let test = match layout {
DatabaseViewLayout::Grid => {
let build_context = make_test_grid();
let view_data: Bytes = build_context.into();
ViewTest::new_grid_view(&sdk, view_data.to_vec()).await
}
DatabaseViewLayout::Board => {
let build_context = make_test_board();
let view_data: Bytes = build_context.into();
ViewTest::new_board_view(&sdk, view_data.to_vec()).await
}
DatabaseViewLayout::Calendar => {
let build_context = make_test_calendar();
let view_data: Bytes = build_context.into();
ViewTest::new_calendar_view(&sdk, view_data.to_vec()).await
}
};
let editor = sdk.grid_manager.open_database(&test.view.id).await.unwrap();
let field_revs = editor.get_field_revs(None).await.unwrap();
let block_meta_revs = editor.get_block_meta_revs().await.unwrap();
let row_pbs = editor.get_all_row_revs(&test.view.id).await.unwrap();
assert_eq!(block_meta_revs.len(), 1);
// It seems like you should add the field in the make_test_grid() function.
// Because we assert the initialize count of the fields is equal to FieldType::COUNT.
assert_eq!(field_revs.len(), FieldType::COUNT);
let grid_id = test.view.id;
Self {
sdk,
view_id: grid_id,
editor,
field_revs,
block_meta_revs,
row_revs: row_pbs,
field_count: FieldType::COUNT,
row_by_row_id: HashMap::default(),
}
}
pub async fn get_row_revs(&self) -> Vec<Arc<RowRevision>> {
self.editor.get_all_row_revs(&self.view_id).await.unwrap()
}
pub async fn grid_filters(&self) -> Vec<FilterPB> {
self.editor.get_all_filters().await.unwrap()
}
pub fn get_field_rev(&self, field_id: &str, field_type: FieldType) -> &Arc<FieldRevision> {
self.field_revs
.iter()
.filter(|field_rev| {
let t_field_type: FieldType = field_rev.ty.into();
field_rev.id == field_id && t_field_type == field_type
})
.collect::<Vec<_>>()
.pop()
.unwrap()
}
/// returns the first `FieldRevision` in the build-in test grid.
/// Not support duplicate `FieldType` in test grid yet.
pub fn get_first_field_rev(&self, field_type: FieldType) -> &Arc<FieldRevision> {
self.field_revs
.iter()
.filter(|field_rev| {
let t_field_type: FieldType = field_rev.ty.into();
t_field_type == field_type
})
.collect::<Vec<_>>()
.pop()
.unwrap()
}
pub fn get_multi_select_type_option(&self, field_id: &str) -> Vec<SelectOptionPB> {
let field_type = FieldType::MultiSelect;
let field_rev = self.get_field_rev(field_id, field_type.clone());
let type_option = field_rev
.get_type_option::<MultiSelectTypeOptionPB>(field_type.into())
.unwrap();
type_option.options
}
pub fn get_single_select_type_option(&self, field_id: &str) -> SingleSelectTypeOptionPB {
let field_type = FieldType::SingleSelect;
let field_rev = self.get_field_rev(field_id, field_type.clone());
let type_option = field_rev
.get_type_option::<SingleSelectTypeOptionPB>(field_type.into())
.unwrap();
type_option
}
pub fn get_checklist_type_option(&self, field_id: &str) -> ChecklistTypeOptionPB {
let field_type = FieldType::Checklist;
let field_rev = self.get_field_rev(field_id, field_type.clone());
let type_option = field_rev
.get_type_option::<ChecklistTypeOptionPB>(field_type.into())
.unwrap();
type_option
}
pub fn get_checkbox_type_option(&self, field_id: &str) -> CheckboxTypeOptionPB {
let field_type = FieldType::Checkbox;
let field_rev = self.get_field_rev(field_id, field_type.clone());
let type_option = field_rev
.get_type_option::<CheckboxTypeOptionPB>(field_type.into())
.unwrap();
type_option
}
pub fn block_id(&self) -> &str {
&self.block_meta_revs.last().unwrap().block_id
}
pub async fn update_cell<T: ToCellChangesetString>(&mut self, field_id: &str, row_id: String, cell_changeset: T) {
let field_rev = self
.field_revs
.iter()
.find(|field_rev| field_rev.id == field_id)
.unwrap();
self.editor
.update_cell_with_changeset(&row_id, &field_rev.id, cell_changeset)
.await
.unwrap();
}
pub(crate) async fn update_text_cell(&mut self, row_id: String, content: &str) {
let field_rev = self
.field_revs
.iter()
.find(|field_rev| {
let field_type: FieldType = field_rev.ty.into();
field_type == FieldType::RichText
})
.unwrap()
.clone();
self.update_cell(&field_rev.id, row_id, content.to_string()).await;
}
pub(crate) async fn update_single_select_cell(&mut self, row_id: String, option_id: &str) {
let field_rev = self
.field_revs
.iter()
.find(|field_rev| {
let field_type: FieldType = field_rev.ty.into();
field_type == FieldType::SingleSelect
})
.unwrap()
.clone();
let cell_changeset = SelectOptionCellChangeset::from_insert_option_id(&option_id);
self.update_cell(&field_rev.id, row_id, cell_changeset).await;
}
}
pub const GOOGLE: &str = "Google";
pub const FACEBOOK: &str = "Facebook";
pub const TWITTER: &str = "Twitter";
pub const COMPLETED: &str = "Completed";
pub const PLANNED: &str = "Planned";
pub const PAUSED: &str = "Paused";
pub const FIRST_THING: &str = "Wake up at 6:00 am";
pub const SECOND_THING: &str = "Get some coffee";
pub const THIRD_THING: &str = "Start working";
/// The build-in test data for grid. Currently, there are five rows in this grid, if you want to add
/// more rows or alter the data in this grid. Some tests may fail. So you need to fix the failed tests.
fn make_test_grid() -> BuildDatabaseContext {
let mut grid_builder = DatabaseBuilder::new();
// Iterate through the FieldType to create the corresponding Field.
for field_type in FieldType::iter() {
let field_type: FieldType = field_type;
// The
match field_type {
FieldType::RichText => {
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Name")
.visibility(true)
.primary(true)
.build();
grid_builder.add_field(text_field);
}
FieldType::Number => {
// Number
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
grid_builder.add_field(number_field);
}
FieldType::DateTime => {
// Date
let date = DateTypeOptionBuilder::default()
.date_format(DateFormat::US)
.time_format(TimeFormat::TwentyFourHour);
let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
grid_builder.add_field(date_field);
}
FieldType::SingleSelect => {
// Single Select
let single_select = SingleSelectTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(COMPLETED))
.add_option(SelectOptionPB::new(PLANNED))
.add_option(SelectOptionPB::new(PAUSED));
let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
grid_builder.add_field(single_select_field);
}
FieldType::MultiSelect => {
// MultiSelect
let multi_select = MultiSelectTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(GOOGLE))
.add_option(SelectOptionPB::new(FACEBOOK))
.add_option(SelectOptionPB::new(TWITTER));
let multi_select_field = FieldBuilder::new(multi_select)
.name("Platform")
.visibility(true)
.build();
grid_builder.add_field(multi_select_field);
}
FieldType::Checkbox => {
// Checkbox
let checkbox = CheckboxTypeOptionBuilder::default();
let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build();
grid_builder.add_field(checkbox_field);
}
FieldType::URL => {
// URL
let url = URLTypeOptionBuilder::default();
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
grid_builder.add_field(url_field);
}
FieldType::Checklist => {
let checklist = ChecklistTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(FIRST_THING))
.add_option(SelectOptionPB::new(SECOND_THING))
.add_option(SelectOptionPB::new(THIRD_THING));
let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
grid_builder.add_field(checklist_field);
}
}
}
for i in 0..6 {
let block_id = grid_builder.block_id().to_owned();
let field_revs = grid_builder.field_revs();
let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs);
match i {
0 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("A"),
FieldType::Number => row_builder.insert_number_cell("1"),
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
FieldType::MultiSelect => row_builder
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
FieldType::Checklist => row_builder.insert_checklist_cell(|options| options),
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
FieldType::URL => row_builder.insert_url_cell("AppFlowy website - https://www.appflowy.io"),
_ => "".to_owned(),
};
}
}
1 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell(""),
FieldType::Number => row_builder.insert_number_cell("2"),
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
FieldType::MultiSelect => row_builder
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(1)]),
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
_ => "".to_owned(),
};
}
}
2 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("C"),
FieldType::Number => row_builder.insert_number_cell("3"),
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
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::Checkbox => row_builder.insert_checkbox_cell("false"),
_ => "".to_owned(),
};
}
}
3 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("DA"),
FieldType::Number => row_builder.insert_number_cell("4"),
FieldType::DateTime => row_builder.insert_date_cell("1668704685"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(0))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
_ => "".to_owned(),
};
}
}
4 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("AE"),
FieldType::Number => row_builder.insert_number_cell(""),
FieldType::DateTime => row_builder.insert_date_cell("1668359085"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(1))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
_ => "".to_owned(),
};
}
}
5 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("AE"),
FieldType::Number => row_builder.insert_number_cell("5"),
FieldType::DateTime => row_builder.insert_date_cell("1671938394"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(1))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
_ => "".to_owned(),
};
}
}
_ => {}
}
let row_rev = row_builder.build();
grid_builder.add_row(row_rev);
}
grid_builder.build()
}
// Kanban board unit test mock data
fn make_test_board() -> BuildDatabaseContext {
let mut grid_builder = DatabaseBuilder::new();
// Iterate through the FieldType to create the corresponding Field.
for field_type in FieldType::iter() {
let field_type: FieldType = field_type;
// The
match field_type {
FieldType::RichText => {
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Name")
.visibility(true)
.primary(true)
.build();
grid_builder.add_field(text_field);
}
FieldType::Number => {
// Number
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
grid_builder.add_field(number_field);
}
FieldType::DateTime => {
// Date
let date = DateTypeOptionBuilder::default()
.date_format(DateFormat::US)
.time_format(TimeFormat::TwentyFourHour);
let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
grid_builder.add_field(date_field);
}
FieldType::SingleSelect => {
// Single Select
let single_select = SingleSelectTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(COMPLETED))
.add_option(SelectOptionPB::new(PLANNED))
.add_option(SelectOptionPB::new(PAUSED));
let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
grid_builder.add_field(single_select_field);
}
FieldType::MultiSelect => {
// MultiSelect
let multi_select = MultiSelectTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(GOOGLE))
.add_option(SelectOptionPB::new(FACEBOOK))
.add_option(SelectOptionPB::new(TWITTER));
let multi_select_field = FieldBuilder::new(multi_select)
.name("Platform")
.visibility(true)
.build();
grid_builder.add_field(multi_select_field);
}
FieldType::Checkbox => {
// Checkbox
let checkbox = CheckboxTypeOptionBuilder::default();
let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build();
grid_builder.add_field(checkbox_field);
}
FieldType::URL => {
// URL
let url = URLTypeOptionBuilder::default();
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
grid_builder.add_field(url_field);
}
FieldType::Checklist => {
let checklist = ChecklistTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(FIRST_THING))
.add_option(SelectOptionPB::new(SECOND_THING))
.add_option(SelectOptionPB::new(THIRD_THING));
let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
grid_builder.add_field(checklist_field);
}
}
}
// We have many assumptions base on the number of the rows, so do not change the number of the loop.
for i in 0..5 {
let block_id = grid_builder.block_id().to_owned();
let field_revs = grid_builder.field_revs();
let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs);
match i {
0 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("A"),
FieldType::Number => row_builder.insert_number_cell("1"),
// 1647251762 => Mar 14,2022
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
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(0), options.remove(0)]),
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"),
_ => "".to_owned(),
};
}
}
1 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("B"),
FieldType::Number => row_builder.insert_number_cell("2"),
// 1647251762 => Mar 14,2022
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
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(0), options.remove(0)]),
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
_ => "".to_owned(),
};
}
}
2 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("C"),
FieldType::Number => row_builder.insert_number_cell("3"),
// 1647251762 => Mar 14,2022
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
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(0)])
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
FieldType::URL => row_builder.insert_url_cell("https://github.com/AppFlowy-IO/AppFlowy"),
_ => "".to_owned(),
};
}
}
3 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("DA"),
FieldType::Number => row_builder.insert_number_cell("4"),
FieldType::DateTime => row_builder.insert_date_cell("1668704685"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(1))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"),
_ => "".to_owned(),
};
}
}
4 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("AE"),
FieldType::Number => row_builder.insert_number_cell(""),
FieldType::DateTime => row_builder.insert_date_cell("1668359085"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(2))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
_ => "".to_owned(),
};
}
}
_ => {}
}
let row_rev = row_builder.build();
grid_builder.add_row(row_rev);
}
grid_builder.build()
}
// Calendar unit test mock data
fn make_test_calendar() -> BuildDatabaseContext {
todo!()
}

View File

@ -1,381 +0,0 @@
use crate::grid::script::EditorScript::*;
use crate::grid::script::*;
use chrono::NaiveDateTime;
use flowy_database::services::field::{
DateCellContentChangeset, DateCellData, MultiSelectTypeOptionPB, SelectOption, SelectOptionCellContentChangeset,
SingleSelectTypeOption, SELECTION_IDS_SEPARATOR,
};
use flowy_database::services::row::{decode_cell_data_from_type_option_cell_data, CreateRowMetaBuilder};
use grid_model::entities::{
CellChangeset, FieldChangesetParams, FieldType, GridBlockInfoChangeset, GridBlockMetaSnapshot, RowMetaChangeset,
TypeOptionDataFormat,
};
#[tokio::test]
async fn grid_create_field() {
let mut test = GridEditorTest::new().await;
let (text_field_params, text_field_meta) = create_text_field(&test.grid_id);
let (single_select_params, single_select_field) = create_single_select_field(&test.grid_id);
let scripts = vec![
CreateField {
params: text_field_params,
},
AssertFieldEqual {
field_index: test.field_count,
field_meta: text_field_meta,
},
];
test.run_scripts(scripts).await;
let scripts = vec![
CreateField {
params: single_select_params,
},
AssertFieldEqual {
field_index: test.field_count,
field_meta: single_select_field,
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_create_duplicate_field() {
let mut test = GridEditorTest::new().await;
let (params, _) = create_text_field(&test.grid_id);
let field_count = test.field_count;
let expected_field_count = field_count + 1;
let scripts = vec![
CreateField { params: params.clone() },
CreateField { params },
AssertFieldCount(expected_field_count),
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_update_field_with_empty_change() {
let mut test = GridEditorTest::new().await;
let (params, field_meta) = create_single_select_field(&test.grid_id);
let changeset = FieldChangesetParams {
field_id: field_meta.id.clone(),
grid_id: test.grid_id.clone(),
..Default::default()
};
let scripts = vec![
CreateField { params },
UpdateField { changeset },
AssertFieldEqual {
field_index: test.field_count,
field_meta,
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_update_field() {
let mut test = GridEditorTest::new().await;
let (single_select_params, single_select_field) = create_single_select_field(&test.grid_id);
let mut cloned_field = single_select_field.clone();
let mut single_select_type_option = SingleSelectTypeOption::from(&single_select_field);
single_select_type_option.options.push(SelectOption::new("Unknown"));
let changeset = FieldChangesetParams {
field_id: single_select_field.id.clone(),
grid_id: test.grid_id.clone(),
frozen: Some(true),
width: Some(1000),
type_option_data: Some(single_select_type_option.protobuf_bytes().to_vec()),
..Default::default()
};
cloned_field.frozen = true;
cloned_field.width = 1000;
cloned_field.insert_type_option_entry(&single_select_type_option);
let scripts = vec![
CreateField {
params: single_select_params,
},
UpdateField { changeset },
AssertFieldEqual {
field_index: test.field_count,
field_meta: cloned_field,
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_delete_field() {
let mut test = GridEditorTest::new().await;
let expected_field_count = test.field_count;
let (text_params, text_field) = create_text_field(&test.grid_id);
let scripts = vec![
CreateField { params: text_params },
DeleteField { field_meta: text_field },
AssertFieldCount(expected_field_count),
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_create_block() {
let grid_block = GridBlockMetaSnapshot::new();
let scripts = vec![
AssertBlockCount(1),
CreateBlock { block: grid_block },
AssertBlockCount(2),
];
GridEditorTest::new().await.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_update_block() {
let grid_block = GridBlockMetaSnapshot::new();
let mut cloned_grid_block = grid_block.clone();
let changeset = GridBlockInfoChangeset {
block_id: grid_block.block_id.clone(),
start_row_index: Some(2),
row_count: Some(10),
};
cloned_grid_block.start_row_index = 2;
cloned_grid_block.row_count = 10;
let scripts = vec![
AssertBlockCount(1),
CreateBlock { block: grid_block },
UpdateBlock { changeset },
AssertBlockCount(2),
AssertBlockEqual {
block_index: 1,
block: cloned_grid_block,
},
];
GridEditorTest::new().await.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_create_row() {
let scripts = vec![AssertRowCount(3), CreateEmptyRow, CreateEmptyRow, AssertRowCount(5)];
GridEditorTest::new().await.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_create_row2() {
let mut test = GridEditorTest::new().await;
let create_row_context = CreateRowMetaBuilder::new(&test.field_metas).build();
let scripts = vec![
AssertRowCount(3),
CreateRow {
context: create_row_context,
},
AssertRowCount(4),
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_update_row() {
let mut test = GridEditorTest::new().await;
let context = CreateRowMetaBuilder::new(&test.field_metas).build();
let changeset = RowMetaChangeset {
row_id: context.row_id.clone(),
height: None,
visibility: None,
cell_by_field_id: Default::default(),
};
let scripts = vec![
AssertRowCount(3),
CreateRow { context },
UpdateRow {
changeset: changeset.clone(),
},
AssertRow { changeset },
AssertRowCount(4),
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_delete_row() {
let mut test = GridEditorTest::new().await;
let context_1 = CreateRowMetaBuilder::new(&test.field_metas).build();
let context_2 = CreateRowMetaBuilder::new(&test.field_metas).build();
let row_ids = vec![context_1.row_id.clone(), context_2.row_id.clone()];
let scripts = vec![
AssertRowCount(3),
CreateRow { context: context_1 },
CreateRow { context: context_2 },
AssertBlockCount(1),
AssertBlock {
block_index: 0,
row_count: 5,
start_row_index: 0,
},
DeleteRow { row_ids },
AssertBlock {
block_index: 0,
row_count: 3,
start_row_index: 0,
},
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_row_add_cells_test() {
let mut test = GridEditorTest::new().await;
let mut builder = CreateRowMetaBuilder::new(&test.field_metas);
for field in &test.field_metas {
match field.field_type {
FieldType::RichText => {
builder.add_cell(&field.id, "hello world".to_owned()).unwrap();
}
FieldType::Number => {
builder.add_cell(&field.id, "18,443".to_owned()).unwrap();
}
FieldType::DateTime => {
builder
.add_cell(&field.id, make_date_cell_string("1647251762"))
.unwrap();
}
FieldType::SingleSelect => {
let type_option = SingleSelectTypeOption::from(field);
let option = type_option.options.first().unwrap();
builder.add_select_option_cell(&field.id, option.id.clone()).unwrap();
}
FieldType::MultiSelect => {
let type_option = MultiSelectTypeOptionPB::from(field);
let ops_ids = type_option
.options
.iter()
.map(|option| option.id.clone())
.collect::<Vec<_>>()
.join(SELECTION_IDS_SEPARATOR);
builder.add_select_option_cell(&field.id, ops_ids).unwrap();
}
FieldType::Checkbox => {
builder.add_cell(&field.id, "false".to_string()).unwrap();
}
FieldType::URL => {
builder.add_cell(&field.id, "1".to_string()).unwrap();
}
}
}
let context = builder.build();
let scripts = vec![CreateRow { context }, AssertGridMetaPad];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_row_add_date_cell_test() {
let mut test = GridEditorTest::new().await;
let mut builder = CreateRowMetaBuilder::new(&test.field_metas);
let mut date_field = None;
let timestamp = 1647390674;
for field in &test.field_metas {
if field.field_type == FieldType::DateTime {
date_field = Some(field.clone());
NaiveDateTime::from_timestamp(123, 0);
// The data should not be empty
assert!(builder.add_cell(&field.id, "".to_string()).is_err());
assert!(builder.add_cell(&field.id, make_date_cell_string("123")).is_ok());
assert!(builder
.add_cell(&field.id, make_date_cell_string(&timestamp.to_string()))
.is_ok());
}
}
let context = builder.build();
let date_field = date_field.unwrap();
let cell_data = context.cell_by_field_id.get(&date_field.id).unwrap().clone();
assert_eq!(
decode_cell_data_from_type_option_cell_data(cell_data.data.clone(), &date_field, &date_field.field_type)
.parse::<DateCellData>()
.unwrap()
.date,
"2022/03/16",
);
let scripts = vec![CreateRow { context }];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_cell_update() {
let mut test = GridEditorTest::new().await;
let field_metas = &test.field_metas;
let row_metas = &test.row_metas;
let grid_blocks = &test.grid_blocks;
assert_eq!(row_metas.len(), 3);
assert_eq!(grid_blocks.len(), 1);
let block_id = &grid_blocks.first().unwrap().block_id;
let mut scripts = vec![];
for (index, row_meta) in row_metas.iter().enumerate() {
for field_meta in field_metas {
if index == 0 {
let data = match field_meta.field_type {
FieldType::RichText => "".to_string(),
FieldType::Number => "123".to_string(),
FieldType::DateTime => make_date_cell_string("123"),
FieldType::SingleSelect => {
let type_option = SingleSelectTypeOption::from(field_meta);
SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str()
}
FieldType::MultiSelect => {
let type_option = MultiSelectTypeOptionPB::from(field_meta);
SelectOptionCellContentChangeset::from_insert(&type_option.options.first().unwrap().id).to_str()
}
FieldType::Checkbox => "1".to_string(),
FieldType::URL => "1".to_string(),
};
scripts.push(UpdateCell {
changeset: CellChangeset {
database_id: block_id.to_string(),
row_id: row_meta.id.clone(),
field_id: field_meta.id.clone(),
cell_content_changeset: Some(data),
},
is_err: false,
});
}
if index == 1 {
let (data, is_err) = match field_meta.field_type {
FieldType::RichText => ("1".to_string().repeat(10001), true),
FieldType::Number => ("abc".to_string(), true),
FieldType::DateTime => ("abc".to_string(), true),
FieldType::SingleSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false),
FieldType::MultiSelect => (SelectOptionCellContentChangeset::from_insert("abc").to_str(), false),
FieldType::Checkbox => ("2".to_string(), false),
FieldType::URL => ("2".to_string(), false),
};
scripts.push(UpdateCell {
changeset: CellChangeset {
database_id: block_id.to_string(),
row_id: row_meta.id.clone(),
field_id: field_meta.id.clone(),
cell_content_changeset: Some(data),
},
is_err,
});
}
}
}
test.run_scripts(scripts).await;
}
fn make_date_cell_string(s: &str) -> String {
serde_json::to_string(&DateCellContentChangeset {
date: Some(s.to_string()),
time: None,
})
.unwrap()
}

View File

@ -1,2 +1,3 @@
mod script;
mod test;
mod url_group_test;

View File

@ -1,8 +1,8 @@
use crate::grid::grid_editor::GridEditorTest;
use crate::grid::database_editor::DatabaseEditorTest;
use flowy_database::entities::{
CreateRowParams, DatabaseViewLayout, FieldType, GroupPB, MoveGroupParams, MoveGroupRowParams, RowPB,
};
use flowy_database::services::cell::{delete_select_option_cell, insert_select_option_cell};
use flowy_database::services::cell::{delete_select_option_cell, insert_select_option_cell, insert_url_cell};
use flowy_database::services::field::{
edit_single_select_type_option, SelectOptionPB, SelectTypeOptionSharedAction, SingleSelectTypeOptionPB,
};
@ -37,11 +37,16 @@ pub enum GroupScript {
group_index: usize,
row_index: usize,
},
UpdateRow {
UpdateGroupedCell {
from_group_index: usize,
row_index: usize,
to_group_index: usize,
},
UpdateGroupedCellWithData {
from_group_index: usize,
row_index: usize,
cell_data: String,
},
MoveGroup {
from_group_index: usize,
to_group_index: usize,
@ -54,13 +59,13 @@ pub enum GroupScript {
},
}
pub struct GridGroupTest {
inner: GridEditorTest,
pub struct DatabaseGroupTest {
inner: DatabaseEditorTest,
}
impl GridGroupTest {
impl DatabaseGroupTest {
pub async fn new() -> Self {
let editor_test = GridEditorTest::new_board().await;
let editor_test = DatabaseEditorTest::new_board().await;
Self { inner: editor_test }
}
@ -109,7 +114,6 @@ impl GridGroupTest {
assert_eq!(row.id, compare_row.id);
}
GroupScript::CreateRow { group_index } => {
//
let group = self.group_at_index(group_index).await;
let params = CreateRowParams {
database_id: self.editor.database_id.clone(),
@ -123,7 +127,7 @@ impl GridGroupTest {
let row = self.row_at_index(group_index, row_index).await;
self.editor.delete_row(&row.id).await.unwrap();
}
GroupScript::UpdateRow {
GroupScript::UpdateGroupedCell {
from_group_index,
row_index,
to_group_index,
@ -154,6 +158,7 @@ impl GridGroupTest {
FieldType::MultiSelect => {
insert_select_option_cell(vec![to_group.group_id.clone()], &field_rev)
}
FieldType::URL => insert_url_cell(to_group.group_id.clone(), &field_rev),
_ => {
panic!("Unsupported group field type");
}
@ -165,6 +170,27 @@ impl GridGroupTest {
row_changeset.cell_by_field_id.insert(field_id, cell_rev);
self.editor.update_row(row_changeset).await.unwrap();
}
GroupScript::UpdateGroupedCellWithData {
from_group_index,
row_index,
cell_data,
} => {
let from_group = self.group_at_index(from_group_index).await;
let field_id = from_group.field_id;
let field_rev = self.editor.get_field_rev(&field_id).await.unwrap();
let field_type: FieldType = field_rev.ty.into();
let cell_rev = match field_type {
FieldType::URL => insert_url_cell(cell_data, &field_rev),
_ => {
panic!("Unsupported group field type");
}
};
let row_id = self.row_at_index(from_group_index, row_index).await.id;
let mut row_changeset = RowChangeset::new(row_id);
row_changeset.cell_by_field_id.insert(field_id, cell_rev);
self.editor.update_row(row_changeset).await.unwrap();
}
GroupScript::MoveGroup {
from_group_index,
to_group_index,
@ -258,15 +284,15 @@ impl GridGroupTest {
}
}
impl std::ops::Deref for GridGroupTest {
type Target = GridEditorTest;
impl std::ops::Deref for DatabaseGroupTest {
type Target = DatabaseEditorTest;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for GridGroupTest {
impl std::ops::DerefMut for DatabaseGroupTest {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}

View File

@ -1,11 +1,11 @@
use crate::grid::group_test::script::GridGroupTest;
use crate::grid::group_test::script::DatabaseGroupTest;
use crate::grid::group_test::script::GroupScript::*;
use flowy_database::services::field::SelectOptionPB;
#[tokio::test]
async fn group_init_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let scripts = vec![
AssertGroupCount(4),
AssertGroupRowCount {
@ -30,7 +30,7 @@ async fn group_init_test() {
#[tokio::test]
async fn group_move_row_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let group = test.group_at_index(1).await;
let scripts = vec![
// Move the row at 0 in group0 to group1 at 1
@ -55,7 +55,7 @@ async fn group_move_row_test() {
#[tokio::test]
async fn group_move_row_to_other_group_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let group = test.group_at_index(1).await;
let scripts = vec![
MoveRow {
@ -83,7 +83,7 @@ async fn group_move_row_to_other_group_test() {
#[tokio::test]
async fn group_move_two_row_to_other_group_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let group_1 = test.group_at_index(1).await;
let scripts = vec![
// Move row at index 0 from group 1 to group 2 at index 1
@ -137,7 +137,7 @@ async fn group_move_two_row_to_other_group_test() {
#[tokio::test]
async fn group_move_row_to_other_group_and_reorder_from_up_to_down_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let group_1 = test.group_at_index(1).await;
let group_2 = test.group_at_index(2).await;
let scripts = vec![
@ -173,7 +173,7 @@ async fn group_move_row_to_other_group_and_reorder_from_up_to_down_test() {
#[tokio::test]
async fn group_move_row_to_other_group_and_reorder_from_bottom_to_up_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let scripts = vec![MoveRow {
from_group_index: 1,
from_row_index: 0,
@ -204,7 +204,7 @@ async fn group_move_row_to_other_group_and_reorder_from_bottom_to_up_test() {
}
#[tokio::test]
async fn group_create_row_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let scripts = vec![
CreateRow { group_index: 1 },
AssertGroupRowCount {
@ -223,7 +223,7 @@ async fn group_create_row_test() {
#[tokio::test]
async fn group_delete_row_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let scripts = vec![
DeleteRow {
group_index: 1,
@ -239,7 +239,7 @@ async fn group_delete_row_test() {
#[tokio::test]
async fn group_delete_all_row_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let scripts = vec![
DeleteRow {
group_index: 1,
@ -259,10 +259,10 @@ async fn group_delete_all_row_test() {
#[tokio::test]
async fn group_update_row_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let scripts = vec![
// Update the row at 0 in group0 by setting the row's group field data
UpdateRow {
UpdateGroupedCell {
from_group_index: 1,
row_index: 0,
to_group_index: 2,
@ -281,10 +281,10 @@ async fn group_update_row_test() {
#[tokio::test]
async fn group_reorder_group_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let scripts = vec![
// Update the row at 0 in group0 by setting the row's group field data
UpdateRow {
UpdateGroupedCell {
from_group_index: 1,
row_index: 0,
to_group_index: 2,
@ -303,9 +303,9 @@ async fn group_reorder_group_test() {
#[tokio::test]
async fn group_move_to_default_group_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let scripts = vec![
UpdateRow {
UpdateGroupedCell {
from_group_index: 1,
row_index: 0,
to_group_index: 0,
@ -324,10 +324,10 @@ async fn group_move_to_default_group_test() {
#[tokio::test]
async fn group_move_from_default_group_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
// Move one row from group 1 to group 0
let scripts = vec![
UpdateRow {
UpdateGroupedCell {
from_group_index: 1,
row_index: 0,
to_group_index: 0,
@ -345,7 +345,7 @@ async fn group_move_from_default_group_test() {
// Move one row from group 0 to group 1
let scripts = vec![
UpdateRow {
UpdateGroupedCell {
from_group_index: 0,
row_index: 0,
to_group_index: 1,
@ -364,7 +364,7 @@ async fn group_move_from_default_group_test() {
#[tokio::test]
async fn group_move_group_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let group_0 = test.group_at_index(0).await;
let group_1 = test.group_at_index(1).await;
let scripts = vec![
@ -394,7 +394,7 @@ async fn group_move_group_test() {
#[tokio::test]
async fn group_move_group_row_after_move_group_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let group_1 = test.group_at_index(1).await;
let group_2 = test.group_at_index(2).await;
let scripts = vec![
@ -430,7 +430,7 @@ async fn group_move_group_row_after_move_group_test() {
#[tokio::test]
async fn group_move_group_to_default_group_pos_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let group_0 = test.group_at_index(0).await;
let group_3 = test.group_at_index(3).await;
let scripts = vec![
@ -452,7 +452,7 @@ async fn group_move_group_to_default_group_pos_test() {
#[tokio::test]
async fn group_insert_single_select_option_test() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let new_option_name = "New option";
let scripts = vec![
AssertGroupCount(4),
@ -468,7 +468,7 @@ async fn group_insert_single_select_option_test() {
#[tokio::test]
async fn group_group_by_other_field() {
let mut test = GridGroupTest::new().await;
let mut test = DatabaseGroupTest::new().await;
let multi_select_field = test.get_multi_select_field().await;
let scripts = vec![
GroupByField {
@ -486,52 +486,3 @@ async fn group_group_by_other_field() {
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn group_group_by_url() {
let mut test = GridGroupTest::new().await;
let url_field = test.get_url_field().await;
let scripts = vec![
GroupByField {
field_id: url_field.id.clone(),
},
AssertGroupRowCount {
group_index: 0,
row_count: 2,
},
AssertGroupRowCount {
group_index: 1,
row_count: 2,
},
AssertGroupRowCount {
group_index: 2,
row_count: 1,
},
AssertGroupCount(3),
MoveRow {
from_group_index: 0,
from_row_index: 0,
to_group_index: 1,
to_row_index: 0,
},
MoveRow {
from_group_index: 1,
from_row_index: 0,
to_group_index: 2,
to_row_index: 0,
},
AssertGroupRowCount {
group_index: 0,
row_count: 1,
},
AssertGroupRowCount {
group_index: 1,
row_count: 2,
},
AssertGroupRowCount {
group_index: 2,
row_count: 2,
},
];
test.run_scripts(scripts).await;
}

View File

@ -0,0 +1,148 @@
use crate::grid::group_test::script::DatabaseGroupTest;
use crate::grid::group_test::script::GroupScript::*;
#[tokio::test]
async fn group_group_by_url() {
let mut test = DatabaseGroupTest::new().await;
let url_field = test.get_url_field().await;
let scripts = vec![
GroupByField {
field_id: url_field.id.clone(),
},
// no status group
AssertGroupRowCount {
group_index: 0,
row_count: 2,
},
// https://appflowy.io
AssertGroupRowCount {
group_index: 1,
row_count: 2,
},
// https://github.com/AppFlowy-IO/AppFlowy
AssertGroupRowCount {
group_index: 2,
row_count: 1,
},
AssertGroupCount(3),
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn group_alter_url_to_another_group_url_test() {
let mut test = DatabaseGroupTest::new().await;
let url_field = test.get_url_field().await;
let scripts = vec![
GroupByField {
field_id: url_field.id.clone(),
},
// no status group
AssertGroupRowCount {
group_index: 0,
row_count: 2,
},
// https://appflowy.io
AssertGroupRowCount {
group_index: 1,
row_count: 2,
},
// https://github.com/AppFlowy-IO/AppFlowy
AssertGroupRowCount {
group_index: 2,
row_count: 1,
},
// When moving the last row from 2nd group to 1nd group, the 2nd group will be removed
UpdateGroupedCell {
from_group_index: 2,
row_index: 0,
to_group_index: 1,
},
AssertGroupCount(2),
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn group_alter_url_to_new_url_test() {
let mut test = DatabaseGroupTest::new().await;
let url_field = test.get_url_field().await;
let scripts = vec![
GroupByField {
field_id: url_field.id.clone(),
},
// When moving the last row from 2nd group to 1nd group, the 2nd group will be removed
UpdateGroupedCellWithData {
from_group_index: 0,
row_index: 0,
cell_data: "https://github.com/AppFlowy-IO".to_string(),
},
// no status group
AssertGroupRowCount {
group_index: 0,
row_count: 1,
},
// https://appflowy.io
AssertGroupRowCount {
group_index: 1,
row_count: 2,
},
// https://github.com/AppFlowy-IO/AppFlowy
AssertGroupRowCount {
group_index: 2,
row_count: 1,
},
AssertGroupRowCount {
group_index: 3,
row_count: 1,
},
AssertGroupCount(4),
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn group_move_url_group_row_test() {
let mut test = DatabaseGroupTest::new().await;
let url_field = test.get_url_field().await;
let scripts = vec![
GroupByField {
field_id: url_field.id.clone(),
},
// no status group
AssertGroupRowCount {
group_index: 0,
row_count: 2,
},
// https://appflowy.io
AssertGroupRowCount {
group_index: 1,
row_count: 2,
},
// https://github.com/AppFlowy-IO/AppFlowy
AssertGroupRowCount {
group_index: 2,
row_count: 1,
},
AssertGroupCount(3),
MoveRow {
from_group_index: 0,
from_row_index: 0,
to_group_index: 1,
to_row_index: 0,
},
AssertGroupRowCount {
group_index: 0,
row_count: 1,
},
AssertGroupRowCount {
group_index: 1,
row_count: 3,
},
AssertGroupRowCount {
group_index: 2,
row_count: 1,
},
];
test.run_scripts(scripts).await;
}

View File

@ -0,0 +1,191 @@
// #![allow(clippy::all)]
// #![allow(dead_code)]
// #![allow(unused_imports)]
use crate::grid::block_test::util::GridRowTestBuilder;
use crate::grid::mock_data::{
COMPLETED, FACEBOOK, FIRST_THING, GOOGLE, PAUSED, PLANNED, SECOND_THING, THIRD_THING, TWITTER,
};
use flowy_client_sync::client_database::DatabaseBuilder;
use flowy_database::entities::*;
use flowy_database::services::field::SelectOptionPB;
use flowy_database::services::field::*;
use grid_model::*;
use strum::IntoEnumIterator;
// Kanban board unit test mock data
pub fn make_test_board() -> BuildDatabaseContext {
let mut grid_builder = DatabaseBuilder::new();
// Iterate through the FieldType to create the corresponding Field.
for field_type in FieldType::iter() {
let field_type: FieldType = field_type;
// The
match field_type {
FieldType::RichText => {
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Name")
.visibility(true)
.primary(true)
.build();
grid_builder.add_field(text_field);
}
FieldType::Number => {
// Number
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
grid_builder.add_field(number_field);
}
FieldType::DateTime => {
// Date
let date = DateTypeOptionBuilder::default()
.date_format(DateFormat::US)
.time_format(TimeFormat::TwentyFourHour);
let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
grid_builder.add_field(date_field);
}
FieldType::SingleSelect => {
// Single Select
let single_select = SingleSelectTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(COMPLETED))
.add_option(SelectOptionPB::new(PLANNED))
.add_option(SelectOptionPB::new(PAUSED));
let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
grid_builder.add_field(single_select_field);
}
FieldType::MultiSelect => {
// MultiSelect
let multi_select = MultiSelectTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(GOOGLE))
.add_option(SelectOptionPB::new(FACEBOOK))
.add_option(SelectOptionPB::new(TWITTER));
let multi_select_field = FieldBuilder::new(multi_select)
.name("Platform")
.visibility(true)
.build();
grid_builder.add_field(multi_select_field);
}
FieldType::Checkbox => {
// Checkbox
let checkbox = CheckboxTypeOptionBuilder::default();
let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build();
grid_builder.add_field(checkbox_field);
}
FieldType::URL => {
// URL
let url = URLTypeOptionBuilder::default();
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
grid_builder.add_field(url_field);
}
FieldType::Checklist => {
let checklist = ChecklistTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(FIRST_THING))
.add_option(SelectOptionPB::new(SECOND_THING))
.add_option(SelectOptionPB::new(THIRD_THING));
let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
grid_builder.add_field(checklist_field);
}
}
}
// We have many assumptions base on the number of the rows, so do not change the number of the loop.
for i in 0..5 {
let block_id = grid_builder.block_id().to_owned();
let field_revs = grid_builder.field_revs();
let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs);
match i {
0 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("A"),
FieldType::Number => row_builder.insert_number_cell("1"),
// 1647251762 => Mar 14,2022
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
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(0), options.remove(0)]),
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"),
_ => "".to_owned(),
};
}
}
1 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("B"),
FieldType::Number => row_builder.insert_number_cell("2"),
// 1647251762 => Mar 14,2022
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
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(0), options.remove(0)]),
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
_ => "".to_owned(),
};
}
}
2 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("C"),
FieldType::Number => row_builder.insert_number_cell("3"),
// 1647251762 => Mar 14,2022
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
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(0)])
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
FieldType::URL => row_builder.insert_url_cell("https://github.com/AppFlowy-IO/AppFlowy"),
_ => "".to_owned(),
};
}
}
3 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("DA"),
FieldType::Number => row_builder.insert_number_cell("4"),
FieldType::DateTime => row_builder.insert_date_cell("1668704685"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(1))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
FieldType::URL => row_builder.insert_url_cell("https://appflowy.io"),
_ => "".to_owned(),
};
}
}
4 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("AE"),
FieldType::Number => row_builder.insert_number_cell(""),
FieldType::DateTime => row_builder.insert_date_cell("1668359085"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(2))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
_ => "".to_owned(),
};
}
}
_ => {}
}
let row_rev = row_builder.build();
grid_builder.add_row(row_rev);
}
grid_builder.build()
}

View File

@ -0,0 +1,6 @@
use grid_model::BuildDatabaseContext;
// Calendar unit test mock data
pub fn make_test_calendar() -> BuildDatabaseContext {
todo!()
}

View File

@ -0,0 +1,193 @@
// #![allow(clippy::all)]
// #![allow(dead_code)]
// #![allow(unused_imports)]
use crate::grid::block_test::util::GridRowTestBuilder;
use crate::grid::mock_data::{
COMPLETED, FACEBOOK, FIRST_THING, GOOGLE, PAUSED, PLANNED, SECOND_THING, THIRD_THING, TWITTER,
};
use flowy_client_sync::client_database::DatabaseBuilder;
use flowy_database::entities::*;
use flowy_database::services::field::SelectOptionPB;
use flowy_database::services::field::*;
use grid_model::*;
use strum::IntoEnumIterator;
pub fn make_test_grid() -> BuildDatabaseContext {
let mut grid_builder = DatabaseBuilder::new();
// Iterate through the FieldType to create the corresponding Field.
for field_type in FieldType::iter() {
let field_type: FieldType = field_type;
// The
match field_type {
FieldType::RichText => {
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Name")
.visibility(true)
.primary(true)
.build();
grid_builder.add_field(text_field);
}
FieldType::Number => {
// Number
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
grid_builder.add_field(number_field);
}
FieldType::DateTime => {
// Date
let date = DateTypeOptionBuilder::default()
.date_format(DateFormat::US)
.time_format(TimeFormat::TwentyFourHour);
let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
grid_builder.add_field(date_field);
}
FieldType::SingleSelect => {
// Single Select
let single_select = SingleSelectTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(COMPLETED))
.add_option(SelectOptionPB::new(PLANNED))
.add_option(SelectOptionPB::new(PAUSED));
let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
grid_builder.add_field(single_select_field);
}
FieldType::MultiSelect => {
// MultiSelect
let multi_select = MultiSelectTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(GOOGLE))
.add_option(SelectOptionPB::new(FACEBOOK))
.add_option(SelectOptionPB::new(TWITTER));
let multi_select_field = FieldBuilder::new(multi_select)
.name("Platform")
.visibility(true)
.build();
grid_builder.add_field(multi_select_field);
}
FieldType::Checkbox => {
// Checkbox
let checkbox = CheckboxTypeOptionBuilder::default();
let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build();
grid_builder.add_field(checkbox_field);
}
FieldType::URL => {
// URL
let url = URLTypeOptionBuilder::default();
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
grid_builder.add_field(url_field);
}
FieldType::Checklist => {
let checklist = ChecklistTypeOptionBuilder::default()
.add_option(SelectOptionPB::new(FIRST_THING))
.add_option(SelectOptionPB::new(SECOND_THING))
.add_option(SelectOptionPB::new(THIRD_THING));
let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
grid_builder.add_field(checklist_field);
}
}
}
for i in 0..6 {
let block_id = grid_builder.block_id().to_owned();
let field_revs = grid_builder.field_revs();
let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs);
match i {
0 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("A"),
FieldType::Number => row_builder.insert_number_cell("1"),
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
FieldType::MultiSelect => row_builder
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]),
FieldType::Checklist => row_builder.insert_checklist_cell(|options| options),
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
FieldType::URL => row_builder.insert_url_cell("AppFlowy website - https://www.appflowy.io"),
_ => "".to_owned(),
};
}
}
1 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell(""),
FieldType::Number => row_builder.insert_number_cell("2"),
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
FieldType::MultiSelect => row_builder
.insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(1)]),
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
_ => "".to_owned(),
};
}
}
2 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("C"),
FieldType::Number => row_builder.insert_number_cell("3"),
FieldType::DateTime => row_builder.insert_date_cell("1647251762"),
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::Checkbox => row_builder.insert_checkbox_cell("false"),
_ => "".to_owned(),
};
}
}
3 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("DA"),
FieldType::Number => row_builder.insert_number_cell("4"),
FieldType::DateTime => row_builder.insert_date_cell("1668704685"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(0))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
_ => "".to_owned(),
};
}
}
4 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("AE"),
FieldType::Number => row_builder.insert_number_cell(""),
FieldType::DateTime => row_builder.insert_date_cell("1668359085"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(1))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
_ => "".to_owned(),
};
}
}
5 => {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("AE"),
FieldType::Number => row_builder.insert_number_cell("5"),
FieldType::DateTime => row_builder.insert_date_cell("1671938394"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(1))
}
FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
_ => "".to_owned(),
};
}
}
_ => {}
}
let row_rev = row_builder.build();
grid_builder.add_row(row_rev);
}
grid_builder.build()
}

View File

@ -0,0 +1,19 @@
mod board_mock_data;
mod calendar_mock_data;
mod grid_mock_data;
pub use board_mock_data::*;
pub use calendar_mock_data::*;
pub use grid_mock_data::*;
pub const GOOGLE: &str = "Google";
pub const FACEBOOK: &str = "Facebook";
pub const TWITTER: &str = "Twitter";
pub const COMPLETED: &str = "Completed";
pub const PLANNED: &str = "Planned";
pub const PAUSED: &str = "Paused";
pub const FIRST_THING: &str = "Wake up at 6:00 am";
pub const SECOND_THING: &str = "Get some coffee";
pub const THIRD_THING: &str = "Start working";

View File

@ -1,8 +1,10 @@
mod block_test;
mod cell_test;
mod database_editor;
mod field_test;
mod filter_test;
mod grid_editor;
mod group_test;
mod snapshot_test;
mod sort_test;
mod mock_data;

View File

@ -1,374 +0,0 @@
use bytes::Bytes;
use flowy_client_sync::client_grid::GridBuilder;
use flowy_database::services::field::*;
use flowy_database::services::grid_meta_editor::{GridMetaEditor, GridPadBuilder};
use flowy_database::services::row::CreateRowMetaPayload;
use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
use flowy_test::helper::ViewTest;
use flowy_test::FlowySDKTest;
use grid_model::entities::{
BuildGridContext, CellChangeset, Field, FieldChangesetParams, FieldMeta, FieldOrder, FieldType,
GridBlockInfoChangeset, GridBlockMetaSnapshot, InsertFieldParams, RowMeta, RowMetaChangeset, RowOrder,
TypeOptionDataFormat,
};
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use strum::EnumCount;
use tokio::time::sleep;
pub enum EditorScript {
CreateField {
params: InsertFieldParams,
},
UpdateField {
changeset: FieldChangesetParams,
},
DeleteField {
field_meta: FieldMeta,
},
AssertFieldCount(usize),
AssertFieldEqual {
field_index: usize,
field_meta: FieldMeta,
},
CreateBlock {
block: GridBlockMetaSnapshot,
},
UpdateBlock {
changeset: GridBlockInfoChangeset,
},
AssertBlockCount(usize),
AssertBlock {
block_index: usize,
row_count: i32,
start_row_index: i32,
},
AssertBlockEqual {
block_index: usize,
block: GridBlockMetaSnapshot,
},
CreateEmptyRow,
CreateRow {
context: CreateRowMetaPayload,
},
UpdateRow {
changeset: RowMetaChangeset,
},
AssertRow {
changeset: RowMetaChangeset,
},
DeleteRow {
row_ids: Vec<String>,
},
UpdateCell {
changeset: CellChangeset,
is_err: bool,
},
AssertRowCount(usize),
// AssertRowEqual{ row_index: usize, row: RowMeta},
AssertGridMetaPad,
}
pub struct GridEditorTest {
pub sdk: FlowySDKTest,
pub grid_id: String,
pub editor: Arc<GridMetaEditor>,
pub field_metas: Vec<FieldMeta>,
pub grid_blocks: Vec<GridBlockMetaSnapshot>,
pub row_metas: Vec<Arc<RowMeta>>,
pub field_count: usize,
pub row_order_by_row_id: HashMap<String, RowOrder>,
}
impl GridEditorTest {
pub async fn new() -> Self {
let sdk = FlowySDKTest::default();
let _ = sdk.init_user().await;
let build_context = make_template_1_grid();
let view_data: Bytes = build_context.into();
let test = ViewTest::new_grid_view(&sdk, view_data.to_vec()).await;
let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap();
let field_metas = editor.get_field_metas::<FieldOrder>(None).await.unwrap();
let grid_blocks = editor.get_block_metas().await.unwrap();
let row_metas = get_row_metas(&editor).await;
let grid_id = test.view.id;
Self {
sdk,
grid_id,
editor,
field_metas,
grid_blocks,
row_metas,
field_count: FieldType::COUNT,
row_order_by_row_id: HashMap::default(),
}
}
pub async fn run_scripts(&mut self, scripts: Vec<EditorScript>) {
for script in scripts {
self.run_script(script).await;
}
}
pub async fn run_script(&mut self, script: EditorScript) {
let grid_manager = self.sdk.grid_manager.clone();
let pool = self.sdk.user_session.db_pool().unwrap();
let rev_manager = self.editor.rev_manager();
let _cache = rev_manager.revision_cache().await;
match script {
EditorScript::CreateField { params } => {
if !self.editor.contain_field(&params.field.id).await {
self.field_count += 1;
}
self.editor.insert_field(params).await.unwrap();
self.field_metas = self.editor.get_field_metas::<FieldOrder>(None).await.unwrap();
assert_eq!(self.field_count, self.field_metas.len());
}
EditorScript::UpdateField { changeset: change } => {
self.editor.update_field(change).await.unwrap();
self.field_metas = self.editor.get_field_metas::<FieldOrder>(None).await.unwrap();
}
EditorScript::DeleteField { field_meta } => {
if self.editor.contain_field(&field_meta.id).await {
self.field_count -= 1;
}
self.editor.delete_field(&field_meta.id).await.unwrap();
self.field_metas = self.editor.get_field_metas::<FieldOrder>(None).await.unwrap();
assert_eq!(self.field_count, self.field_metas.len());
}
EditorScript::AssertFieldCount(count) => {
assert_eq!(
self.editor.get_field_metas::<FieldOrder>(None).await.unwrap().len(),
count
);
}
EditorScript::AssertFieldEqual {
field_index,
field_meta,
} => {
let field_metas = self.editor.get_field_metas::<FieldOrder>(None).await.unwrap();
assert_eq!(field_metas[field_index].clone(), field_meta);
}
EditorScript::CreateBlock { block } => {
self.editor.create_block(block).await.unwrap();
self.grid_blocks = self.editor.get_block_metas().await.unwrap();
}
EditorScript::UpdateBlock { changeset: change } => {
self.editor.update_block(change).await.unwrap();
}
EditorScript::AssertBlockCount(count) => {
assert_eq!(self.editor.get_block_metas().await.unwrap().len(), count);
}
EditorScript::AssertBlock {
block_index,
row_count,
start_row_index,
} => {
assert_eq!(self.grid_blocks[block_index].row_count, row_count);
assert_eq!(self.grid_blocks[block_index].start_row_index, start_row_index);
}
EditorScript::AssertBlockEqual { block_index, block } => {
let blocks = self.editor.get_block_metas().await.unwrap();
let compared_block = blocks[block_index].clone();
assert_eq!(compared_block, block);
}
EditorScript::CreateEmptyRow => {
let row_order = self.editor.create_row(None).await.unwrap();
self.row_order_by_row_id.insert(row_order.row_id.clone(), row_order);
self.row_metas = self.get_row_metas().await;
self.grid_blocks = self.editor.get_block_metas().await.unwrap();
}
EditorScript::CreateRow { context } => {
let row_orders = self.editor.insert_rows(vec![context]).await.unwrap();
for row_order in row_orders {
self.row_order_by_row_id.insert(row_order.row_id.clone(), row_order);
}
self.row_metas = self.get_row_metas().await;
self.grid_blocks = self.editor.get_block_metas().await.unwrap();
}
EditorScript::UpdateRow { changeset: change } => self.editor.update_row(change).await.unwrap(),
EditorScript::DeleteRow { row_ids } => {
let row_orders = row_ids
.into_iter()
.map(|row_id| self.row_order_by_row_id.get(&row_id).unwrap().clone())
.collect::<Vec<RowOrder>>();
self.editor.delete_rows(row_orders).await.unwrap();
self.row_metas = self.get_row_metas().await;
self.grid_blocks = self.editor.get_block_metas().await.unwrap();
}
EditorScript::AssertRow { changeset } => {
let row = self.row_metas.iter().find(|row| row.id == changeset.row_id).unwrap();
if let Some(visibility) = changeset.visibility {
assert_eq!(row.visibility, visibility);
}
if let Some(height) = changeset.height {
assert_eq!(row.height, height);
}
}
EditorScript::UpdateCell { changeset, is_err } => {
let result = self.editor.update_cell(changeset).await;
if is_err {
assert!(result.is_err())
} else {
let _ = result.unwrap();
self.row_metas = self.get_row_metas().await;
}
}
EditorScript::AssertRowCount(count) => {
assert_eq!(self.row_metas.len(), count);
}
EditorScript::AssertGridMetaPad => {
sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await;
let mut grid_rev_manager = grid_manager.make_grid_rev_manager(&self.grid_id, pool.clone()).unwrap();
let grid_pad = grid_rev_manager.load::<GridPadBuilder>(None).await.unwrap();
println!("{}", grid_pad.delta_str());
}
}
}
async fn get_row_metas(&self) -> Vec<Arc<RowMeta>> {
get_row_metas(&self.editor).await
}
}
async fn get_row_metas(editor: &Arc<GridMetaEditor>) -> Vec<Arc<RowMeta>> {
editor
.grid_block_snapshots(None)
.await
.unwrap()
.pop()
.unwrap()
.row_metas
}
pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldMeta) {
let field_meta = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Name")
.visibility(true)
.build();
let cloned_field_meta = field_meta.clone();
let type_option_data = field_meta
.get_type_option_entry::<RichTextTypeOptionPB>(&field_meta.field_type)
.unwrap()
.protobuf_bytes()
.to_vec();
let field = Field {
id: field_meta.id,
name: field_meta.name,
desc: field_meta.desc,
field_type: field_meta.field_type,
frozen: field_meta.frozen,
visibility: field_meta.visibility,
width: field_meta.width,
is_primary: false,
};
let params = InsertFieldParams {
grid_id: grid_id.to_owned(),
field,
type_option_data,
start_field_id: None,
};
(params, cloned_field_meta)
}
pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldMeta) {
let single_select = SingleSelectTypeOptionBuilder::default()
.option(SelectOption::new("Done"))
.option(SelectOption::new("Progress"));
let field_meta = FieldBuilder::new(single_select).name("Name").visibility(true).build();
let cloned_field_meta = field_meta.clone();
let type_option_data = field_meta
.get_type_option_entry::<SingleSelectTypeOption>(&field_meta.field_type)
.unwrap()
.protobuf_bytes()
.to_vec();
let field = Field {
id: field_meta.id,
name: field_meta.name,
desc: field_meta.desc,
field_type: field_meta.field_type,
frozen: field_meta.frozen,
visibility: field_meta.visibility,
width: field_meta.width,
is_primary: false,
};
let params = InsertFieldParams {
grid_id: grid_id.to_owned(),
field,
type_option_data,
start_field_id: None,
};
(params, cloned_field_meta)
}
fn make_template_1_grid() -> BuildGridContext {
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Name")
.visibility(true)
.build();
// Single Select
let single_select = SingleSelectTypeOptionBuilder::default()
.option(SelectOption::new("Live"))
.option(SelectOption::new("Completed"))
.option(SelectOption::new("Planned"))
.option(SelectOption::new("Paused"));
let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
// MultiSelect
let multi_select = MultiSelectTypeOptionBuilder::default()
.option(SelectOption::new("Google"))
.option(SelectOption::new("Facebook"))
.option(SelectOption::new("Twitter"));
let multi_select_field = FieldBuilder::new(multi_select)
.name("Platform")
.visibility(true)
.build();
// Number
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
// Date
let date = DateTypeOptionBuilder::default()
.date_format(DateFormat::US)
.time_format(TimeFormat::TwentyFourHour);
let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
// Checkbox
let checkbox = CheckboxTypeOptionBuilder::default();
let checkbox_field = FieldBuilder::new(checkbox).name("is done").visibility(true).build();
// URL
let url = URLTypeOptionBuilder::default();
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
GridBuilder::default()
.add_field(text_field)
.add_field(single_select_field)
.add_field(multi_select_field)
.add_field(number_field)
.add_field(date_field)
.add_field(checkbox_field)
.add_field(url_field)
.add_empty_row()
.add_empty_row()
.add_empty_row()
.build()
}

View File

@ -1,4 +1,4 @@
use crate::grid::grid_editor::GridEditorTest;
use crate::grid::database_editor::DatabaseEditorTest;
use flowy_client_sync::client_database::{DatabaseOperations, DatabaseRevisionPad};
use flowy_revision::{RevisionSnapshot, REVISION_WRITE_INTERVAL_IN_MILLIS};
@ -26,15 +26,15 @@ pub enum SnapshotScript {
},
}
pub struct GridSnapshotTest {
inner: GridEditorTest,
pub struct DatabaseSnapshotTest {
inner: DatabaseEditorTest,
pub current_snapshot: Option<RevisionSnapshot>,
pub current_revision: Option<Revision>,
}
impl GridSnapshotTest {
impl DatabaseSnapshotTest {
pub async fn new() -> Self {
let editor_test = GridEditorTest::new_table().await;
let editor_test = DatabaseEditorTest::new_table().await;
Self {
inner: editor_test,
current_snapshot: None,
@ -88,15 +88,15 @@ impl GridSnapshotTest {
}
}
}
impl std::ops::Deref for GridSnapshotTest {
type Target = GridEditorTest;
impl std::ops::Deref for DatabaseSnapshotTest {
type Target = DatabaseEditorTest;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for GridSnapshotTest {
impl std::ops::DerefMut for DatabaseSnapshotTest {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}

View File

@ -1,9 +1,9 @@
use crate::grid::field_test::util::create_text_field;
use crate::grid::snapshot_test::script::{GridSnapshotTest, SnapshotScript::*};
use crate::grid::snapshot_test::script::{DatabaseSnapshotTest, SnapshotScript::*};
#[tokio::test]
async fn snapshot_create_test() {
let mut test = GridSnapshotTest::new().await;
let mut test = DatabaseSnapshotTest::new().await;
let (_, field_rev) = create_text_field(&test.grid_id());
let scripts = vec![CreateField { field_rev }, WriteSnapshot];
test.run_scripts(scripts).await;
@ -19,7 +19,7 @@ async fn snapshot_create_test() {
#[tokio::test]
async fn snapshot_multi_version_test() {
let mut test = GridSnapshotTest::new().await;
let mut test = DatabaseSnapshotTest::new().await;
let original_content = test.grid_pad().await.json_str().unwrap();
// Create a field

View File

@ -1,10 +1,10 @@
use crate::grid::sort_test::script::{GridSortTest, SortScript::*};
use crate::grid::sort_test::script::{DatabaseSortTest, SortScript::*};
use flowy_database::entities::FieldType;
use grid_model::SortCondition;
#[tokio::test]
async fn sort_checkbox_and_then_text_by_descending_test() {
let mut test = GridSortTest::new().await;
let mut test = DatabaseSortTest::new().await;
let checkbox_field = test.get_first_field_rev(FieldType::Checkbox);
let text_field = test.get_first_field_rev(FieldType::RichText);
let scripts = vec![

View File

@ -1,11 +1,11 @@
use crate::grid::sort_test::script::GridSortTest;
use crate::grid::sort_test::script::DatabaseSortTest;
use crate::grid::sort_test::script::SortScript::*;
use flowy_database::entities::FieldType;
use grid_model::SortCondition;
#[tokio::test]
async fn sort_text_with_checkbox_by_ascending_test() {
let mut test = GridSortTest::new().await;
let mut test = DatabaseSortTest::new().await;
let text_field = test.get_first_field_rev(FieldType::RichText).clone();
let checkbox_field = test.get_first_field_rev(FieldType::Checkbox).clone();
let scripts = vec![

View File

@ -1,4 +1,4 @@
use crate::grid::grid_editor::GridEditorTest;
use crate::grid::database_editor::DatabaseEditorTest;
use async_stream::stream;
use flowy_database::entities::{AlterSortParams, CellPathParams, DeleteSortParams};
use flowy_database::services::sort::SortType;
@ -36,15 +36,15 @@ pub enum SortScript {
},
}
pub struct GridSortTest {
inner: GridEditorTest,
pub struct DatabaseSortTest {
inner: DatabaseEditorTest,
pub current_sort_rev: Option<SortRevision>,
recv: Option<Receiver<GridViewChanged>>,
}
impl GridSortTest {
impl DatabaseSortTest {
pub async fn new() -> Self {
let editor_test = GridEditorTest::new_table().await;
let editor_test = DatabaseEditorTest::new_table().await;
Self {
inner: editor_test,
current_sort_rev: None,
@ -154,15 +154,15 @@ async fn assert_sort_changed(
.await;
}
impl std::ops::Deref for GridSortTest {
type Target = GridEditorTest;
impl std::ops::Deref for DatabaseSortTest {
type Target = DatabaseEditorTest;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for GridSortTest {
impl std::ops::DerefMut for DatabaseSortTest {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}

View File

@ -1,10 +1,10 @@
use crate::grid::sort_test::script::{GridSortTest, SortScript::*};
use crate::grid::sort_test::script::{DatabaseSortTest, SortScript::*};
use flowy_database::entities::FieldType;
use grid_model::SortCondition;
#[tokio::test]
async fn sort_text_by_ascending_test() {
let mut test = GridSortTest::new().await;
let mut test = DatabaseSortTest::new().await;
let text_field = test.get_first_field_rev(FieldType::RichText);
let scripts = vec![
AssertCellContentOrder {
@ -25,7 +25,7 @@ async fn sort_text_by_ascending_test() {
#[tokio::test]
async fn sort_change_notification_by_update_text_test() {
let mut test = GridSortTest::new().await;
let mut test = DatabaseSortTest::new().await;
let text_field = test.get_first_field_rev(FieldType::RichText).clone();
let scripts = vec![
InsertSort {
@ -57,7 +57,7 @@ async fn sort_change_notification_by_update_text_test() {
#[tokio::test]
async fn sort_text_by_ascending_and_delete_sort_test() {
let mut test = GridSortTest::new().await;
let mut test = DatabaseSortTest::new().await;
let text_field = test.get_first_field_rev(FieldType::RichText).clone();
let scripts = vec![InsertSort {
field_rev: text_field.clone(),
@ -80,7 +80,7 @@ async fn sort_text_by_ascending_and_delete_sort_test() {
#[tokio::test]
async fn sort_text_by_descending_test() {
let mut test = GridSortTest::new().await;
let mut test = DatabaseSortTest::new().await;
let text_field = test.get_first_field_rev(FieldType::RichText);
let scripts = vec![
AssertCellContentOrder {
@ -101,7 +101,7 @@ async fn sort_text_by_descending_test() {
#[tokio::test]
async fn sort_checkbox_by_ascending_test() {
let mut test = GridSortTest::new().await;
let mut test = DatabaseSortTest::new().await;
let checkbox_field = test.get_first_field_rev(FieldType::Checkbox);
let scripts = vec![
AssertCellContentOrder {
@ -118,7 +118,7 @@ async fn sort_checkbox_by_ascending_test() {
#[tokio::test]
async fn sort_checkbox_by_descending_test() {
let mut test = GridSortTest::new().await;
let mut test = DatabaseSortTest::new().await;
let checkbox_field = test.get_first_field_rev(FieldType::Checkbox);
let scripts = vec![
AssertCellContentOrder {
@ -139,7 +139,7 @@ async fn sort_checkbox_by_descending_test() {
#[tokio::test]
async fn sort_date_by_ascending_test() {
let mut test = GridSortTest::new().await;
let mut test = DatabaseSortTest::new().await;
let date_field = test.get_first_field_rev(FieldType::DateTime);
let scripts = vec![
AssertCellContentOrder {
@ -160,7 +160,7 @@ async fn sort_date_by_ascending_test() {
#[tokio::test]
async fn sort_date_by_descending_test() {
let mut test = GridSortTest::new().await;
let mut test = DatabaseSortTest::new().await;
let date_field = test.get_first_field_rev(FieldType::DateTime);
let scripts = vec![
AssertCellContentOrder {
@ -195,7 +195,7 @@ async fn sort_date_by_descending_test() {
#[tokio::test]
async fn sort_number_by_descending_test() {
let mut test = GridSortTest::new().await;
let mut test = DatabaseSortTest::new().await;
let number_field = test.get_first_field_rev(FieldType::Number);
let scripts = vec![
AssertCellContentOrder {
@ -216,7 +216,7 @@ async fn sort_number_by_descending_test() {
#[tokio::test]
async fn sort_single_select_by_descending_test() {
let mut test = GridSortTest::new().await;
let mut test = DatabaseSortTest::new().await;
let single_select = test.get_first_field_rev(FieldType::SingleSelect);
let scripts = vec![
AssertCellContentOrder {
@ -237,7 +237,7 @@ async fn sort_single_select_by_descending_test() {
#[tokio::test]
async fn sort_multi_select_by_ascending_test() {
let mut test = GridSortTest::new().await;
let mut test = DatabaseSortTest::new().await;
let multi_select = test.get_first_field_rev(FieldType::MultiSelect);
let scripts = vec![
AssertCellContentOrder {

View File

@ -5,8 +5,6 @@ use std::convert::TryInto;
impl lib_dispatch::Error for FlowyError {
fn as_response(&self) -> AFPluginEventResponse {
let bytes: Bytes = self.clone().try_into().unwrap();
println!("Serialize FlowyError: {:?} to event response", self);
ResponseBuilder::Err().data(bytes).build()
}
}

View File

@ -3,7 +3,7 @@ pub mod helper;
use crate::helper::*;
use flowy_core::{FlowySDK, FlowySDKConfig};
use flowy_core::{AppFlowyCore, AppFlowyCoreConfig};
use flowy_document::entities::DocumentVersionPB;
use flowy_net::get_client_server_configuration;
use flowy_user::entities::UserProfilePB;
@ -16,11 +16,11 @@ pub mod prelude {
#[derive(Clone)]
pub struct FlowySDKTest {
pub inner: FlowySDK,
pub inner: AppFlowyCore,
}
impl std::ops::Deref for FlowySDKTest {
type Target = FlowySDK;
type Target = AppFlowyCore;
fn deref(&self) -> &Self::Target {
&self.inner
@ -36,10 +36,10 @@ impl std::default::Default for FlowySDKTest {
impl FlowySDKTest {
pub fn new(document_version: DocumentVersionPB) -> Self {
let server_config = get_client_server_configuration().unwrap();
let config = FlowySDKConfig::new(&root_dir(), nanoid!(6), server_config)
let config = AppFlowyCoreConfig::new(&root_dir(), nanoid!(6), server_config)
.with_document_version(document_version)
.log_filter("info", vec![]);
let sdk = std::thread::spawn(|| FlowySDK::new(config)).join().unwrap();
let sdk = std::thread::spawn(|| AppFlowyCore::new(config)).join().unwrap();
std::mem::forget(sdk.dispatcher());
Self { inner: sdk }
}

View File

@ -1,3 +1,3 @@
echo "Start building rust sdk"
rustup show
cargo make --profile development-windows-x86 appflowy-sdk-dev
cargo make --profile development-windows-x86 appflowy-core-dev

View File

@ -17,15 +17,15 @@ rustup show
case "$FLOWY_DEV_ENV" in
Linux)
cargo make --profile "development-linux-$(uname -m)" appflowy-sdk-dev
cargo make --profile "development-linux-$(uname -m)" appflowy-core-dev
;;
macOS)
cargo make --profile "development-mac-$(uname -m)" appflowy-sdk-dev
cargo make --profile "development-mac-$(uname -m)" appflowy-core-dev
;;
Windows)
cargo make --profile development-windows appflowy-sdk-dev
cargo make --profile development-windows appflowy-core-dev
;;
*)

View File

@ -1,8 +1,3 @@
# cargo make --profile production task
# Run the task with profile, e.g.
# cargo make --profile development-mac appflowy-sdk-dev
# cargo make --profile production-windows-x86 appflowy-sdk-dev
[tasks.env_check]
dependencies = ["echo_env", "install_flutter_protobuf"]
@ -15,12 +10,12 @@ condition = { env_set = [
"stable",
] }
[tasks.appflowy-sdk-dev]
mac_alias = "appflowy-sdk-dev-macos"
windows_alias = "appflowy-sdk-dev-windows"
linux_alias = "appflowy-sdk-dev-linux"
[tasks.appflowy-core-dev]
mac_alias = "appflowy-core-dev-macos"
windows_alias = "appflowy-core-dev-windows"
linux_alias = "appflowy-core-dev-linux"
[tasks.appflowy-sdk-dev-android]
[tasks.appflowy-core-dev-android]
category = "Build"
dependencies = ["env_check"]
run_task = { name = [
@ -29,7 +24,7 @@ run_task = { name = [
"restore-crate-type",
] }
[tasks.appflowy-sdk-dev-macos]
[tasks.appflowy-core-dev-macos]
category = "Build"
dependencies = ["env_check"]
run_task = { name = [
@ -39,7 +34,7 @@ run_task = { name = [
"restore-crate-type",
] }
[tasks.appflowy-sdk-dev-windows]
[tasks.appflowy-core-dev-windows]
category = "Build"
dependencies = ["env_check"]
run_task = { name = [
@ -49,7 +44,7 @@ run_task = { name = [
"restore-crate-type",
] }
[tasks.appflowy-sdk-dev-linux]
[tasks.appflowy-core-dev-linux]
category = "Build"
dependencies = ["env_check"]
run_task = { name = [
@ -59,7 +54,6 @@ run_task = { name = [
"restore-crate-type",
] }
#
[tasks.sdk-build]
private = true
@ -112,7 +106,7 @@ script = [
script_runner = "@duckscript"
#
[tasks.appflowy-sdk-release]
[tasks.appflowy-core-release]
description = "Build flowy sdk in release mode"
category = "Build"
dependencies = ["env_check"]
@ -144,7 +138,7 @@ linux_alias = "post-desktop-linux"
private = true
script = [
"""
echo "🚀 🚀 🚀 Flowy-SDK(macOS) build success"
echo "🚀 🚀 🚀 AppFlowy-Core build success"
dart_ffi_dir= set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/packages/appflowy_backend/${TARGET_OS}
lib = set lib${LIB_NAME}.${LIB_EXT}
@ -161,7 +155,7 @@ script_runner = "@duckscript"
private = true
script = [
"""
echo "🚀 🚀 🚀 Flowy-SDK(windows) build success"
echo "🚀 🚀 🚀 AppFlowy-Core build success"
dart_ffi_dir= set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/windows/flutter/dart_ffi
lib = set ${LIB_NAME}.${LIB_EXT}
@ -180,7 +174,7 @@ script_runner = "@duckscript"
private = true
script = [
"""
echo "🚀 🚀 🚀 Flowy-SDK(linux) build success"
echo "🚀 🚀 🚀 AppFlowy-Core build success"
dart_ffi_dir= set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/linux/flutter/dart_ffi
lib = set lib${LIB_NAME}.${LIB_EXT}

View File

@ -4,17 +4,33 @@ windows_alias = "appflowy-windows"
linux_alias = "appflowy-linux"
[tasks.appflowy-macos]
dependencies = ["appflowy-sdk-release"]
run_task = { name = ["code_generation", "set-app-version", "flutter-build", "copy-to-product"] }
dependencies = ["appflowy-core-release"]
run_task = { name = [
"code_generation",
"set-app-version",
"flutter-build",
"copy-to-product",
] }
script_runner = "@shell"
[tasks.appflowy-windows]
dependencies = ["appflowy-sdk-release"]
run_task = { name = ["code_generation", "set-app-version", "flutter-build", "copy-to-product"] }
dependencies = ["appflowy-core-release"]
run_task = { name = [
"code_generation",
"set-app-version",
"flutter-build",
"copy-to-product",
] }
[tasks.appflowy-linux]
dependencies = ["appflowy-sdk-release"]
run_task = { name = ["code_generation", "set-app-version", "flutter-build", "copy-to-product", "create-release-archive"] }
dependencies = ["appflowy-core-release"]
run_task = { name = [
"code_generation",
"set-app-version",
"flutter-build",
"copy-to-product",
"create-release-archive",
] }
script_runner = "@shell"
[tasks.appflowy-dev]
@ -23,17 +39,32 @@ windows_alias = "appflowy-windows-dev"
linux_alias = "appflowy-linux-dev"
[tasks.appflowy-macos-dev]
dependencies = ["appflowy-sdk-dev"]
run_task = { name = ["code_generation", "set-app-version", "flutter-build", "copy-to-product"] }
dependencies = ["appflowy-core-dev"]
run_task = { name = [
"code_generation",
"set-app-version",
"flutter-build",
"copy-to-product",
] }
script_runner = "@shell"
[tasks.appflowy-windows-dev]
dependencies = ["appflowy-sdk-dev"]
run_task = { name = ["code_generation", "set-app-version", "flutter-build", "copy-to-product"] }
dependencies = ["appflowy-core-dev"]
run_task = { name = [
"code_generation",
"set-app-version",
"flutter-build",
"copy-to-product",
] }
[tasks.appflowy-linux-dev]
dependencies = ["appflowy-sdk-dev"]
run_task = { name = ["code_generation", "set-app-version", "flutter-build", "copy-to-product"] }
dependencies = ["appflowy-core-dev"]
run_task = { name = [
"code_generation",
"set-app-version",
"flutter-build",
"copy-to-product",
] }
script_runner = "@shell"
[tasks.copy-to-product]
@ -96,15 +127,13 @@ script = [
script_runner = "@duckscript"
[tasks.set-app-version]
script = [
"""
script = ["""
if is_empty ${APP_VERSION}
APP_VERSION = set ${CURRENT_APP_VERSION}
set_env APP_VERSION ${CURRENT_APP_VERSION}
end
echo APP_VERSION: ${APP_VERSION}
""",
]
"""]
script_runner = "@duckscript"
# The following tasks will create an archive that will be used on the GitHub Releases section
@ -118,7 +147,7 @@ linux_alias = "create-release-archive-linux"
[tasks.create-release-archive-linux]
script = [
"cd ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/product/${APP_VERSION}/${TARGET_OS}/Release",
"tar -czf ${PRODUCT_NAME}-${TARGET_OS}-x86.tar.gz *"
"tar -czf ${PRODUCT_NAME}-${TARGET_OS}-x86.tar.gz *",
]
[tasks.create-release-archive-windows]
@ -136,36 +165,30 @@ script = [
]
[tasks.flutter-build]
script = [
"""
script = ["""
cd app_flowy/
flutter clean
flutter pub get
flutter build ${TARGET_OS} --${BUILD_FLAG} --build-name=${APP_VERSION}
""",
]
"""]
script_runner = "@shell"
[tasks.flutter-build.linux]
script = [
"""
script = ["""
cd app_flowy/
flutter clean
flutter pub get
flutter build ${TARGET_OS} --${BUILD_FLAG}
""",
]
"""]
script_runner = "@shell"
[tasks.flutter-build.windows]
script = [
"""
script = ["""
cd app_flowy
exec cmd.exe /c flutter clean
exec cmd.exe /c flutter pub get
exec cmd.exe /c flutter build ${TARGET_OS} --${BUILD_FLAG}
""",
]
"""]
script_runner = "@duckscript"
[tasks.code_generation]
@ -177,7 +200,7 @@ script = [
flutter packages pub get
flutter packages pub run easy_localization:generate -S assets/translations/ -f keys -o locale_keys.g.dart -S assets/translations -s en.json
flutter packages pub run build_runner build --delete-conflicting-outputs
"""
""",
]
[tasks.code_generation.windows]
@ -189,7 +212,7 @@ script = [
exec cmd.exe /c flutter packages pub get
exec cmd.exe /c flutter packages pub run easy_localization:generate -S assets/translations/ -f keys -o locale_keys.g.dart -S assets/translations -s en.json
exec cmd.exe /c flutter packages pub run build_runner build --delete-conflicting-outputs
"""
""",
]
[tasks.dry_code_generation]
@ -199,7 +222,7 @@ script = [
cd app_flowy
flutter packages pub run easy_localization:generate -S assets/translations/ -f keys -o locale_keys.g.dart -S assets/translations -s en.json
flutter packages pub run build_runner build --delete-conflicting-outputs
"""
""",
]
[tasks.dry_code_generation.windows]
@ -209,5 +232,5 @@ script = [
cd ./app_flowy/
exec cmd.exe /c flutter packages pub run easy_localization:generate -S assets/translations/ -f keys -o locale_keys.g.dart -S assets/translations -s en.json
exec cmd.exe /c flutter packages pub run build_runner build --delete-conflicting-outputs
"""
""",
]

View File

@ -7,6 +7,7 @@ script = ["""
script_runner = "@shell"
[tasks.tauri_dev]
env = { RUST_LOG = "debug" }
script = ["""
cd appflowy_tauri
npm run tauri dev

View File

@ -18,6 +18,7 @@ cargo make --profile test-linux dart_unit_test_inner
script_runner = "@shell"
[tasks.dart_unit_test_inner]
env = { RUST_LOG = "info" }
dependencies = ["build-test-lib"]
description = "Run flutter unit tests"
script = '''
@ -29,6 +30,7 @@ flutter test --dart-define=RUST_LOG=${TEST_RUST_LOG} --concurrency=1
run_task = { name = ["rust_lib_unit_test", "shared_lib_unit_test"] }
[tasks.rust_lib_unit_test]
env = { RUST_LOG = "info" }
description = "Run rust-lib unit tests"
script = '''
cd rust-lib
@ -36,6 +38,7 @@ cargo test --no-default-features --features="sync, rev-sqlite"
'''
[tasks.shared_lib_unit_test]
env = { RUST_LOG = "info" }
description = "Run shared-lib unit test"
script = '''
cd ../shared-lib
@ -62,8 +65,7 @@ fi
[tasks.clean_profraw_files]
description = "Cleans profraw files that are created by `cargo test`"
script_runner = "@duckscript"
script = [
"""
script = ["""
rust_lib_profs = glob_array ./rust-lib/**/*.profraw
for prof in ${rust_lib_profs}
full_path = canonicalize ${prof}
@ -76,14 +78,12 @@ script = [
rm ${full_path}
end
"""
]
"""]
[tasks.run_rustlib_coverage_tests]
description = "Run tests with coverage instrumentation"
script_runner = "@shell"
script = [
"""
script = ["""
echo --- Running coverage tests ---
cd rust-lib/
@ -91,14 +91,12 @@ script = [
RUSTFLAGS='-C instrument-coverage' \
LLVM_PROFILE_FILE='prof-%p-%m.profraw' \
cargo test --no-default-features --features="sync,rev-sqlite"
"""
]
"""]
[tasks.run_sharedlib_coverage_tests]
description = "Run tests with coverage instrumentation"
script_runner = "@shell"
script = [
"""
script = ["""
echo --- Running coverage tests ---
cd ../shared-lib
@ -107,8 +105,7 @@ script = [
LLVM_PROFILE_FILE='prof-%p-%m.profraw' \
cargo test --no-default-features
"""
]
"""]
[tasks.get_rustlib_grcov_report]
description = "Get `grcov` HTML report for test coverage for rust-lib"
@ -128,7 +125,7 @@ script = [
--output-path target/coverage-html
echo "--- Done! Generated HTML report under 'target/coverage-html' for rustlib."
"""
""",
]
[tasks.get_sharedlib_grcov_report]
@ -149,21 +146,20 @@ script = [
--output-path target/coverage-html
echo "--- Done! Generated HTML report under 'target/coverage-html' for sharedlib."
"""
""",
]
[tasks.get_grcov_report]
description = "Get `grcov` HTML report for test coverage"
run_task = { name = [
"get_rustlib_grcov_report",
"get_sharedlib_grcov_report"
"get_sharedlib_grcov_report",
], parallel = true }
[tasks.get_sharedlib_lcov_report]
description = "Generates `lcov` report for `shared-lib`"
script_runner = "@shell"
script = [
"""
script = ["""
echo Getting 'lcov' results for 'shared-lib'
cd ../shared-lib
@ -178,14 +174,12 @@ script = [
--output-path target/coverage.lcov
echo "--- Done! Generated 'target/coverage.lcov' sharedlib."
"""
]
"""]
[tasks.get_rustlib_lcov_report]
description = "Generates `lcov` report for `rust-lib`"
script_runner = "@shell"
script = [
"""
script = ["""
echo Getting 'lcov' results for 'rust-lib'
cd rust-lib/
@ -200,23 +194,22 @@ script = [
--output-path target/coverage.lcov
echo "--- Done! Generated 'target/coverage.lcov' for rustlib."
"""
]
"""]
[tasks.get_lcov_report]
description = "Get `lcov` reports for test coverage"
run_task = { name = [
"get_sharedlib_lcov_report",
"get_rustlib_lcov_report"
"get_rustlib_lcov_report",
], parallel = true }
[tasks.rust_unit_test_with_coverage]
description = "Run rust unit test with code coverage"
run_task = { name = [
"check_grcov",
'appflowy-flutter-deps-tools',
"run_rustlib_coverage_tests",
"run_sharedlib_coverage_tests",
"get_lcov_report",
"clean_profraw_files"
]}
"check_grcov",
'appflowy-flutter-deps-tools',
"run_rustlib_coverage_tests",
"run_sharedlib_coverage_tests",
"get_lcov_report",
"clean_profraw_files",
] }

View File

@ -10,7 +10,7 @@ chrono = "0.4.19"
bytes = { version = "1.0" }
pin-project = "1.0.12"
futures-core = { version = "0.3" }
tokio = { version = "1.0", features = ["time", "rt"] }
tokio = { version = "1", features = ["time", "rt"] }
rand = "0.8.5"
async-trait = "0.1.59"
md5 = "0.7.0"