Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14f656be75 | ||
|
|
56bbe29058 | ||
|
|
f03d8d35ef | ||
|
|
d110e9446e | ||
|
|
826170e110 | ||
|
|
c590d59ed5 | ||
|
|
89b4e334fb | ||
|
|
79c186fa99 | ||
|
|
398a9010bf | ||
|
|
61b66291cb |
No files matched your search
+574
-5
@@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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<void> main() async {
|
||||
@@ -13,5 +14,6 @@ Future<void> runIntegration4OnDesktop() async {
|
||||
first_test.main();
|
||||
|
||||
document_test_runner_2.main();
|
||||
grid_calculations_test.main();
|
||||
// DON'T add more tests here.
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
-2
@@ -9,11 +9,9 @@ class CalculationsBackendService {
|
||||
final String viewId;
|
||||
|
||||
// Get Calculations (initial fetch)
|
||||
|
||||
Future<FlowyResult<RepeatedCalculationsPB, FlowyError>>
|
||||
getCalculations() async {
|
||||
final payload = DatabaseViewIdPB()..value = viewId;
|
||||
|
||||
return DatabaseEventGetAllCalculations(payload).send();
|
||||
}
|
||||
|
||||
|
||||
+54
-31
@@ -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<CalculateCell> {
|
||||
}
|
||||
});
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
if (widget.calculation != null)
|
||||
RemoveCalculationButton(
|
||||
onTap: () => context.read<CalculationsBloc>().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<CalculationsBloc>().add(
|
||||
CalculationsEvent.updateCalculationType(
|
||||
widget.fieldInfo.id,
|
||||
type,
|
||||
calculationId: widget.calculation?.id,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
return BlocProvider.value(
|
||||
value: context.read<CalculationsBloc>(),
|
||||
child: CalculateSelector(
|
||||
fieldInfo: widget.fieldInfo,
|
||||
calculation: widget.calculation,
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -125,7 +102,7 @@ class _CalculateCellState extends State<CalculateCell> {
|
||||
}
|
||||
|
||||
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<CalculateCell> {
|
||||
_ => 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<CalculationsBloc>().add(
|
||||
CalculationsEvent.removeCalculation(
|
||||
fieldInfo.id,
|
||||
calculation!.id,
|
||||
),
|
||||
),
|
||||
),
|
||||
...fieldInfo.fieldType.calculationsForFieldType().map(
|
||||
(type) => CalculationTypeItem(
|
||||
type: type,
|
||||
onTap: () {
|
||||
if (type != calculation?.calculationType) {
|
||||
context.read<CalculationsBloc>().add(
|
||||
CalculationsEvent.updateCalculationType(
|
||||
fieldInfo.id,
|
||||
type,
|
||||
calculationId: calculation?.id,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+3
-11
@@ -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(),
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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::<Vec<_>>();
|
||||
|
||||
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<Field>, rows: Vec<Arc<Row>>) -> 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::<Vec<_>>();
|
||||
|
||||
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);
|
||||
|
||||
+32
-7
@@ -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
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
mod calculation_test;
|
||||
mod script;
|
||||
@@ -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<Receiver<DatabaseViewChanged>>,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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<Arc<Row>>,
|
||||
pub field_count: usize,
|
||||
pub row_by_row_id: HashMap<String, RowMetaPB>,
|
||||
view_change_recv: Option<Receiver<DatabaseViewChanged>>,
|
||||
}
|
||||
|
||||
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<FilterPB> {
|
||||
self.editor.get_all_filters(&self.view_id).await.items
|
||||
}
|
||||
|
||||
pub async fn get_filter(
|
||||
&self,
|
||||
filter_type: FilterType,
|
||||
field_type: Option<FieldType>,
|
||||
) -> Option<FilterPB> {
|
||||
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<FieldType>,
|
||||
) -> Option<FilterPB> {
|
||||
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<FilterRowChanged>,
|
||||
) {
|
||||
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<String>) {
|
||||
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<FilterRowChanged>,
|
||||
) {
|
||||
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<String>,
|
||||
field_type: FieldType,
|
||||
data: BoxAny,
|
||||
changed: Option<FilterRowChanged>,
|
||||
) {
|
||||
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<FilterRowChanged>,
|
||||
) {
|
||||
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<String>,
|
||||
changed: Option<FilterRowChanged>,
|
||||
) {
|
||||
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<String>,
|
||||
changed: Option<FilterRowChanged>,
|
||||
) {
|
||||
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<FilterRowChanged>) {
|
||||
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<FilterPB>) {
|
||||
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<FilterRowChanged>) {
|
||||
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,
|
||||
}
|
||||
+3
-3
@@ -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 {
|
||||
|
||||
+3
-3
@@ -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();
|
||||
|
||||
|
||||
+4
-4
@@ -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<String> {
|
||||
async fn get_checklist_cell_options(test: &DatabaseEditorTest) -> Vec<String> {
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<Receiver<DatabaseViewChanged>>,
|
||||
}
|
||||
|
||||
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<FilterPB> {
|
||||
self.editor.get_all_filters(&self.view_id).await.items
|
||||
}
|
||||
|
||||
pub async fn get_filter(
|
||||
&self,
|
||||
filter_type: FilterType,
|
||||
field_type: Option<FieldType>,
|
||||
) -> Option<FilterPB> {
|
||||
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<FieldType>,
|
||||
) -> Option<FilterPB> {
|
||||
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<FilterRowChanged>,
|
||||
) {
|
||||
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<String>) {
|
||||
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<FilterRowChanged>,
|
||||
) {
|
||||
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<String>,
|
||||
field_type: FieldType,
|
||||
data: BoxAny,
|
||||
changed: Option<FilterRowChanged>,
|
||||
) {
|
||||
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<FilterRowChanged>,
|
||||
) {
|
||||
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<String>,
|
||||
changed: Option<FilterRowChanged>,
|
||||
) {
|
||||
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<String>,
|
||||
changed: Option<FilterRowChanged>,
|
||||
) {
|
||||
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<FilterRowChanged>) {
|
||||
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<FilterPB>) {
|
||||
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<FilterRowChanged>) {
|
||||
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
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in new issue
Block a user