diff --git a/frontend/appflowy_flutter/integration_test/desktop/grid/grid_calculations_test.dart b/frontend/appflowy_flutter/integration_test/desktop/grid/grid_calculations_test.dart index 2eaa7ea6a5115..16736a8f88cdc 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/grid/grid_calculations_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/grid/grid_calculations_test.dart @@ -1,6 +1,13 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/text.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/services.dart'; + +import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/number.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart'; -import 'package:flutter/services.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -19,9 +26,6 @@ void main() { // Change one Field to Number await tester.changeFieldTypeOfFieldWithName('Type', FieldType.Number); - - expect(find.text('Calculate'), findsOneWidget); - await tester.changeCalculateAtIndex(1, CalculationType.Sum); // Enter values in cells @@ -55,7 +59,7 @@ void main() { await tester.changeFieldTypeOfFieldWithName('Type', FieldType.Number); await tester.changeFieldTypeOfFieldWithName('Done', FieldType.Number); - expect(find.text('Calculate'), findsNWidgets(2)); + expect(find.text('Calculate'), findsNWidgets(3)); await tester.changeCalculateAtIndex(1, CalculationType.Sum); await tester.changeCalculateAtIndex(2, CalculationType.Min); @@ -97,11 +101,576 @@ void main() { await tester.hoverOnFirstRowOfGrid(); await tester.tapRowMenuButtonInGrid(); await tester.tapDeleteOnRowMenu(); + await tester.tap(find.text(LocaleKeys.button_delete.tr())); await tester.pumpAndSettle(const Duration(seconds: 1)); expect(find.text('150'), findsNWidgets(2)); expect(find.text('100'), findsNWidgets(2)); }); + + testWidgets('Calculations with filter', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid); + + // Change two Fields to Number + await tester.changeFieldTypeOfFieldWithName('Type', FieldType.Number); + await tester.changeFieldTypeOfFieldWithName('Done', FieldType.Number); + + expect(find.text('Calculate'), findsNWidgets(3)); + + await tester.changeCalculateAtIndex(1, CalculationType.Sum); + await tester.changeCalculateAtIndex(2, CalculationType.Min); + + // Enter values in cells + await tester.editCell( + rowIndex: 0, + fieldType: FieldType.Number, + input: '100', + ); + await tester.editCell( + rowIndex: 1, + fieldType: FieldType.Number, + input: '150', + ); + await tester.editCell( + rowIndex: 2, + fieldType: FieldType.Number, + input: '100', + ); + await tester.editCell( + rowIndex: 0, + fieldType: FieldType.Number, + input: '150', + cellIndex: 1, + ); + await tester.editCell( + rowIndex: 1, + fieldType: FieldType.Number, + input: '100', + cellIndex: 1, + ); + await tester.editCell( + rowIndex: 2, + fieldType: FieldType.Number, + input: '50', + cellIndex: 1, + ); + await tester.pumpAndSettle(); + + // Dismiss edit cell + await tester.sendKeyDownEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + // Expect sum to be 100 + 150 + 100 = 350 + expect(find.text('350'), findsOneWidget); + + // Expect min to be 50 + expect(find.text('50'), findsNWidgets(2)); + + await tester.tapDatabaseFilterButton(); + await tester.tapCreateFilterByFieldType(FieldType.Number, 'Type'); + await tester.pumpAndSettle(); + + await tester.tap(find.byType(NumberFilterChoiceChip)); + await tester.pumpAndSettle(); + + await tester.enterText( + find.descendant( + of: find.byType(NumberFilterEditor), + matching: find.byType(FlowyTextField), + ), + '100', + ); + await tester.pumpAndSettle(const Duration(seconds: 2)); + + // Expect the sum to be 100+100 = 200 + expect(find.text('200'), findsOneWidget); + + // Expect the min to be 50 + expect(find.text('50'), findsNWidgets(2)); + + await tester.enterText( + find.descendant( + of: find.byType(NumberFilterEditor), + matching: find.byType(FlowyTextField), + ), + '150', + ); + await tester.pumpAndSettle(const Duration(seconds: 2)); + + // Expect the sum to be 150 (3 times, text field, cell, and calculate cell) + expect(find.text('150'), findsNWidgets(3)); + + // Expect the min to be 100 + expect(find.text('100'), findsNWidgets(2)); + }); + + testWidgets('Calculations count + count empty w/ filter', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid); + expect(find.text('Calculate'), findsNWidgets(3)); + + await tester.changeFieldTypeOfFieldWithName('Type', FieldType.RichText); + + await tester.changeCalculateAtIndex(0, CalculationType.Count); + await tester.changeCalculateAtIndex(1, CalculationType.CountEmpty); + + // Enter values in 2nd column (count empty) + await tester.editCell( + rowIndex: 0, + fieldType: FieldType.RichText, + input: 'A', + cellIndex: 1, + ); + await tester.editCell( + rowIndex: 1, + fieldType: FieldType.RichText, + input: 'A', + cellIndex: 1, + ); + await tester.pumpAndSettle(); + + // Dismiss edit cell + await tester.sendKeyDownEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + // Expect count to be 3 + expect( + find.byWidgetPredicate( + (w) => + w is CalculateCell && + w.calculation != null && + w.calculation!.value == '3', + ), + findsOneWidget, + ); + + // Expect count empty to be 1 + expect( + find.byWidgetPredicate( + (w) => + w is CalculateCell && + w.calculation != null && + w.calculation!.value == '1', + ), + findsOneWidget, + ); + + await tester.tapDatabaseFilterButton(); + await tester.tapCreateFilterByFieldType(FieldType.RichText, 'Type'); + await tester.pumpAndSettle(); + + await tester.tap(find.byType(TextFilterChoicechip)); + await tester.pumpAndSettle(); + + await tester.enterText( + find.descendant( + of: find.byType(TextFilterEditor), + matching: find.byType(FlowyTextField), + ), + 'A', + ); + await tester.pumpAndSettle(const Duration(seconds: 2)); + + // Expect the count to be 2 + expect( + find.byWidgetPredicate( + (w) => + w is CalculateCell && + w.calculation != null && + w.calculation!.value == '2', + ), + findsOneWidget, + ); + + // Expect the count empty to be 0 + expect( + find.byWidgetPredicate( + (w) => + w is CalculateCell && + w.calculation != null && + w.calculation!.value == '0', + ), + findsOneWidget, + ); + + await tester.enterText( + find.descendant( + of: find.byType(TextFilterEditor), + matching: find.byType(FlowyTextField), + ), + 'B', + ); + await tester.pumpAndSettle(const Duration(seconds: 2)); + + // Expect the count to be 0 + expect( + find.byWidgetPredicate( + (w) => + w is CalculateCell && + w.calculation != null && + w.calculation!.value == '0', + ), + findsOneWidget, + ); + + // Expect the count empty to be 0 + expect( + find.byWidgetPredicate( + (w) => + w is CalculateCell && + w.calculation != null && + w.calculation!.value == '0', + ), + findsOneWidget, + ); + + await tester.enterText( + find.descendant( + of: find.byType(TextFilterEditor), + matching: find.byType(FlowyTextField), + ), + '', + ); + await tester.pumpAndSettle(const Duration(seconds: 2)); + await tester.dismissCellEditor(); + + await tester.tapCreateRowButtonInGrid(); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + // Expect the count to be 4 + expect( + find.byWidgetPredicate( + (w) => + w is CalculateCell && + w.calculation != null && + w.calculation!.value == '4', + ), + findsOneWidget, + ); + + // Expect the count empty to be 2 + expect( + find.byWidgetPredicate( + (w) => + w is CalculateCell && + w.calculation != null && + w.calculation!.value == '2', + ), + findsOneWidget, + ); + }); + + testWidgets('Calculations count not empty w/ filter', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid); + expect(find.text('Calculate'), findsNWidgets(3)); + + await tester.changeFieldTypeOfFieldWithName('Type', FieldType.Number); + + await tester.changeCalculateAtIndex(0, CalculationType.CountNonEmpty); + await tester.changeCalculateAtIndex(1, CalculationType.CountNonEmpty); + + await tester.editCell( + rowIndex: 0, + fieldType: FieldType.Number, + input: '1', + ); + await tester.editCell( + rowIndex: 1, + fieldType: FieldType.Number, + input: '2', + ); + await tester.pumpAndSettle(); + + // Dismiss edit cell + await tester.sendKeyDownEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(); + + expect( + find.byWidgetPredicate( + (w) => + w is CalculateCell && + w.calculation != null && + w.calculation!.value == '0', + ), + findsOneWidget, + ); + + expect( + find.byWidgetPredicate( + (w) => + w is CalculateCell && + w.calculation != null && + w.calculation!.value == '2', + ), + findsOneWidget, + ); + + await tester.tapDatabaseFilterButton(); + await tester.tapCreateFilterByFieldType(FieldType.Number, 'Type'); + await tester.pumpAndSettle(); + + await tester.tap(find.byType(NumberFilterChoiceChip)); + await tester.pumpAndSettle(); + + await tester.enterText( + find.descendant( + of: find.byType(NumberFilterEditor), + matching: find.byType(FlowyTextField), + ), + '1', + ); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + expect( + find.byWidgetPredicate( + (w) => + w is CalculateCell && + w.calculation != null && + w.calculation!.value == '0', + ), + findsOneWidget, + ); + + expect( + find.byWidgetPredicate( + (w) => + w is CalculateCell && + w.calculation != null && + w.calculation!.value == '1', + ), + findsOneWidget, + ); + + await tester.pumpAndSettle(const Duration(seconds: 1)); + await tester.dismissCellEditor(); + + await tester.tapCreateRowButtonInGrid(); + await tester.pumpAndSettle(); + + expect( + find.byWidgetPredicate( + (w) => + w is CalculateCell && + w.calculation != null && + w.calculation!.value == '0', + ), + findsOneWidget, + ); + + expect( + find.byWidgetPredicate( + (w) => + w is CalculateCell && + w.calculation != null && + w.calculation!.value == '2', + ), + findsOneWidget, + ); + }); + + testWidgets('Median calculation', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid); + expect(find.text('Calculate'), findsNWidgets(3)); + + await tester.changeFieldTypeOfFieldWithName('Type', FieldType.Number); + await tester.changeCalculateAtIndex(1, CalculationType.Median); + + await tester.tapCreateRowButtonInGrid(); + await tester.pumpAndSettle(); + + await tester.editCell( + rowIndex: 0, + fieldType: FieldType.Number, + input: '10', + ); + + expect( + find.descendant( + of: find.byType(CalculateCell), + matching: find.text('10'), + ), + findsOneWidget, + ); + + await tester.editCell( + rowIndex: 1, + fieldType: FieldType.Number, + input: '20', + ); + + expect( + find.descendant( + of: find.byType(CalculateCell), + matching: find.text('15'), + ), + findsOneWidget, + ); + + await tester.editCell( + rowIndex: 2, + fieldType: FieldType.Number, + input: '30', + ); + + expect( + find.descendant( + of: find.byType(CalculateCell), + matching: find.text('20'), + ), + findsOneWidget, + ); + + await tester.editCell( + rowIndex: 3, + fieldType: FieldType.Number, + input: '40', + ); + + expect( + find.descendant( + of: find.byType(CalculateCell), + matching: find.text('25'), + ), + findsOneWidget, + ); + + // Dismiss edit cell + await tester.sendKeyDownEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(); + + await tester.tapDatabaseFilterButton(); + await tester.tapCreateFilterByFieldType(FieldType.Number, 'Type'); + await tester.pumpAndSettle(); + + await tester.tap(find.byType(NumberFilterChoiceChip)); + await tester.pumpAndSettle(); + + await tester.enterText( + find.descendant( + of: find.byType(NumberFilterEditor), + matching: find.byType(FlowyTextField), + ), + '30', + ); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + expect( + find.descendant( + of: find.byType(CalculateCell), + matching: find.text('30'), + ), + findsOneWidget, + ); + }); + + testWidgets('Median calculation', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid); + expect(find.text('Calculate'), findsNWidgets(3)); + + await tester.changeFieldTypeOfFieldWithName('Type', FieldType.Number); + await tester.changeCalculateAtIndex(1, CalculationType.Median); + + await tester.tapCreateRowButtonInGrid(); + await tester.pumpAndSettle(); + + await tester.editCell( + rowIndex: 0, + fieldType: FieldType.Number, + input: '10', + ); + + expect( + find.descendant( + of: find.byType(CalculateCell), + matching: find.text('10'), + ), + findsOneWidget, + ); + + await tester.editCell( + rowIndex: 1, + fieldType: FieldType.Number, + input: '20', + ); + + expect( + find.descendant( + of: find.byType(CalculateCell), + matching: find.text('15'), + ), + findsOneWidget, + ); + + await tester.editCell( + rowIndex: 2, + fieldType: FieldType.Number, + input: '30', + ); + + expect( + find.descendant( + of: find.byType(CalculateCell), + matching: find.text('20'), + ), + findsOneWidget, + ); + + await tester.editCell( + rowIndex: 3, + fieldType: FieldType.Number, + input: '40', + ); + + expect( + find.descendant( + of: find.byType(CalculateCell), + matching: find.text('25'), + ), + findsOneWidget, + ); + + // Dismiss edit cell + await tester.sendKeyDownEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(); + + await tester.tapDatabaseFilterButton(); + await tester.tapCreateFilterByFieldType(FieldType.Number, 'Type'); + await tester.pumpAndSettle(); + + await tester.tap(find.byType(NumberFilterChoiceChip)); + await tester.pumpAndSettle(); + + await tester.enterText( + find.descendant( + of: find.byType(NumberFilterEditor), + matching: find.byType(FlowyTextField), + ), + '30', + ); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + expect( + find.descendant( + of: find.byType(CalculateCell), + matching: find.text('30'), + ), + findsOneWidget, + ); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop_runner_4.dart b/frontend/appflowy_flutter/integration_test/desktop_runner_4.dart index e51c711549377..571abaecf1e8b 100644 --- a/frontend/appflowy_flutter/integration_test/desktop_runner_4.dart +++ b/frontend/appflowy_flutter/integration_test/desktop_runner_4.dart @@ -1,6 +1,7 @@ import 'package:integration_test/integration_test.dart'; import 'desktop/document/document_test_runner_2.dart' as document_test_runner_2; +import 'desktop/grid/grid_calculations_test.dart' as grid_calculations_test; import 'desktop/first_test/first_test.dart' as first_test; Future main() async { @@ -13,5 +14,6 @@ Future runIntegration4OnDesktop() async { first_test.main(); document_test_runner_2.main(); + grid_calculations_test.main(); // DON'T add more tests here. } diff --git a/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart b/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart index ff7e33ebdf8bb..c8ee24c31c7f6 100644 --- a/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart +++ b/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart @@ -763,12 +763,26 @@ extension AppFlowyDatabaseTest on WidgetTester { await tap(find.byType(CalculateCell).at(index)); await pumpAndSettle(); - await tap( - find.descendant( - of: find.byType(CalculationTypeItem), - matching: find.text(type.label), - ), + final calculateMenu = find + .descendant( + of: find.byType(CalculateSelector), + matching: find.byWidgetPredicate((w) => w is Scrollable), + ) + .first; + + final calculateType = find.descendant( + of: find.byType(CalculationTypeItem), + matching: find.text(type.label), ); + + await scrollUntilVisible( + calculateType, + 20, + scrollable: calculateMenu, + duration: const Duration(milliseconds: 250), + ); + + await tap(calculateType); await pumpAndSettle(); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart b/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart index e3ef8d578ea89..f6080f91f5978 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/calculations/calculations_service.dart @@ -9,11 +9,9 @@ class CalculationsBackendService { final String viewId; // Get Calculations (initial fetch) - Future> getCalculations() async { final payload = DatabaseViewIdPB()..value = viewId; - return DatabaseEventGetAllCalculations(payload).send(); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart index 5ea364bc727a1..43eaaf4f91872 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart @@ -1,3 +1,5 @@ +import 'package:flutter/material.dart'; + import 'package:appflowy/plugins/database/application/calculations/calculation_type_ext.dart'; import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy/plugins/database/application/field/type_option/number_format_bloc.dart'; @@ -12,7 +14,6 @@ import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum. import 'package:appflowy_backend/protobuf/flowy-database2/number_entities.pb.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class CalculateCell extends StatefulWidget { @@ -85,35 +86,11 @@ class _CalculateCellState extends State { } }); - return SingleChildScrollView( - child: Column( - children: [ - if (widget.calculation != null) - RemoveCalculationButton( - onTap: () => context.read().add( - CalculationsEvent.removeCalculation( - widget.fieldInfo.id, - widget.calculation!.id, - ), - ), - ), - ...widget.fieldInfo.fieldType.calculationsForFieldType().map( - (type) => CalculationTypeItem( - type: type, - onTap: () { - if (type != widget.calculation?.calculationType) { - context.read().add( - CalculationsEvent.updateCalculationType( - widget.fieldInfo.id, - type, - calculationId: widget.calculation?.id, - ), - ); - } - }, - ), - ), - ], + return BlocProvider.value( + value: context.read(), + child: CalculateSelector( + fieldInfo: widget.fieldInfo, + calculation: widget.calculation, ), ); }, @@ -125,7 +102,7 @@ class _CalculateCellState extends State { } Widget _showCalculateValue(BuildContext context, String? prefix) { - prefix = prefix != null ? '$prefix ' : ''; + prefix = prefix != null && prefix.isNotEmpty ? '$prefix ' : ''; final calculateValue = '$prefix${_withoutTrailingZeros(widget.calculation!.value)}'; @@ -208,3 +185,49 @@ class _CalculateCellState extends State { _ => null, }; } + +class CalculateSelector extends StatelessWidget { + const CalculateSelector({ + super.key, + required this.fieldInfo, + this.calculation, + }); + + final FieldInfo fieldInfo; + final CalculationPB? calculation; + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + if (calculation != null) + RemoveCalculationButton( + onTap: () => context.read().add( + CalculationsEvent.removeCalculation( + fieldInfo.id, + calculation!.id, + ), + ), + ), + ...fieldInfo.fieldType.calculationsForFieldType().map( + (type) => CalculationTypeItem( + type: type, + onTap: () { + if (type != calculation?.calculationType) { + context.read().add( + CalculationsEvent.updateCalculationType( + fieldInfo.id, + type, + calculationId: calculation?.id, + ), + ); + } + }, + ), + ), + ], + ), + ); + } +} diff --git a/frontend/rust-lib/event-integration-test/tests/database/local_test/calculate_test.rs b/frontend/rust-lib/event-integration-test/tests/database/local_test/calculate_test.rs index 7412f54951ba3..c90d124c0700d 100644 --- a/frontend/rust-lib/event-integration-test/tests/database/local_test/calculate_test.rs +++ b/frontend/rust-lib/event-integration-test/tests/database/local_test/calculate_test.rs @@ -9,7 +9,7 @@ use flowy_database2::entities::{ use tokio::time::sleep; #[tokio::test] -async fn calculation_integration_test1() { +async fn get_calculate_after_edit_cell_test() { let test = EventIntegrationTest::new().await; test.sign_up_as_anon().await; @@ -17,17 +17,8 @@ async fn calculation_integration_test1() { let payload = gen_csv_import_data("project.csv", &workspace_id); let view = test.import_data(payload).await.pop().unwrap(); let database = test.open_database(&view.id).await; + let database_view_id = &view.id; - average_calculation(test, database, &view.id).await; -} - -// Tests for the CalculationType::Average -// Is done on the Delay column in the project.csv -async fn average_calculation( - test: EventIntegrationTest, - database: DatabasePB, - database_view_id: &str, -) { // Delay column is the 11th column (index 10) in the project.csv let delay_field = database.fields.get(10).unwrap(); @@ -54,6 +45,7 @@ async fn average_calculation( ); // Update a cell in the delay column at fourth row (3rd index) + // edit the Delay column in the project.csv let cell_changeset = CellChangesetPB { view_id: database_view_id.to_string(), row_id: database.rows.get(3).unwrap().id.clone(), diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index e284466054951..8fed986f27028 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -329,7 +329,7 @@ impl DatabaseEditor { .database_views .get_or_init_view_editor(&update.view_id) .await?; - view_editor.v_update_calculations(update).await?; + view_editor.v_edit_calculations(update).await?; Ok(()) } diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs index 8b9b7032c73b9..6f7a930604301 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs @@ -693,41 +693,7 @@ impl DatabaseViewEditor { Ok(()) } - pub async fn v_update_calculate(&self, field_id: &str) -> Option<()> { - let field = self.delegate.get_field(field_id).await?; - let cal = self - .delegate - .get_calculation(&self.view_id, &field.id) - .await?; - - let cells = self - .delegate - .get_cells_for_field(&self.view_id, field_id) - .await - .into_iter() - .flat_map(|row_cell| row_cell.cell.map(Arc::new)) - .collect::>(); - - let changes = self - .calculations_controller - .handle_cells_changed(&field, &cal, cells) - .await; - - if !changes.is_empty() { - let notification = CalculationChangesetNotificationPB::from_update(&self.view_id, changes); - if let Err(_err) = self - .notifier - .send(DatabaseViewChanged::CalculationValueNotification( - notification, - )) - { - error!("Failed to send CalculationValueNotification"); - } - } - - None - } - + #[instrument(level = "trace", skip_all)] pub async fn v_calculate_rows(&self, fields: Vec, rows: Vec>) -> FlowyResult<()> { let mut updates = vec![]; // Filter fields to only those with calculations @@ -768,6 +734,7 @@ impl DatabaseViewEditor { } // Send notification if updates were made + trace!("Calculations updates: {:?}", updates); if !updates.is_empty() { let notification = CalculationChangesetNotificationPB::from_update(&self.view_id, updates); if let Err(_err) = self @@ -798,10 +765,42 @@ impl DatabaseViewEditor { self.delegate.get_all_calculations(&self.view_id).await } - pub async fn v_update_calculations( - &self, - params: UpdateCalculationChangesetPB, - ) -> FlowyResult<()> { + pub async fn v_update_calculate(&self, field_id: &str) -> Option<()> { + let field = self.delegate.get_field(field_id).await?; + let cal = self + .delegate + .get_calculation(&self.view_id, &field.id) + .await?; + + let cells = self + .delegate + .get_cells_for_field(&self.view_id, field_id) + .await + .into_iter() + .flat_map(|row_cell| row_cell.cell.map(Arc::new)) + .collect::>(); + + let changes = self + .calculations_controller + .handle_cells_changed(&field, &cal, cells) + .await; + + if !changes.is_empty() { + let notification = CalculationChangesetNotificationPB::from_update(&self.view_id, changes); + if let Err(_err) = self + .notifier + .send(DatabaseViewChanged::CalculationValueNotification( + notification, + )) + { + error!("Failed to send CalculationValueNotification"); + } + } + + None + } + + pub async fn v_edit_calculations(&self, params: UpdateCalculationChangesetPB) -> FlowyResult<()> { let calculation_id = params .calculation_id .unwrap_or_else(gen_database_calculation_id); diff --git a/frontend/rust-lib/flowy-database2/tests/database/calculations_test/calculation_test.rs b/frontend/rust-lib/flowy-database2/tests/database/calculations_test/calculation_test.rs index 23e70976e200b..3e2d9653da91a 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/calculations_test/calculation_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/calculations_test/calculation_test.rs @@ -1,13 +1,38 @@ use std::sync::Arc; -use crate::database::calculations_test::script::DatabaseCalculationTest; +use crate::database::database_editor::{DatabaseEditorTest, FilterRowChanged}; use collab_database::fields::Field; -use flowy_database2::entities::{CalculationType, FieldType, UpdateCalculationChangesetPB}; +use flowy_database2::entities::{ + CalculationType, FieldType, NumberFilterConditionPB, NumberFilterPB, UpdateCalculationChangesetPB, +}; use lib_infra::box_any::BoxAny; #[tokio::test] -async fn calculations_test() { - let mut test = DatabaseCalculationTest::new().await; +async fn calculate_with_filter_test() { + let mut test = DatabaseEditorTest::new_grid().await; + let row_count = test.rows.len(); + let expected = 1; + // let sub = test.sdk.notification_sender.subscribe().await.unwrap(); + + test + .create_data_filter( + None, + FieldType::Number, + BoxAny::new(NumberFilterPB { + condition: NumberFilterConditionPB::Equal, + content: "1".to_string(), + }), + Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + ) + .await; +} + +#[tokio::test] +async fn insert_delete_calculate_test() { + let mut test = DatabaseEditorTest::new_grid().await; let expected_sum = 25.00; let expected_min = 1.00; @@ -89,7 +114,7 @@ async fn calculations_test() { #[tokio::test] async fn calculations_empty_test() { - let mut test = DatabaseCalculationTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let view_id = &test.view_id(); let text_fields = test @@ -128,7 +153,7 @@ async fn calculations_empty_test() { #[tokio::test] async fn calculations_non_empty_test() { - let mut test = DatabaseCalculationTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let view_id = &test.view_id(); let text_fields = test @@ -167,7 +192,7 @@ async fn calculations_non_empty_test() { #[tokio::test] async fn calculations_count_test() { - let mut test = DatabaseCalculationTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let view_id = &test.view_id(); let text_fields = test diff --git a/frontend/rust-lib/flowy-database2/tests/database/calculations_test/mod.rs b/frontend/rust-lib/flowy-database2/tests/database/calculations_test/mod.rs index 258788eb1c8c0..370ae7a63f495 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/calculations_test/mod.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/calculations_test/mod.rs @@ -1,2 +1 @@ mod calculation_test; -mod script; diff --git a/frontend/rust-lib/flowy-database2/tests/database/calculations_test/script.rs b/frontend/rust-lib/flowy-database2/tests/database/calculations_test/script.rs deleted file mode 100644 index d11ca64c4ac1e..0000000000000 --- a/frontend/rust-lib/flowy-database2/tests/database/calculations_test/script.rs +++ /dev/null @@ -1,71 +0,0 @@ -use collab_database::rows::RowId; -use tokio::sync::broadcast::Receiver; - -use flowy_database2::entities::UpdateCalculationChangesetPB; -use flowy_database2::services::database_view::DatabaseViewChanged; - -use crate::database::database_editor::DatabaseEditorTest; - -pub struct DatabaseCalculationTest { - inner: DatabaseEditorTest, - recv: Option>, -} - -impl DatabaseCalculationTest { - pub async fn new() -> Self { - let editor_test = DatabaseEditorTest::new_grid().await; - Self { - inner: editor_test, - recv: None, - } - } - - pub fn view_id(&self) -> String { - self.view_id.clone() - } - - pub async fn insert_calculation(&mut self, payload: UpdateCalculationChangesetPB) { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id()) - .await - .unwrap(), - ); - self.editor.update_calculation(payload).await.unwrap(); - } - - pub async fn assert_calculation_float_value(&mut self, expected: f64) { - let calculations = self.editor.get_all_calculations(&self.view_id()).await; - let calculation = calculations.items.first().unwrap(); - assert_eq!(calculation.value, format!("{:.2}", expected)); - } - - pub async fn assert_calculation_value(&mut self, expected: &str) { - let calculations = self.editor.get_all_calculations(&self.view_id()).await; - let calculation = calculations.items.first().unwrap(); - assert_eq!(calculation.value, expected); - } - - pub async fn duplicate_row(&self, row_id: &RowId) { - self - .editor - .duplicate_row(&self.view_id, row_id) - .await - .unwrap(); - } -} - -impl std::ops::Deref for DatabaseCalculationTest { - type Target = DatabaseEditorTest; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl std::ops::DerefMut for DatabaseCalculationTest { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} diff --git a/frontend/rust-lib/flowy-database2/tests/database/database_editor.rs b/frontend/rust-lib/flowy-database2/tests/database/database_editor.rs index 852a4ea591490..a2d563b84e51d 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/database_editor.rs @@ -1,6 +1,3 @@ -use std::collections::HashMap; -use std::sync::Arc; - use collab_database::database::gen_database_view_id; use collab_database::fields::checkbox_type_option::CheckboxTypeOption; use collab_database::fields::checklist_type_option::ChecklistTypeOption; @@ -9,16 +6,24 @@ use collab_database::fields::select_type_option::{ }; use collab_database::fields::Field; use collab_database::rows::{Row, RowId}; -use lib_infra::box_any::BoxAny; -use strum::EnumCount; - use event_integration_test::folder_event::ViewTest; use event_integration_test::EventIntegrationTest; -use flowy_database2::entities::{DatabasePB, FieldType, FilterPB, RowMetaPB}; +use flowy_database2::entities::{ + DatabasePB, DatabaseViewSettingPB, FieldType, FilterPB, FilterType, RowMetaPB, + TextFilterConditionPB, TextFilterPB, UpdateCalculationChangesetPB, +}; +use lib_infra::box_any::BoxAny; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::Duration; +use strum::EnumCount; +use tokio::sync::broadcast::Receiver; use flowy_database2::services::database::DatabaseEditor; +use flowy_database2::services::database_view::DatabaseViewChanged; use flowy_database2::services::field::checklist_filter::ChecklistCellChangeset; use flowy_database2::services::field::SelectOptionCellChangeset; +use flowy_database2::services::filter::{FilterChangeset, FilterInner}; use flowy_database2::services::share::csv::{CSVFormat, ImportResult}; use flowy_error::FlowyResult; @@ -34,6 +39,7 @@ pub struct DatabaseEditorTest { pub rows: Vec>, pub field_count: usize, pub row_by_row_id: HashMap, + view_change_recv: Option>, } impl DatabaseEditorTest { @@ -101,6 +107,7 @@ impl DatabaseEditorTest { rows, field_count: FieldType::COUNT, row_by_row_id: HashMap::default(), + view_change_recv: None, }; this.get_database_data(&view_id).await; this @@ -297,4 +304,318 @@ impl DatabaseEditorTest { .await .ok() } + + pub async fn insert_calculation(&mut self, payload: UpdateCalculationChangesetPB) { + self.view_change_recv = Some( + self + .editor + .subscribe_view_changed(&self.view_id()) + .await + .unwrap(), + ); + self.editor.update_calculation(payload).await.unwrap(); + } + + pub async fn assert_calculation_float_value(&mut self, expected: f64) { + let calculations = self.editor.get_all_calculations(&self.view_id()).await; + let calculation = calculations.items.first().unwrap(); + assert_eq!(calculation.value, format!("{:.2}", expected)); + } + + pub async fn assert_calculation_value(&mut self, expected: &str) { + let calculations = self.editor.get_all_calculations(&self.view_id()).await; + let calculation = calculations.items.first().unwrap(); + assert_eq!(calculation.value, expected); + } + + pub async fn duplicate_row(&self, row_id: &RowId) { + self + .editor + .duplicate_row(&self.view_id, row_id) + .await + .unwrap(); + } + + pub fn view_id(&self) -> String { + self.view_id.clone() + } + + pub async fn get_all_filters(&self) -> Vec { + self.editor.get_all_filters(&self.view_id).await.items + } + + pub async fn get_filter( + &self, + filter_type: FilterType, + field_type: Option, + ) -> Option { + let filters = self.editor.get_all_filters(&self.view_id).await; + + for filter in filters.items.iter() { + let result = Self::find_filter(filter, filter_type, field_type); + if result.is_some() { + return result; + } + } + + None + } + + fn find_filter( + filter: &FilterPB, + filter_type: FilterType, + field_type: Option, + ) -> Option { + match &filter.filter_type { + FilterType::And | FilterType::Or if filter.filter_type == filter_type => Some(filter.clone()), + FilterType::And | FilterType::Or => { + for child_filter in filter.children.iter() { + if let Some(result) = Self::find_filter(child_filter, filter_type, field_type) { + return Some(result); + } + } + None + }, + FilterType::Data + if filter.filter_type == filter_type + && field_type.map_or(false, |field_type| { + field_type == filter.data.clone().unwrap().field_type + }) => + { + Some(filter.clone()) + }, + _ => None, + } + } + + pub async fn update_text_cell_with_change( + &mut self, + row_id: RowId, + text: String, + changed: Option, + ) { + self.subscribe_view_changed().await; + self.assert_future_changed(changed).await; + self.update_text_cell(row_id, &text).await.unwrap(); + } + + pub async fn update_checklist_cell(&mut self, row_id: RowId, selected_option_ids: Vec) { + self + .set_checklist_cell(row_id, selected_option_ids) + .await + .unwrap(); + } + + pub async fn update_single_select_cell_with_change( + &mut self, + row_id: RowId, + option_id: String, + changed: Option, + ) { + self.subscribe_view_changed().await; + self.assert_future_changed(changed).await; + self + .update_single_select_cell(row_id, &option_id) + .await + .unwrap(); + } + + pub async fn create_data_filter( + &mut self, + parent_filter_id: Option, + field_type: FieldType, + data: BoxAny, + changed: Option, + ) { + self.subscribe_view_changed().await; + self.assert_future_changed(changed).await; + let field = self.get_first_field(field_type).await; + let params = FilterChangeset::Insert { + parent_filter_id, + data: FilterInner::Data { + field_id: field.id, + field_type, + condition_and_content: data, + }, + }; + self + .editor + .modify_view_filters(&self.view_id, params) + .await + .unwrap(); + } + + pub async fn update_text_filter( + &mut self, + filter: FilterPB, + condition: TextFilterConditionPB, + content: String, + changed: Option, + ) { + self.subscribe_view_changed().await; + self.assert_future_changed(changed).await; + let current_filter = filter.data.unwrap(); + let params = FilterChangeset::UpdateData { + filter_id: filter.id, + data: FilterInner::Data { + field_id: current_filter.field_id, + field_type: current_filter.field_type, + condition_and_content: BoxAny::new(TextFilterPB { condition, content }), + }, + }; + self + .editor + .modify_view_filters(&self.view_id, params) + .await + .unwrap(); + } + + pub async fn create_and_filter( + &mut self, + parent_filter_id: Option, + changed: Option, + ) { + self.subscribe_view_changed().await; + self.assert_future_changed(changed).await; + let params = FilterChangeset::Insert { + parent_filter_id, + data: FilterInner::And { children: vec![] }, + }; + self + .editor + .modify_view_filters(&self.view_id, params) + .await + .unwrap(); + } + + pub async fn create_or_filter( + &mut self, + parent_filter_id: Option, + changed: Option, + ) { + self.subscribe_view_changed().await; + self.assert_future_changed(changed).await; + let params = FilterChangeset::Insert { + parent_filter_id, + data: FilterInner::Or { children: vec![] }, + }; + self + .editor + .modify_view_filters(&self.view_id, params) + .await + .unwrap(); + } + + pub async fn delete_filter(&mut self, filter_id: String, changed: Option) { + self.subscribe_view_changed().await; + self.assert_future_changed(changed).await; + let params = FilterChangeset::Delete { filter_id }; + self + .editor + .modify_view_filters(&self.view_id, params) + .await + .unwrap(); + } + + pub async fn assert_filter_count(&self, count: usize) { + let filters = self.editor.get_all_filters(&self.view_id).await.items; + assert_eq!(count, filters.len()); + } + + pub async fn assert_grid_setting(&self, expected_setting: DatabaseViewSettingPB) { + let setting = self + .editor + .get_database_view_setting(&self.view_id) + .await + .unwrap(); + assert_eq!(expected_setting, setting); + } + + pub async fn assert_filters(&self, expected: Vec) { + let actual = self.get_all_filters().await; + for (actual_filter, expected_filter) in actual.iter().zip(expected.iter()) { + Self::assert_filter(actual_filter, expected_filter); + } + } + + pub async fn assert_number_of_visible_rows(&self, expected: usize) { + let (tx, rx) = tokio::sync::oneshot::channel(); + let _ = self + .editor + .open_database_view(&self.view_id, Some(tx)) + .await + .unwrap(); + rx.await.unwrap(); + + let rows = self.editor.get_all_rows(&self.view_id).await.unwrap(); + assert_eq!(rows.len(), expected); + } + + pub async fn wait(&self, millisecond: u64) { + tokio::time::sleep(Duration::from_millis(millisecond)).await; + } + + async fn subscribe_view_changed(&mut self) { + self.view_change_recv = Some( + self + .editor + .subscribe_view_changed(&self.view_id) + .await + .unwrap(), + ); + } + + async fn assert_future_changed(&mut self, change: Option) { + if change.is_none() { + return; + } + let change = change.unwrap(); + let mut receiver = self.view_change_recv.take().unwrap(); + tokio::spawn(async move { + match tokio::time::timeout(Duration::from_secs(2), receiver.recv()).await { + Ok(changed) => { + if let DatabaseViewChanged::FilterNotification(notification) = changed.unwrap() { + assert_eq!( + notification.visible_rows.len(), + change.showing_num_of_rows, + "visible rows not match" + ); + assert_eq!( + notification.invisible_rows.len(), + change.hiding_num_of_rows, + "invisible rows not match" + ); + } + }, + Err(e) => { + panic!("Process filter task timeout: {:?}", e); + }, + } + }); + } + + fn assert_filter(actual: &FilterPB, expected: &FilterPB) { + assert_eq!(actual.filter_type, expected.filter_type); + assert_eq!(actual.children.is_empty(), expected.children.is_empty()); + assert_eq!(actual.data.is_some(), expected.data.is_some()); + + match actual.filter_type { + FilterType::Data => { + let actual_data = actual.data.clone().unwrap(); + let expected_data = expected.data.clone().unwrap(); + assert_eq!(actual_data.field_type, expected_data.field_type); + assert_eq!(actual_data.data, expected_data.data); + }, + FilterType::And | FilterType::Or => { + for (actual_child, expected_child) in actual.children.iter().zip(expected.children.iter()) { + Self::assert_filter(actual_child, expected_child); + } + }, + } + } +} + +pub struct FilterRowChanged { + pub(crate) showing_num_of_rows: usize, + pub(crate) hiding_num_of_rows: usize, } diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/advanced_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/advanced_filter_test.rs index d077b4c61f953..1378eeb5f1159 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/advanced_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/advanced_filter_test.rs @@ -1,4 +1,4 @@ -use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; +use crate::database::database_editor::{DatabaseEditorTest, FilterRowChanged}; use bytes::Bytes; use flowy_database2::entities::{ CheckboxFilterConditionPB, CheckboxFilterPB, DateFilterConditionPB, DateFilterPB, FieldType, @@ -16,7 +16,7 @@ use std::convert::TryInto; /// #[tokio::test] async fn create_advanced_filter_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let create_checkbox_filter = || -> CheckboxFilterPB { CheckboxFilterPB { @@ -189,7 +189,7 @@ async fn create_advanced_filter_test() { /// #[tokio::test] async fn create_advanced_filter_with_conversion_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let create_checkbox_filter = || -> CheckboxFilterPB { CheckboxFilterPB { diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/checkbox_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/checkbox_filter_test.rs index 26a018d8de65c..7e55e3e9bc3bd 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/checkbox_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/checkbox_filter_test.rs @@ -1,10 +1,10 @@ -use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; +use crate::database::database_editor::{DatabaseEditorTest, FilterRowChanged}; use flowy_database2::entities::{CheckboxFilterConditionPB, CheckboxFilterPB, FieldType}; use lib_infra::box_any::BoxAny; #[tokio::test] async fn grid_filter_checkbox_is_check_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let expected = 3; let row_count = test.rows.len(); @@ -29,7 +29,7 @@ async fn grid_filter_checkbox_is_check_test() { #[tokio::test] async fn grid_filter_checkbox_is_uncheck_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let expected = 4; let row_count = test.rows.len(); diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/checklist_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/checklist_filter_test.rs index 88d48bcecac09..bdaab1b41d924 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/checklist_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/checklist_filter_test.rs @@ -1,11 +1,11 @@ -use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; +use crate::database::database_editor::{DatabaseEditorTest, FilterRowChanged}; use collab_database::template::check_list_parse::ChecklistCellData; use flowy_database2::entities::{ChecklistFilterConditionPB, ChecklistFilterPB, FieldType}; use lib_infra::box_any::BoxAny; #[tokio::test] async fn grid_filter_checklist_is_incomplete_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let expected = 5; let row_count = test.rows.len(); let option_ids = get_checklist_cell_options(&test).await; @@ -36,7 +36,7 @@ async fn grid_filter_checklist_is_incomplete_test() { #[tokio::test] async fn grid_filter_checklist_is_complete_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let expected = 2; let row_count = test.rows.len(); let option_ids = get_checklist_cell_options(&test).await; @@ -65,7 +65,7 @@ async fn grid_filter_checklist_is_complete_test() { test.assert_number_of_visible_rows(expected).await; } -async fn get_checklist_cell_options(test: &DatabaseFilterTest) -> Vec { +async fn get_checklist_cell_options(test: &DatabaseEditorTest) -> Vec { let field = test.get_first_field(FieldType::Checklist).await; let row_cell = test.editor.get_cell(&field.id, &test.rows[0].id).await; row_cell diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/date_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/date_filter_test.rs index deff2a5888879..498a88c1aea21 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/date_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/date_filter_test.rs @@ -1,10 +1,10 @@ -use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; +use crate::database::database_editor::{DatabaseEditorTest, FilterRowChanged}; use flowy_database2::entities::{DateFilterConditionPB, DateFilterPB, FieldType}; use lib_infra::box_any::BoxAny; #[tokio::test] async fn grid_filter_date_is_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 3; @@ -32,7 +32,7 @@ async fn grid_filter_date_is_test() { #[tokio::test] async fn grid_filter_date_after_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 3; @@ -60,7 +60,7 @@ async fn grid_filter_date_after_test() { #[tokio::test] async fn grid_filter_date_on_or_after_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 3; @@ -88,7 +88,7 @@ async fn grid_filter_date_on_or_after_test() { #[tokio::test] async fn grid_filter_date_on_or_before_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 4; @@ -116,7 +116,7 @@ async fn grid_filter_date_on_or_before_test() { #[tokio::test] async fn grid_filter_date_within_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 5; diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/mod.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/mod.rs index e99cc725d5c85..b3ffcf9a9957b 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/mod.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/mod.rs @@ -3,7 +3,7 @@ mod checkbox_filter_test; mod checklist_filter_test; mod date_filter_test; mod number_filter_test; -mod script; +// mod script; mod select_option_filter_test; mod text_filter_test; mod time_filter_test; diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/number_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/number_filter_test.rs index 509b520361c2a..1977ec31436cc 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/number_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/number_filter_test.rs @@ -1,10 +1,10 @@ -use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; +use crate::database::database_editor::{DatabaseEditorTest, FilterRowChanged}; use flowy_database2::entities::{FieldType, NumberFilterConditionPB, NumberFilterPB}; use lib_infra::box_any::BoxAny; #[tokio::test] async fn grid_filter_number_is_equal_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 1; @@ -30,7 +30,7 @@ async fn grid_filter_number_is_equal_test() { #[tokio::test] async fn grid_filter_number_is_less_than_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 2; @@ -57,7 +57,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 = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 2; @@ -83,7 +83,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 = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 3; @@ -109,7 +109,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 = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 2; @@ -135,7 +135,7 @@ async fn grid_filter_number_is_empty_test() { #[tokio::test] async fn grid_filter_number_is_not_empty_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 5; diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/script.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/script.rs deleted file mode 100644 index 6592c0430550e..0000000000000 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/script.rs +++ /dev/null @@ -1,323 +0,0 @@ -#![allow(dead_code)] - -use std::time::Duration; - -use collab_database::rows::RowId; -use flowy_database2::services::filter::{FilterChangeset, FilterInner}; -use lib_infra::box_any::BoxAny; -use tokio::sync::broadcast::Receiver; - -use flowy_database2::entities::{ - DatabaseViewSettingPB, FieldType, FilterPB, FilterType, TextFilterConditionPB, TextFilterPB, -}; -use flowy_database2::services::database_view::DatabaseViewChanged; - -use crate::database::database_editor::DatabaseEditorTest; - -pub struct FilterRowChanged { - pub(crate) showing_num_of_rows: usize, - pub(crate) hiding_num_of_rows: usize, -} - -pub struct DatabaseFilterTest { - inner: DatabaseEditorTest, - recv: Option>, -} - -impl DatabaseFilterTest { - pub async fn new() -> Self { - let editor_test = DatabaseEditorTest::new_grid().await; - Self { - inner: editor_test, - recv: None, - } - } - - pub async fn get_all_filters(&self) -> Vec { - self.editor.get_all_filters(&self.view_id).await.items - } - - pub async fn get_filter( - &self, - filter_type: FilterType, - field_type: Option, - ) -> Option { - let filters = self.inner.editor.get_all_filters(&self.view_id).await; - - for filter in filters.items.iter() { - let result = Self::find_filter(filter, filter_type, field_type); - if result.is_some() { - return result; - } - } - - None - } - - fn find_filter( - filter: &FilterPB, - filter_type: FilterType, - field_type: Option, - ) -> Option { - match &filter.filter_type { - FilterType::And | FilterType::Or if filter.filter_type == filter_type => Some(filter.clone()), - FilterType::And | FilterType::Or => { - for child_filter in filter.children.iter() { - if let Some(result) = Self::find_filter(child_filter, filter_type, field_type) { - return Some(result); - } - } - None - }, - FilterType::Data - if filter.filter_type == filter_type - && field_type.map_or(false, |field_type| { - field_type == filter.data.clone().unwrap().field_type - }) => - { - Some(filter.clone()) - }, - _ => None, - } - } - - pub async fn update_text_cell_with_change( - &mut self, - row_id: RowId, - text: String, - changed: Option, - ) { - self.subscribe_view_changed().await; - self.assert_future_changed(changed).await; - self.update_text_cell(row_id, &text).await.unwrap(); - } - - pub async fn update_checklist_cell(&mut self, row_id: RowId, selected_option_ids: Vec) { - self - .set_checklist_cell(row_id, selected_option_ids) - .await - .unwrap(); - } - - pub async fn update_single_select_cell_with_change( - &mut self, - row_id: RowId, - option_id: String, - changed: Option, - ) { - self.subscribe_view_changed().await; - self.assert_future_changed(changed).await; - self - .update_single_select_cell(row_id, &option_id) - .await - .unwrap(); - } - - pub async fn create_data_filter( - &mut self, - parent_filter_id: Option, - field_type: FieldType, - data: BoxAny, - changed: Option, - ) { - self.subscribe_view_changed().await; - self.assert_future_changed(changed).await; - let field = self.get_first_field(field_type).await; - let params = FilterChangeset::Insert { - parent_filter_id, - data: FilterInner::Data { - field_id: field.id, - field_type, - condition_and_content: data, - }, - }; - self - .editor - .modify_view_filters(&self.view_id, params) - .await - .unwrap(); - } - - pub async fn update_text_filter( - &mut self, - filter: FilterPB, - condition: TextFilterConditionPB, - content: String, - changed: Option, - ) { - self.subscribe_view_changed().await; - self.assert_future_changed(changed).await; - let current_filter = filter.data.unwrap(); - let params = FilterChangeset::UpdateData { - filter_id: filter.id, - data: FilterInner::Data { - field_id: current_filter.field_id, - field_type: current_filter.field_type, - condition_and_content: BoxAny::new(TextFilterPB { condition, content }), - }, - }; - self - .editor - .modify_view_filters(&self.view_id, params) - .await - .unwrap(); - } - - pub async fn create_and_filter( - &mut self, - parent_filter_id: Option, - changed: Option, - ) { - self.subscribe_view_changed().await; - self.assert_future_changed(changed).await; - let params = FilterChangeset::Insert { - parent_filter_id, - data: FilterInner::And { children: vec![] }, - }; - self - .editor - .modify_view_filters(&self.view_id, params) - .await - .unwrap(); - } - - pub async fn create_or_filter( - &mut self, - parent_filter_id: Option, - changed: Option, - ) { - self.subscribe_view_changed().await; - self.assert_future_changed(changed).await; - let params = FilterChangeset::Insert { - parent_filter_id, - data: FilterInner::Or { children: vec![] }, - }; - self - .editor - .modify_view_filters(&self.view_id, params) - .await - .unwrap(); - } - - pub async fn delete_filter(&mut self, filter_id: String, changed: Option) { - self.subscribe_view_changed().await; - self.assert_future_changed(changed).await; - let params = FilterChangeset::Delete { filter_id }; - self - .editor - .modify_view_filters(&self.view_id, params) - .await - .unwrap(); - } - - pub async fn assert_filter_count(&self, count: usize) { - let filters = self.editor.get_all_filters(&self.view_id).await.items; - assert_eq!(count, filters.len()); - } - - pub async fn assert_grid_setting(&self, expected_setting: DatabaseViewSettingPB) { - let setting = self - .editor - .get_database_view_setting(&self.view_id) - .await - .unwrap(); - assert_eq!(expected_setting, setting); - } - - pub async fn assert_filters(&self, expected: Vec) { - let actual = self.get_all_filters().await; - for (actual_filter, expected_filter) in actual.iter().zip(expected.iter()) { - Self::assert_filter(actual_filter, expected_filter); - } - } - - pub async fn assert_number_of_visible_rows(&self, expected: usize) { - let (tx, rx) = tokio::sync::oneshot::channel(); - let _ = self - .editor - .open_database_view(&self.view_id, Some(tx)) - .await - .unwrap(); - rx.await.unwrap(); - - let rows = self.editor.get_all_rows(&self.view_id).await.unwrap(); - assert_eq!(rows.len(), expected); - } - - pub async fn wait(&self, millisecond: u64) { - tokio::time::sleep(Duration::from_millis(millisecond)).await; - } - - async fn subscribe_view_changed(&mut self) { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id) - .await - .unwrap(), - ); - } - - async fn assert_future_changed(&mut self, change: Option) { - if change.is_none() { - return; - } - let change = change.unwrap(); - let mut receiver = self.recv.take().unwrap(); - tokio::spawn(async move { - match tokio::time::timeout(Duration::from_secs(2), receiver.recv()).await { - Ok(changed) => { - if let DatabaseViewChanged::FilterNotification(notification) = changed.unwrap() { - assert_eq!( - notification.visible_rows.len(), - change.showing_num_of_rows, - "visible rows not match" - ); - assert_eq!( - notification.invisible_rows.len(), - change.hiding_num_of_rows, - "invisible rows not match" - ); - } - }, - Err(e) => { - panic!("Process filter task timeout: {:?}", e); - }, - } - }); - } - - fn assert_filter(actual: &FilterPB, expected: &FilterPB) { - assert_eq!(actual.filter_type, expected.filter_type); - assert_eq!(actual.children.is_empty(), expected.children.is_empty()); - assert_eq!(actual.data.is_some(), expected.data.is_some()); - - match actual.filter_type { - FilterType::Data => { - let actual_data = actual.data.clone().unwrap(); - let expected_data = expected.data.clone().unwrap(); - assert_eq!(actual_data.field_type, expected_data.field_type); - assert_eq!(actual_data.data, expected_data.data); - }, - FilterType::And | FilterType::Or => { - for (actual_child, expected_child) in actual.children.iter().zip(expected.children.iter()) { - Self::assert_filter(actual_child, expected_child); - } - }, - } - } -} - -impl std::ops::Deref for DatabaseFilterTest { - type Target = DatabaseEditorTest; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl std::ops::DerefMut for DatabaseFilterTest { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs index 4a92f4c93ffb4..79646c3648e3e 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs @@ -1,10 +1,10 @@ -use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; +use crate::database::database_editor::{DatabaseEditorTest, FilterRowChanged}; use flowy_database2::entities::{FieldType, SelectOptionFilterConditionPB, SelectOptionFilterPB}; use lib_infra::box_any::BoxAny; #[tokio::test] async fn grid_filter_multi_select_is_empty_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; // Create Multi-Select "Is Empty" filter test @@ -25,7 +25,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 = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; // Create Multi-Select "Is Not Empty" filter test @@ -46,7 +46,7 @@ async fn grid_filter_multi_select_is_not_empty_test() { #[tokio::test] async fn grid_filter_single_select_is_empty_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let expected = 3; let row_count = test.rows.len(); @@ -72,7 +72,7 @@ async fn grid_filter_single_select_is_empty_test() { #[tokio::test] async fn grid_filter_single_select_is_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let field = test.get_first_field(FieldType::SingleSelect).await; let mut options = test.get_single_select_type_option(&field.id).await; 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 = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let field = test.get_first_field(FieldType::SingleSelect).await; let row_details = test.get_rows().await; let mut options = test.get_single_select_type_option(&field.id).await; @@ -147,7 +147,7 @@ async fn grid_filter_single_select_is_test2() { #[tokio::test] async fn grid_filter_multi_select_contains_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let field = test.get_first_field(FieldType::MultiSelect).await; let mut options = test.get_multi_select_type_option(&field.id).await; @@ -170,7 +170,7 @@ async fn grid_filter_multi_select_contains_test() { #[tokio::test] async fn grid_filter_multi_select_contains_test2() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let field = test.get_first_field(FieldType::MultiSelect).await; let mut options = test.get_multi_select_type_option(&field.id).await; diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs index 88b2c0382f123..1eaafd58a7f77 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs @@ -1,10 +1,10 @@ -use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; +use crate::database::database_editor::{DatabaseEditorTest, FilterRowChanged}; use flowy_database2::entities::{FieldType, TextFilterConditionPB, TextFilterPB}; use lib_infra::box_any::BoxAny; #[tokio::test] async fn grid_filter_text_is_empty_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; // Create Text "Is Empty" filter test @@ -28,7 +28,7 @@ async fn grid_filter_text_is_empty_test() { #[tokio::test] async fn grid_filter_text_is_not_empty_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; // Create Text "Is Not Empty" filter test @@ -67,7 +67,7 @@ async fn grid_filter_text_is_not_empty_test() { #[tokio::test] async fn grid_filter_is_text_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; // Create Text "Is" filter test @@ -88,7 +88,7 @@ async fn grid_filter_is_text_test() { #[tokio::test] async fn grid_filter_contain_text_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; // Create Text "Contains" filter test @@ -109,7 +109,7 @@ async fn grid_filter_contain_text_test() { #[tokio::test] async fn grid_filter_contain_text_test2() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_detail = test.rows.clone(); // Create Text "Contains" filter @@ -143,7 +143,7 @@ async fn grid_filter_contain_text_test2() { #[tokio::test] async fn grid_filter_does_not_contain_text_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; // Create Text "Does Not Contain" filter test @@ -164,7 +164,7 @@ async fn grid_filter_does_not_contain_text_test() { #[tokio::test] async fn grid_filter_start_with_text_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; // Create Text "Starts With" filter test @@ -185,7 +185,7 @@ async fn grid_filter_start_with_text_test() { #[tokio::test] async fn grid_filter_ends_with_text_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; // Create Text "Ends With" filter test @@ -206,7 +206,7 @@ async fn grid_filter_ends_with_text_test() { #[tokio::test] async fn grid_update_text_filter_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; // Create Text "Ends With" filter test @@ -248,7 +248,7 @@ async fn grid_update_text_filter_test() { #[tokio::test] async fn grid_filter_delete_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; // Create Text "Is Empty" filter test @@ -278,7 +278,7 @@ async fn grid_filter_delete_test() { #[tokio::test] async fn grid_filter_update_empty_text_cell_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row = test.rows.clone(); // Create Text "Is Empty" filter diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/time_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/time_filter_test.rs index 6512947c16204..d56e889a53f69 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/time_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/time_filter_test.rs @@ -1,10 +1,10 @@ -use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; +use crate::database::database_editor::{DatabaseEditorTest, FilterRowChanged}; use flowy_database2::entities::{FieldType, NumberFilterConditionPB, TimeFilterPB}; use lib_infra::box_any::BoxAny; #[tokio::test] async fn grid_filter_time_is_equal_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 1; @@ -30,7 +30,7 @@ async fn grid_filter_time_is_equal_test() { #[tokio::test] async fn grid_filter_time_is_less_than_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 1; @@ -56,7 +56,7 @@ async fn grid_filter_time_is_less_than_test() { #[tokio::test] async fn grid_filter_time_is_less_than_or_equal_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 1; @@ -82,7 +82,7 @@ async fn grid_filter_time_is_less_than_or_equal_test() { #[tokio::test] async fn grid_filter_time_is_empty_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 6; @@ -108,7 +108,7 @@ async fn grid_filter_time_is_empty_test() { #[tokio::test] async fn grid_filter_time_is_not_empty_test() { - let mut test = DatabaseFilterTest::new().await; + let mut test = DatabaseEditorTest::new_grid().await; let row_count = test.rows.len(); let expected = 1;