Bläddra i källkod

test: import database integration (#2803)

* feat: support importing database raw data

* feat: verify import database test

* test: fix test

* ci: update integration test ci config

* ci: codecov with os flag

* ci: update docker command

* ci: update docker command

* ci: update docker command

* ci: update docker command

* test: add filter test
Nathan.fooo 1 år sedan
förälder
incheckning
d96a1d8bd4
35 ändrade filer med 720 tillägg och 139 borttagningar
  1. 2 1
      .github/workflows/flutter_ci.yaml
  2. 25 23
      .github/workflows/integration_test.yml
  3. 0 0
      frontend/appflowy_flutter/assets/test/workspaces/database/v020.afdb
  4. 2 1
      frontend/appflowy_flutter/assets/translations/en.json
  5. 0 1
      frontend/appflowy_flutter/integration_test/database_field_test.dart
  6. 72 0
      frontend/appflowy_flutter/integration_test/database_filter_test.dart
  7. 0 12
      frontend/appflowy_flutter/integration_test/database_row_page_test.dart
  8. 215 0
      frontend/appflowy_flutter/integration_test/database_share_test.dart
  9. 14 0
      frontend/appflowy_flutter/integration_test/runner.dart
  10. 14 12
      frontend/appflowy_flutter/integration_test/util/base.dart
  11. 5 0
      frontend/appflowy_flutter/integration_test/util/common_operations.dart
  12. 255 53
      frontend/appflowy_flutter/integration_test/util/database_test_op.dart
  13. 4 1
      frontend/appflowy_flutter/lib/main.dart
  14. 3 3
      frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart
  15. 3 3
      frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart
  16. 2 0
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart
  17. 36 11
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart
  18. 9 4
      frontend/appflowy_flutter/lib/startup/startup.dart
  19. 1 0
      frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart
  20. 4 1
      frontend/appflowy_flutter/lib/startup/tasks/windows.dart
  21. 1 0
      frontend/appflowy_flutter/lib/user/presentation/skip_log_in_screen.dart
  22. 3 1
      frontend/appflowy_flutter/lib/workspace/application/settings/share/import_service.dart
  23. 1 0
      frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/add_button.dart
  24. 9 0
      frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/import/import_panel.dart
  25. 18 1
      frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/import/import_type.dart
  26. 2 0
      frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart
  27. 2 2
      frontend/appflowy_flutter/pubspec.lock
  28. 1 0
      frontend/appflowy_flutter/pubspec.yaml
  29. 4 1
      frontend/appflowy_flutter/test/util.dart
  30. 1 0
      frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs
  31. 1 1
      frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs
  32. 3 1
      frontend/rust-lib/flowy-folder2/src/entities/import.rs
  33. 2 1
      frontend/rust-lib/flowy-folder2/src/share/import.rs
  34. 1 1
      frontend/rust-lib/flowy-test/tests/database/test.rs
  35. 5 4
      frontend/scripts/docker-buildfiles/Dockerfile

+ 2 - 1
.github/workflows/flutter_ci.yaml

@@ -124,4 +124,5 @@ jobs:
           name: appflowy
           flags: appflowy_flutter_unit_test
           fail_ci_if_error: true
-          verbose: true
+          verbose: true
+          os: ${{ matrix.os }}

+ 25 - 23
.github/workflows/integration_test.yml

@@ -21,6 +21,8 @@ on:
 
 env:
   CARGO_TERM_COLOR: always
+  FLUTTER_VERSION: "3.10.1"
+  RUST_TOOLCHAIN: "1.70"
 
 jobs:
   tests:
@@ -36,31 +38,36 @@ jobs:
         with:
           toolchain: "stable-2022-04-07"
 
-      - uses: subosito/flutter-action@v2
+      - name: Install Rust toolchain
+        id: rust_toolchain
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: ${{ env.RUST_TOOLCHAIN }}
+          target: ${{ matrix.target }}
+          override: true
+          profile: minimal
+
+      - name: Install flutter
+        id: flutter
+        uses: subosito/flutter-action@v2
         with:
           channel: "stable"
-          flutter-version: "3.10.1"
+          flutter-version: ${{ env.FLUTTER_VERSION }}
           cache: true
 
-      - name: Cache Cargo
-        uses: actions/cache@v2
+      - uses: Swatinem/rust-cache@v2
         with:
-          path: |
-            ~/.cargo
-          key: ${{ runner.os }}-cargo-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
+          prefix-key: ${{ matrix.os }}
+          workspaces: |
+            frontend/rust-lib
 
-      - name: Cache Rust
-        id: cache-rust-target
-        uses: actions/cache@v2
+      - uses: davidB/rust-cargo-make@v1
         with:
-          path: |
-            frontend/rust-lib/target
-            shared-lib/target
-          key: ${{ runner.os }}-rust-rust-lib-share-lib-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
+          version: '0.36.6'
 
-      - name: Setup Environment
+      - name: Install prerequisites
+        working-directory: frontend
         run: |
-          cargo install --force cargo-make
           cargo install --force duckscript_cli
           if [ "$RUNNER_OS" == "Linux" ]; then
             sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
@@ -73,14 +80,8 @@ jobs:
           elif [ "$RUNNER_OS" == "macOS" ]; then
             echo 'do nothing'
           fi
-        shell: bash
-
-      - if: steps.cache-cargo.outputs.cache-hit != 'true'
-        name: Rust Deps
-        working-directory: frontend
-        run: |
-          cargo install cargo-make
           cargo make appflowy-flutter-deps-tools
+        shell: bash
 
       - name: Config Flutter
         run: |
@@ -126,3 +127,4 @@ jobs:
           flags: appflowy_flutter_integrateion_test
           fail_ci_if_error: true
           verbose: true
+          os: ${{ matrix.os }}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
frontend/appflowy_flutter/assets/test/workspaces/database/v020.afdb


+ 2 - 1
frontend/appflowy_flutter/assets/translations/en.json

@@ -55,7 +55,8 @@
     "textAndMarkdown": "Text & Markdown",
     "documentFromV010": "Document from v0.1.0",
     "databaseFromV010": "Database from v0.1.0",
-    "csv": "CSV"
+    "csv": "CSV",
+    "database": "Database"
   },
   "disclosureAction": {
     "rename": "Rename",

+ 0 - 1
frontend/appflowy_flutter/integration_test/database_field_test.dart

@@ -114,7 +114,6 @@ void main() {
       await tester.tapNewPropertyButton();
       await tester.renameField('New field 1');
       await tester.dismissFieldEditor();
-      await tester.createField(FieldType.RichText, 'New field 1');
 
       // Delete the field
       await tester.tapGridFieldWithName('New field 1');

+ 72 - 0
frontend/appflowy_flutter/integration_test/database_filter_test.dart

@@ -0,0 +1,72 @@
+import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+
+import 'util/database_test_op.dart';
+import 'util/util.dart';
+
+void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  group('grid', () {
+    const location = 'import_files';
+
+    setUp(() async {
+      await TestFolder.cleanTestLocation(location);
+      await TestFolder.setTestLocation(location);
+    });
+
+    tearDown(() async {
+      await TestFolder.cleanTestLocation(location);
+    });
+
+    tearDownAll(() async {
+      await TestFolder.cleanTestLocation(null);
+    });
+
+    testWidgets('add text filter', (tester) async {
+      await tester.openV020database();
+
+      // create a filter
+      await tester.tapDatabaseFilterButton();
+      await tester.tapCreateFilterByFieldType(FieldType.RichText, 'Name');
+      await tester.tapFilterButtonInGrid('Name');
+
+      // enter 'A' in the filter text field
+      await tester.assertNumberOfRowsInGridPage(10);
+      await tester.enterTextInTextFilter('A');
+      await tester.assertNumberOfRowsInGridPage(1);
+
+      // after remove the filter, the grid should show all rows
+      await tester.enterTextInTextFilter('');
+      await tester.assertNumberOfRowsInGridPage(10);
+
+      await tester.enterTextInTextFilter('B');
+      await tester.assertNumberOfRowsInGridPage(1);
+
+      // open the menu to delete the filter
+      await tester.tapTextFilterDisclosureButtonInGrid();
+      await tester.tapDeleteTextFilterButtonInGrid();
+      await tester.assertNumberOfRowsInGridPage(10);
+
+      await tester.pumpAndSettle();
+    });
+
+    testWidgets('add checklist filter', (tester) async {
+      await tester.openV020database();
+
+      // create a filter
+      await tester.tapDatabaseFilterButton();
+      await tester.tapCreateFilterByFieldType(FieldType.Checkbox, 'Done');
+      await tester.assertNumberOfRowsInGridPage(5);
+
+      await tester.tapFilterButtonInGrid('Done');
+      await tester.tapCheckboxFilterButtonInGrid();
+
+      await tester.tapUnCheckedButtonOnCheckboxFilter();
+      await tester.assertNumberOfRowsInGridPage(5);
+
+      await tester.pumpAndSettle();
+    });
+  });
+}

+ 0 - 12
frontend/appflowy_flutter/integration_test/database_row_page_test.dart

@@ -28,18 +28,6 @@ void main() {
       await TestFolder.cleanTestLocation(null);
     });
 
-    testWidgets('open first row of the grid', (tester) async {
-      await tester.initializeAppFlowy();
-      await tester.tapGoButton();
-
-      // Create a new grid
-      await tester.tapAddButton();
-      await tester.tapCreateGridButton();
-
-      // Hover first row and then open the row page
-      await tester.openFirstRowDetailPage();
-    });
-
     testWidgets('insert emoji in the row detail page', (tester) async {
       await tester.initializeAppFlowy();
       await tester.tapGoButton();

+ 215 - 0
frontend/appflowy_flutter/integration_test/database_share_test.dart

@@ -0,0 +1,215 @@
+import 'dart:io';
+
+import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:integration_test/integration_test.dart';
+
+import 'util/database_test_op.dart';
+import 'util/mock/mock_file_picker.dart';
+import 'util/util.dart';
+import 'package:path/path.dart' as p;
+
+void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  group('database', () {
+    const location = 'import_files';
+    setUp(() async {
+      await TestFolder.cleanTestLocation(location);
+      await TestFolder.setTestLocation(location);
+    });
+
+    tearDown(() async {
+      await TestFolder.cleanTestLocation(location);
+    });
+
+    tearDownAll(() async {
+      await TestFolder.cleanTestLocation(null);
+    });
+
+    testWidgets('import v0.2.0 database data', (tester) async {
+      await tester.initializeAppFlowy();
+      await tester.tapGoButton();
+
+      // expect to see a readme page
+      tester.expectToSeePageName(readme);
+
+      await tester.tapAddButton();
+      await tester.tapImportButton();
+
+      final testFileNames = ['v020.afdb'];
+      final fileLocation = await tester.currentFileLocation();
+      for (final fileName in testFileNames) {
+        final str = await rootBundle.loadString(
+          p.join(
+            'assets/test/workspaces/database',
+            fileName,
+          ),
+        );
+        File(p.join(fileLocation, fileName)).writeAsStringSync(str);
+      }
+      // mock get files
+      await mockPickFilePaths(testFileNames, name: location);
+      await tester.tapDatabaseRawDataButton();
+      await tester.openPage('v020');
+
+      // check the import content
+      // await tester.assertCellContent(
+      //   rowIndex: 7,
+      //   fieldType: FieldType.RichText,
+      //   // fieldName: 'Name',
+      //   content: '',
+      // );
+
+      // check the text cell
+      final textCells = <String>['A', 'B', 'C', 'D', 'E', '', '', '', '', ''];
+      for (final (index, content) in textCells.indexed) {
+        await tester.assertCellContent(
+          rowIndex: index,
+          fieldType: FieldType.RichText,
+          content: content,
+        );
+      }
+
+      // check the checkbox cell
+      final checkboxCells = <bool>[
+        true,
+        true,
+        true,
+        true,
+        true,
+        false,
+        false,
+        false,
+        false,
+        false
+      ];
+      for (final (index, content) in checkboxCells.indexed) {
+        await tester.assertCheckboxCell(
+          rowIndex: index,
+          isSelected: content,
+        );
+      }
+
+      // check the number cell
+      final numberCells = <String>[
+        '-1',
+        '-2',
+        '0.1',
+        '0.2',
+        '1',
+        '2',
+        '10',
+        '11',
+        '12',
+        ''
+      ];
+      for (final (index, content) in numberCells.indexed) {
+        await tester.assertCellContent(
+          rowIndex: index,
+          fieldType: FieldType.Number,
+          content: content,
+        );
+      }
+
+      // check the url cell
+      final urlCells = <String>[
+        'appflowy.io',
+        'no url',
+        'appflowy.io',
+        'https://github.com/AppFlowy-IO/',
+        '',
+        '',
+      ];
+      for (final (index, content) in urlCells.indexed) {
+        await tester.assertCellContent(
+          rowIndex: index,
+          fieldType: FieldType.URL,
+          content: content,
+        );
+      }
+
+      // check the single select cell
+      final singleSelectCells = <String>[
+        's1',
+        's2',
+        's3',
+        's4',
+        's5',
+        '',
+        '',
+        '',
+        '',
+        '',
+      ];
+      for (final (index, content) in singleSelectCells.indexed) {
+        await tester.assertSingleSelectOption(
+          rowIndex: index,
+          content: content,
+        );
+      }
+
+      // check the multi select cell
+      final List<List<String>> multiSelectCells = [
+        ['m1'],
+        ['m1', 'm2'],
+        ['m1', 'm2', 'm3'],
+        ['m1', 'm2', 'm3'],
+        ['m1', 'm2', 'm3', 'm4', 'm5'],
+        [],
+        [],
+        [],
+        [],
+        [],
+      ];
+      for (final (index, contents) in multiSelectCells.indexed) {
+        await tester.assertMultiSelectOption(
+          rowIndex: index,
+          contents: contents,
+        );
+      }
+
+      // check the checklist cell
+      final List<double> checklistCells = [
+        0.6,
+        0.3,
+        1.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+        0.0,
+      ];
+      for (final (index, percent) in checklistCells.indexed) {
+        await tester.assertChecklistCellInGrid(
+          rowIndex: index,
+          percent: percent,
+        );
+      }
+
+      // check the date cell
+      final List<String> dateCells = [
+        'Jun 01, 2023',
+        'Jun 02, 2023',
+        'Jun 03, 2023',
+        'Jun 04, 2023',
+        'Jun 05, 2023',
+        'Jun 05, 2023',
+        'Jun 16, 2023',
+        '',
+        '',
+        ''
+      ];
+      for (final (index, content) in dateCells.indexed) {
+        await tester.assertDateCellInGrid(
+          rowIndex: index,
+          fieldType: FieldType.DateTime,
+          content: content,
+        );
+      }
+    });
+  });
+}

+ 14 - 0
frontend/appflowy_flutter/integration_test/runner.dart

@@ -6,6 +6,13 @@ import 'cover_image_test.dart' as cover_image_test;
 import 'share_markdown_test.dart' as share_markdown_test;
 import 'import_files_test.dart' as import_files_test;
 import 'document_with_database_test.dart' as document_with_database_test;
+import 'database_cell_test.dart' as database_cell_test;
+import 'database_field_test.dart' as database_field_test;
+import 'database_share_test.dart' as database_share_test;
+import 'database_row_page_test.dart' as database_row_page_test;
+import 'database_row_test.dart' as database_row_test;
+import 'database_setting_test.dart' as database_setting_test;
+import 'database_filter_test.dart' as database_filter_test;
 
 /// The main task runner for all integration tests in AppFlowy.
 ///
@@ -22,6 +29,13 @@ void main() {
   share_markdown_test.main();
   import_files_test.main();
   document_with_database_test.main();
+  database_cell_test.main();
+  database_field_test.main();
+  database_share_test.main();
+  database_row_page_test.main();
+  database_row_test.main();
+  database_setting_test.main();
+  database_filter_test.main();
   // board_test.main();
   // empty_document_test.main();
   // smart_menu_test.main();

+ 14 - 12
frontend/appflowy_flutter/integration_test/util/base.dart

@@ -1,10 +1,12 @@
 import 'dart:io';
 
 import 'package:appflowy/core/config/kv_keys.dart';
-import 'package:appflowy/main.dart' as app;
+import 'package:appflowy/startup/entry_point.dart';
+import 'package:appflowy/startup/startup.dart';
 import 'package:appflowy/startup/tasks/prelude.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:path_provider/path_provider.dart';
@@ -60,18 +62,18 @@ class TestFolder {
 extension AppFlowyTestBase on WidgetTester {
   Future<void> initializeAppFlowy() async {
     TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
-        .setMockMethodCallHandler(
-      const MethodChannel('hotkey_manager'),
-      (MethodCall methodCall) async {
-        if (methodCall.method == 'unregisterAll') {
-          // do nothing
-        }
-
-        return;
-      },
-    );
+        .setMockMethodCallHandler(const MethodChannel('hotkey_manager'),
+            (MethodCall methodCall) async {
+      if (methodCall.method == 'unregisterAll') {
+        // do nothing
+      }
+
+      return;
+    });
+
+    WidgetsFlutterBinding.ensureInitialized();
+    await FlowyRunner.run(FlowyApp(), IntegrationMode.integrationTest);
 
-    await app.main();
     await wait(3000);
     await pumpAndSettle(const Duration(seconds: 2));
   }

+ 5 - 0
frontend/appflowy_flutter/integration_test/util/common_operations.dart

@@ -140,6 +140,11 @@ extension CommonOperations on WidgetTester {
     }
   }
 
+  /// open the page with given name.
+  Future<void> openPage(String name) async {
+    await tapButton(findPageName(name));
+  }
+
   /// Tap the ... button beside the page name.
   ///
   /// Must call [hoverOnPageName] first.

+ 255 - 53
frontend/appflowy_flutter/integration_test/util/database_test_op.dart

@@ -1,19 +1,31 @@
+import 'dart:io';
 import 'dart:ui';
 
 import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/database_view/application/setting/setting_bloc.dart';
 import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart';
 import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/text.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/disclosure_button.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/filter_menu_item.dart';
 import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_list.dart';
 import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart';
 import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart';
+import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart';
+import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
 import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart';
 import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart';
 import 'package:appflowy/plugins/database_view/widgets/setting/database_setting.dart';
 import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
+import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
+import 'package:flowy_infra_ui/style_widget/text_field.dart';
 import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
@@ -36,8 +48,39 @@ import 'package:table_calendar/table_calendar.dart';
 
 import 'base.dart';
 import 'common_operations.dart';
+import 'expectation.dart';
+import 'package:path/path.dart' as p;
+
+import 'mock/mock_file_picker.dart';
 
 extension AppFlowyDatabaseTest on WidgetTester {
+  Future<void> openV020database() async {
+    await initializeAppFlowy();
+    await tapGoButton();
+
+    // expect to see a readme page
+    expectToSeePageName(readme);
+
+    await tapAddButton();
+    await tapImportButton();
+
+    final testFileNames = ['v020.afdb'];
+    final fileLocation = await currentFileLocation();
+    for (final fileName in testFileNames) {
+      final str = await rootBundle.loadString(
+        p.join(
+          'assets/test/workspaces/database',
+          fileName,
+        ),
+      );
+      File(p.join(fileLocation, fileName)).writeAsStringSync(str);
+    }
+    // mock get files
+    await mockPickFilePaths(testFileNames, name: 'import_files');
+    await tapDatabaseRawDataButton();
+    await openPage('v020');
+  }
+
   Future<void> hoverOnFirstRowOfGrid() async {
     final findRow = find.byType(GridRow);
     expect(findRow, findsWidgets);
@@ -51,29 +94,27 @@ extension AppFlowyDatabaseTest on WidgetTester {
     required FieldType fieldType,
     required String input,
   }) async {
-    final findRow = find.byType(GridRow);
-    final findCell = finderForFieldType(fieldType);
-
-    final cell = find.descendant(
-      of: findRow.at(rowIndex),
-      matching: findCell,
-    );
+    final cell = cellFinder(rowIndex, fieldType);
 
     expect(cell, findsOneWidget);
     await enterText(cell, input);
     await pumpAndSettle();
   }
 
-  Future<void> tapCheckboxCellInGrid({
-    required int rowIndex,
-  }) async {
-    final findRow = find.byType(GridRow);
-    final findCell = finderForFieldType(FieldType.Checkbox);
-
-    final cell = find.descendant(
+  Finder cellFinder(int rowIndex, FieldType fieldType) {
+    final findRow = find.byType(GridRow, skipOffstage: false);
+    final findCell = finderForFieldType(fieldType);
+    return find.descendant(
       of: findRow.at(rowIndex),
       matching: findCell,
+      skipOffstage: false,
     );
+  }
+
+  Future<void> tapCheckboxCellInGrid({
+    required int rowIndex,
+  }) async {
+    final cell = cellFinder(rowIndex, FieldType.Checkbox);
 
     final button = find.descendant(
       of: cell,
@@ -88,14 +129,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
     required int rowIndex,
     required bool isSelected,
   }) async {
-    final findRow = find.byType(GridRow);
-    final findCell = finderForFieldType(FieldType.Checkbox);
-
-    final cell = find.descendant(
-      of: findRow.at(rowIndex),
-      matching: findCell,
-    );
-
+    final cell = cellFinder(rowIndex, FieldType.Checkbox);
     var finder = find.byType(CheckboxCellUncheck);
     if (isSelected) {
       finder = find.byType(CheckboxCellCheck);
@@ -114,36 +148,118 @@ extension AppFlowyDatabaseTest on WidgetTester {
     required int rowIndex,
     required FieldType fieldType,
   }) async {
-    final findRow = find.byType(GridRow);
-    final findCell = finderForFieldType(fieldType);
+    final cell = cellFinder(rowIndex, fieldType);
+    expect(cell, findsOneWidget);
+    await tapButton(cell, warnIfMissed: false);
+  }
 
-    final cell = find.descendant(
-      of: findRow.at(rowIndex),
-      matching: findCell,
+  /// The [fieldName] must be uqniue in the grid.
+  Future<void> assertCellContent({
+    required int rowIndex,
+    required FieldType fieldType,
+    required String content,
+  }) async {
+    final findCell = cellFinder(rowIndex, fieldType);
+    final findContent = find.descendant(
+      of: findCell,
+      matching: find.text(content),
+      skipOffstage: false,
     );
 
-    expect(cell, findsOneWidget);
-    await tapButton(cell);
+    final text = find.descendant(
+      of: find.byType(TextField),
+      matching: findContent,
+      skipOffstage: false,
+    );
+
+    expect(text, findsOneWidget);
   }
 
-  Future<void> assertCellContent({
+  Future<void> assertSingleSelectOption({
+    required int rowIndex,
+    required String content,
+  }) async {
+    final findCell = cellFinder(rowIndex, FieldType.SingleSelect);
+    if (content.isNotEmpty) {
+      final finder = find.descendant(
+        of: findCell,
+        matching: find.byWidgetPredicate(
+          (widget) => widget is SelectOptionTag && widget.name == content,
+        ),
+      );
+      expect(finder, findsOneWidget);
+    }
+  }
+
+  Future<void> assertMultiSelectOption({
+    required int rowIndex,
+    required List<String> contents,
+  }) async {
+    final findCell = cellFinder(rowIndex, FieldType.MultiSelect);
+    for (final content in contents) {
+      if (content.isNotEmpty) {
+        final finder = find.descendant(
+          of: findCell,
+          matching: find.byWidgetPredicate(
+            (widget) => widget is SelectOptionTag && widget.name == content,
+          ),
+        );
+        expect(finder, findsOneWidget);
+      }
+    }
+  }
+
+  Future<void> assertChecklistCellInGrid({
+    required int rowIndex,
+    required double percent,
+  }) async {
+    final findCell = cellFinder(rowIndex, FieldType.Checklist);
+    final finder = find.descendant(
+      of: findCell,
+      matching: find.byWidgetPredicate(
+        (widget) {
+          if (widget is ChecklistProgressBar) {
+            return widget.percent == percent;
+          }
+          return false;
+        },
+      ),
+    );
+    expect(finder, findsOneWidget);
+  }
+
+  Future<void> assertDateCellInGrid({
     required int rowIndex,
     required FieldType fieldType,
     required String content,
   }) async {
-    final findRow = find.byType(GridRow);
-    final findCell = finderForFieldType(fieldType);
-    final cell = find.descendant(
+    final findRow = find.byType(GridRow, skipOffstage: false);
+    final findCell = find.descendant(
       of: findRow.at(rowIndex),
-      matching: findCell,
+      matching: find.byWidgetPredicate(
+        (widget) => widget is GridDateCell && widget.fieldType == fieldType,
+      ),
+      skipOffstage: false,
     );
 
-    final findContent = find.descendant(
-      of: cell,
-      matching: find.text(content),
+    final dateCellText = find.descendant(
+      of: findCell,
+      matching: find.byType(GridDateCellText),
     );
 
-    expect(findContent, findsOneWidget);
+    final text = find.descendant(
+      of: dateCellText,
+      matching: find.byWidgetPredicate(
+        (widget) {
+          if (widget is FlowyText) {
+            return widget.title == content;
+          }
+          return false;
+        },
+      ),
+      skipOffstage: false,
+    );
+    expect(text, findsOneWidget);
   }
 
   Future<void> selectDay({
@@ -290,8 +406,13 @@ extension AppFlowyDatabaseTest on WidgetTester {
 
   /// Must call [tapTypeOptionButton] first.
   Future<void> selectFieldType(FieldType fieldType) async {
-    final fieldTypeButton = find.byWidgetPredicate(
-      (widget) => widget is FlowyText && widget.title == fieldType.title(),
+    final fieldTypeCell = find.byType(FieldTypeCell);
+
+    final fieldTypeButton = find.descendant(
+      of: fieldTypeCell,
+      matching: find.byWidgetPredicate(
+        (widget) => widget is FlowyText && widget.title == fieldType.title(),
+      ),
     );
     await tapButton(fieldTypeButton);
   }
@@ -308,7 +429,10 @@ extension AppFlowyDatabaseTest on WidgetTester {
   }
 
   Future<void> assertNumberOfRowsInGridPage(int num) async {
-    expect(find.byType(GridRow), findsNWidgets(num));
+    expect(
+      find.byType(GridRow, skipOffstage: false),
+      findsNWidgets(num),
+    );
   }
 
   Future<void> assertDocumentExistInRowDetailPage() async {
@@ -353,9 +477,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
 
   Future<void> dismissFieldEditor() async {
     await sendKeyEvent(LogicalKeyboardKey.escape);
-    await sendKeyEvent(LogicalKeyboardKey.escape);
-    await sendKeyEvent(LogicalKeyboardKey.escape);
-    await pumpAndSettle();
+    await pumpAndSettle(const Duration(milliseconds: 200));
   }
 
   Future<void> findFieldEditor(dynamic matcher) async {
@@ -405,6 +527,82 @@ extension AppFlowyDatabaseTest on WidgetTester {
     await tapButton(find.byType(SettingButton));
   }
 
+  Future<void> tapDatabaseFilterButton() async {
+    await tapButton(find.byType(FilterButton));
+  }
+
+  Future<void> tapCreateFilterByFieldType(
+    FieldType fieldType,
+    String title,
+  ) async {
+    final findFilter = find.byWidgetPredicate(
+      (widget) =>
+          widget is GridFilterPropertyCell &&
+          widget.fieldInfo.fieldType == fieldType &&
+          widget.fieldInfo.name == title,
+    );
+
+    await tapButton(findFilter);
+  }
+
+  Future<void> tapFilterButtonInGrid(String filterName) async {
+    final findFilter = find.byType(FilterMenuItem);
+    final button = find.descendant(
+      of: findFilter,
+      matching: find.text(filterName),
+    );
+
+    await tapButton(button);
+  }
+
+  Future<void> enterTextInTextFilter(String text) async {
+    final findEditor = find.byType(TextFilterEditor);
+    final findTextField = find.descendant(
+      of: findEditor,
+      matching: find.byType(FlowyTextField),
+    );
+
+    await enterText(findTextField, text);
+    await pumpAndSettle(const Duration(milliseconds: 300));
+  }
+
+  Future<void> tapTextFilterDisclosureButtonInGrid() async {
+    final findEditor = find.byType(TextFilterEditor);
+    final findDisclosure = find.descendant(
+      of: findEditor,
+      matching: find.byType(DisclosureButton),
+    );
+
+    await tapButton(findDisclosure);
+  }
+
+  /// must call [tapTextFilterDisclosureButtonInGrid] first.
+  Future<void> tapDeleteTextFilterButtonInGrid() async {
+    await tapButton(find.text(LocaleKeys.grid_settings_deleteFilter.tr()));
+  }
+
+  Future<void> tapCheckboxFilterButtonInGrid() async {
+    await tapButton(find.byType(CheckboxFilterConditionList));
+  }
+
+  Future<void> tapCheckedButtonOnCheckboxFilter() async {
+    final button = find.descendant(
+      of: find.byType(HoverButton),
+      matching: find.text(LocaleKeys.grid_checkboxFilter_isChecked.tr()),
+    );
+
+    await tapButton(button);
+  }
+
+  Future<void> tapUnCheckedButtonOnCheckboxFilter() async {
+    final button = find.descendant(
+      of: find.byType(HoverButton),
+      matching: find.text(LocaleKeys.grid_checkboxFilter_isUnchecked.tr()),
+    );
+
+    await tapButton(button);
+  }
+
   /// Should call [tapDatabaseSettingButton] first.
   Future<void> tapDatabaseLayoutButton() async {
     final findSettingItem = find.byType(DatabaseSettingItem);
@@ -439,6 +637,10 @@ extension AppFlowyDatabaseTest on WidgetTester {
   Future<void> assertCurrentDatabaseLayoutType(DatabaseLayoutPB layout) async {
     expect(finderForDatabaseLayoutType(layout), findsOneWidget);
   }
+
+  Future<void> tapDatabaseRawDataButton() async {
+    await tapButtonWithName(LocaleKeys.importPanel_database.tr());
+  }
 }
 
 Finder finderForDatabaseLayoutType(DatabaseLayoutPB layout) {
@@ -457,24 +659,24 @@ Finder finderForDatabaseLayoutType(DatabaseLayoutPB layout) {
 Finder finderForFieldType(FieldType fieldType) {
   switch (fieldType) {
     case FieldType.Checkbox:
-      return find.byType(GridCheckboxCell);
+      return find.byType(GridCheckboxCell, skipOffstage: false);
     case FieldType.DateTime:
-      return find.byType(GridDateCell);
+      return find.byType(GridDateCell, skipOffstage: false);
     case FieldType.LastEditedTime:
     case FieldType.CreatedTime:
-      return find.byType(GridDateCell);
+      return find.byType(GridDateCell, skipOffstage: false);
     case FieldType.SingleSelect:
-      return find.byType(GridSingleSelectCell);
+      return find.byType(GridSingleSelectCell, skipOffstage: false);
     case FieldType.MultiSelect:
-      return find.byType(GridMultiSelectCell);
+      return find.byType(GridMultiSelectCell, skipOffstage: false);
     case FieldType.Checklist:
-      return find.byType(GridChecklistCell);
+      return find.byType(GridChecklistCell, skipOffstage: false);
     case FieldType.Number:
-      return find.byType(GridNumberCell);
+      return find.byType(GridNumberCell, skipOffstage: false);
     case FieldType.RichText:
-      return find.byType(GridTextCell);
+      return find.byType(GridTextCell, skipOffstage: false);
     case FieldType.URL:
-      return find.byType(GridURLCell);
+      return find.byType(GridURLCell, skipOffstage: false);
     default:
       throw Exception('Unknown field type: $fieldType');
   }

+ 4 - 1
frontend/appflowy_flutter/lib/main.dart

@@ -6,5 +6,8 @@ import 'startup/startup.dart';
 Future<void> main() async {
   WidgetsFlutterBinding.ensureInitialized();
 
-  await FlowyRunner.run(FlowyApp());
+  await FlowyRunner.run(
+    FlowyApp(),
+    integrationEnv(),
+  );
 }

+ 3 - 3
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart

@@ -109,7 +109,7 @@ class _CheckboxFilterEditorState extends State<CheckboxFilterEditor> {
         children: [
           FlowyText(state.filterInfo.fieldInfo.name),
           const HSpace(4),
-          CheckboxFilterConditionPBList(
+          CheckboxFilterConditionList(
             filterInfo: state.filterInfo,
             popoverMutex: popoverMutex,
             onCondition: (condition) {
@@ -137,11 +137,11 @@ class _CheckboxFilterEditorState extends State<CheckboxFilterEditor> {
   }
 }
 
-class CheckboxFilterConditionPBList extends StatelessWidget {
+class CheckboxFilterConditionList extends StatelessWidget {
   final FilterInfo filterInfo;
   final PopoverMutex popoverMutex;
   final Function(CheckboxFilterConditionPB) onCondition;
-  const CheckboxFilterConditionPBList({
+  const CheckboxFilterConditionList({
     required this.filterInfo,
     required this.popoverMutex,
     required this.onCondition,

+ 3 - 3
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart

@@ -60,7 +60,7 @@ class _GridCreateFilterListState extends State<GridCreateFilterList> {
             final cells = state.creatableFields.map((fieldInfo) {
               return SizedBox(
                 height: GridSize.popoverItemHeight,
-                child: _FilterPropertyCell(
+                child: GridFilterPropertyCell(
                   fieldInfo: fieldInfo,
                   onTap: (fieldInfo) => createFilter(fieldInfo),
                 ),
@@ -147,10 +147,10 @@ class _FilterTextFieldDelegate extends SliverPersistentHeaderDelegate {
   }
 }
 
-class _FilterPropertyCell extends StatelessWidget {
+class GridFilterPropertyCell extends StatelessWidget {
   final FieldInfo fieldInfo;
   final Function(FieldInfo) onTap;
-  const _FilterPropertyCell({
+  const GridFilterPropertyCell({
     required this.fieldInfo,
     required this.onTap,
     Key? key,

+ 2 - 0
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart

@@ -42,6 +42,7 @@ class GridCellBuilder {
           cellControllerBuilder: cellControllerBuilder,
           key: key,
           style: style,
+          fieldType: cellContext.fieldType,
         );
       case FieldType.LastEditedTime:
       case FieldType.CreatedTime:
@@ -50,6 +51,7 @@ class GridCellBuilder {
           key: key,
           editable: false,
           style: style,
+          fieldType: cellContext.fieldType,
         );
       case FieldType.SingleSelect:
         return GridSingleSelectCell(

+ 36 - 11
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart

@@ -1,4 +1,5 @@
 import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
+import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
@@ -22,11 +23,17 @@ abstract class GridCellDelegate {
 
 class GridDateCell extends GridCellWidget {
   final bool editable;
+
+  /// The [GridDateCell] is used by Field Type [FieldType.DateTime],
+  /// [FieldType.CreatedTime], [FieldType.LastEditedTime]. So it needs
+  /// to know the field type.
+  final FieldType fieldType;
   final CellControllerBuilder cellControllerBuilder;
   late final DateCellStyle? cellStyle;
 
   GridDateCell({
     GridCellStyle? style,
+    required this.fieldType,
     required this.cellControllerBuilder,
     this.editable = true,
     Key? key,
@@ -65,17 +72,9 @@ class _DateCellState extends GridCellState<GridDateCell> {
       value: _cellBloc,
       child: BlocBuilder<DateCellBloc, DateCellState>(
         builder: (context, state) {
-          Widget dateTextWidget = SizedBox.expand(
-            child: Align(
-              alignment: alignment,
-              child: Padding(
-                padding: GridSize.cellContentInsets,
-                child: FlowyText.medium(
-                  state.dateStr,
-                  overflow: TextOverflow.ellipsis,
-                ),
-              ),
-            ),
+          Widget dateTextWidget = GridDateCellText(
+            dateStr: state.dateStr,
+            alignment: alignment,
           );
 
           // If the cell is editable, wrap it in a popover.
@@ -123,3 +122,29 @@ class _DateCellState extends GridCellState<GridDateCell> {
   @override
   String? onCopy() => _cellBloc.state.dateStr;
 }
+
+class GridDateCellText extends StatelessWidget {
+  final String dateStr;
+  final Alignment alignment;
+  const GridDateCellText({
+    required this.dateStr,
+    required this.alignment,
+    super.key,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox.expand(
+      child: Align(
+        alignment: alignment,
+        child: Padding(
+          padding: GridSize.cellContentInsets,
+          child: FlowyText.medium(
+            dateStr,
+            overflow: TextOverflow.ellipsis,
+          ),
+        ),
+      ),
+    );
+  }
+}

+ 9 - 4
frontend/appflowy_flutter/lib/startup/startup.dart

@@ -20,7 +20,8 @@ abstract class EntryPoint {
 
 class FlowyRunner {
   static Future<void> run(
-    EntryPoint f, {
+    EntryPoint f,
+    IntegrationMode mode, {
     LaunchConfiguration config = const LaunchConfiguration(
       autoRegistrationSupported: false,
     ),
@@ -29,8 +30,7 @@ class FlowyRunner {
     await getIt.reset();
 
     // Specify the env
-    final env = integrationEnv();
-    initGetIt(getIt, env, f, config);
+    initGetIt(getIt, mode, f, config);
 
     final directory = await getIt<LocalFileStorage>()
         .getPath()
@@ -55,7 +55,7 @@ class FlowyRunner {
 
         // init the app widget
         // ignore in test mode
-        if (!env.isTest()) ...[
+        if (!mode.isTest()) ...[
           const HotKeyTask(),
           InitSupabaseTask(
             url: Env.supabaseUrl,
@@ -146,12 +146,17 @@ enum IntegrationMode {
   develop,
   release,
   test,
+  integrationTest,
 }
 
 extension IntegrationEnvExt on IntegrationMode {
   bool isTest() {
     return this == IntegrationMode.test;
   }
+
+  bool isIntegrationTest() {
+    return this == IntegrationMode.integrationTest;
+  }
 }
 
 IntegrationMode integrationEnv() {

+ 1 - 0
frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart

@@ -61,6 +61,7 @@ Future<Directory> appFlowyDocumentDirectory() async {
       final Directory documentsDir = await getApplicationSupportDirectory();
       return Directory(path.join(documentsDir.path, 'data')).create();
     case IntegrationMode.test:
+    case IntegrationMode.integrationTest:
       return Directory(path.join(Directory.current.path, '.sandbox'));
   }
 }

+ 4 - 1
frontend/appflowy_flutter/lib/startup/tasks/windows.dart

@@ -26,7 +26,10 @@ class InitAppWindowTask extends LaunchTask with WindowListener {
     await windowManager.ensureInitialized();
     windowManager.addListener(this);
 
-    final windowSize = await WindowSizeManager().getSize();
+    Size windowSize = await WindowSizeManager().getSize();
+    if (context.env.isIntegrationTest()) {
+      windowSize = const Size(1600, 1200);
+    }
 
     final windowOptions = WindowOptions(
       size: windowSize,

+ 1 - 0
frontend/appflowy_flutter/lib/user/presentation/skip_log_in_screen.dart

@@ -109,6 +109,7 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
   Future<void> _relaunchAppAndAutoRegister() async {
     await FlowyRunner.run(
       FlowyApp(),
+      integrationEnv(),
       config: const LaunchConfiguration(
         autoRegistrationSupported: true,
       ),

+ 3 - 1
frontend/appflowy_flutter/lib/workspace/application/settings/share/import_service.dart

@@ -26,7 +26,9 @@ extension on ImportTypePB {
     switch (this) {
       case ImportTypePB.HistoryDocument:
         return ViewLayoutPB.Document;
-      case ImportTypePB.HistoryDatabase || ImportTypePB.CSV:
+      case ImportTypePB.HistoryDatabase ||
+            ImportTypePB.CSV ||
+            ImportTypePB.RawDatabase:
         return ViewLayoutPB.Grid;
       default:
         throw UnimplementedError('Unsupported import type $this');

+ 1 - 0
frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/add_button.dart

@@ -89,6 +89,7 @@ class AddButton extends StatelessWidget {
                 case ImportType.historyDocument:
                 case ImportType.historyDatabase:
                 case ImportType.databaseCSV:
+                case ImportType.databaseRawData:
                   onSelected(
                     action.pluginBuilder,
                     name,

+ 9 - 0
frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/import/import_panel.dart

@@ -74,6 +74,7 @@ class ImportPanel extends StatelessWidget {
         childAspectRatio: 1 / .2,
         crossAxisCount: 2,
         children: ImportType.values
+            .where((element) => element.enableOnRelease)
             .map(
               (e) => Card(
                 child: FlowyButton(
@@ -137,6 +138,14 @@ class ImportPanel extends StatelessWidget {
             ImportTypePB.HistoryDatabase,
           );
           break;
+        case ImportType.databaseRawData:
+          await ImportBackendService.importData(
+            utf8.encode(data),
+            name,
+            parentViewId,
+            ImportTypePB.RawDatabase,
+          );
+          break;
         case ImportType.databaseCSV:
           await ImportBackendService.importData(
             utf8.encode(data),

+ 18 - 1
frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/import/import_type.dart

@@ -1,13 +1,15 @@
 import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
+import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 
 enum ImportType {
   historyDocument,
   historyDatabase,
   markdownOrText,
-  databaseCSV;
+  databaseCSV,
+  databaseRawData;
 
   @override
   String toString() {
@@ -20,6 +22,8 @@ enum ImportType {
         return LocaleKeys.importPanel_textAndMarkdown.tr();
       case ImportType.databaseCSV:
         return LocaleKeys.importPanel_csv.tr();
+      case ImportType.databaseRawData:
+        return LocaleKeys.importPanel_database.tr();
     }
   }
 
@@ -32,6 +36,8 @@ enum ImportType {
             name = 'editor/documents';
           case ImportType.databaseCSV:
             name = 'editor/board';
+          case ImportType.databaseRawData:
+            name = 'editor/board';
           case ImportType.markdownOrText:
             name = 'editor/text';
         }
@@ -40,11 +46,21 @@ enum ImportType {
         );
       };
 
+  bool get enableOnRelease {
+    switch (this) {
+      case ImportType.databaseRawData:
+        return kDebugMode;
+      default:
+        return true;
+    }
+  }
+
   List<String> get allowedExtensions {
     switch (this) {
       case ImportType.historyDocument:
         return ['afdoc'];
       case ImportType.historyDatabase:
+      case ImportType.databaseRawData:
         return ['afdb'];
       case ImportType.markdownOrText:
         return ['md', 'txt'];
@@ -57,6 +73,7 @@ enum ImportType {
     switch (this) {
       case ImportType.historyDocument:
       case ImportType.databaseCSV:
+      case ImportType.databaseRawData:
       case ImportType.historyDatabase:
       case ImportType.markdownOrText:
         return true;

+ 2 - 0
frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart

@@ -177,6 +177,7 @@ class _ChangeStoragePathButtonState extends State<_ChangeStoragePathButton> {
           await context.read<SettingsLocationCubit>().setPath(path);
           await FlowyRunner.run(
             FlowyApp(),
+            integrationEnv(),
             config: const LaunchConfiguration(
               autoRegistrationSupported: true,
             ),
@@ -249,6 +250,7 @@ class _RecoverDefaultStorageButtonState
         await context.read<SettingsLocationCubit>().setPath(path);
         await FlowyRunner.run(
           FlowyApp(),
+          integrationEnv(),
           config: const LaunchConfiguration(
             autoRegistrationSupported: true,
           ),

+ 2 - 2
frontend/appflowy_flutter/pubspec.lock

@@ -1473,10 +1473,10 @@ packages:
     dependency: transitive
     description:
       name: tuple
-      sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa"
+      sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
       url: "https://pub.dev"
     source: hosted
-    version: "2.0.1"
+    version: "2.0.2"
   typed_data:
     dependency: transitive
     description:

+ 1 - 0
frontend/appflowy_flutter/pubspec.yaml

@@ -182,4 +182,5 @@ flutter:
     - assets/test/workspaces/
     - assets/template/
     - assets/test/workspaces/markdowns/
+    - assets/test/workspaces/database/
     # END: EXCLUDE_IN_RELEASE

+ 4 - 1
frontend/appflowy_flutter/test/util.dart

@@ -34,7 +34,10 @@ class AppFlowyUnitTest {
     SharedPreferences.setMockInitialValues({});
     _pathProviderInitialized();
 
-    await FlowyRunner.run(FlowyTestApp());
+    await FlowyRunner.run(
+      FlowyTestApp(),
+      IntegrationMode.test,
+    );
 
     final test = AppFlowyUnitTest();
     await test._signIn();

+ 1 - 0
frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs

@@ -325,6 +325,7 @@ impl FolderOperationHandler for DatabaseFolderOperation {
     let format = match import_type {
       ImportType::CSV => CSVFormat::Original,
       ImportType::HistoryDatabase => CSVFormat::META,
+      ImportType::RawDatabase => CSVFormat::META,
       _ => CSVFormat::Original,
     };
     FutureResult::new(async move {

+ 1 - 1
frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs

@@ -36,7 +36,7 @@ impl ChecklistCellData {
     if total_options == 0 {
       return 0.0;
     }
-    (selected_options as f64) / (total_options as f64)
+    ((selected_options as f64) / (total_options as f64) * 10.0).trunc() / 10.0
   }
 
   pub fn from_options(options: Vec<String>) -> Self {

+ 3 - 1
frontend/rust-lib/flowy-folder2/src/entities/import.rs

@@ -9,7 +9,8 @@ use flowy_error::FlowyError;
 pub enum ImportTypePB {
   HistoryDocument = 0,
   HistoryDatabase = 1,
-  CSV = 2,
+  RawDatabase = 2,
+  CSV = 3,
 }
 
 impl From<ImportTypePB> for ImportType {
@@ -17,6 +18,7 @@ impl From<ImportTypePB> for ImportType {
     match pb {
       ImportTypePB::HistoryDocument => ImportType::HistoryDocument,
       ImportTypePB::HistoryDatabase => ImportType::HistoryDatabase,
+      ImportTypePB::RawDatabase => ImportType::RawDatabase,
       ImportTypePB::CSV => ImportType::CSV,
     }
   }

+ 2 - 1
frontend/rust-lib/flowy-folder2/src/share/import.rs

@@ -4,7 +4,8 @@ use collab_folder::core::ViewLayout;
 pub enum ImportType {
   HistoryDocument = 0,
   HistoryDatabase = 1,
-  CSV = 2,
+  RawDatabase = 2,
+  CSV = 3,
 }
 
 #[derive(Clone, Debug)]

+ 1 - 1
frontend/rust-lib/flowy-test/tests/database/test.rs

@@ -653,7 +653,7 @@ async fn update_checklist_cell_test() {
 
   assert_eq!(cell.options.len(), 3);
   assert_eq!(cell.selected_options.len(), 2);
-  assert_eq!(cell.percentage, 0.6666666666666666);
+  assert_eq!(cell.percentage, 0.6);
 }
 
 // The number of groups should be 0 if there is no group by field in grid

+ 5 - 4
frontend/scripts/docker-buildfiles/Dockerfile

@@ -32,8 +32,8 @@ RUN yay -S --noconfirm curl base-devel openssl clang cmake ninja pkg-config xdg-
 RUN xdg-user-dirs-update
 RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
 RUN source ~/.cargo/env && \
-    rustup toolchain install stable && \
-    rustup default stable
+    rustup toolchain install 1.70 && \
+    rustup default 1.70
 
 # Install Flutter
 RUN sudo pacman -S --noconfirm git tar gtk3
@@ -44,10 +44,10 @@ RUN curl -sSfL \
     rm flutter.tar.xz
 RUN flutter config --enable-linux-desktop
 RUN flutter doctor
-RUN dart pub global activate protoc_plugin
+RUN dart pub global activate protoc_plugin 20.0.1
 
 # Intall build dependencies for AppFlowy
-RUN sudo pacman -S --noconfirm git libkeybinder3 sqlite
+RUN sudo pacman -S --noconfirm git libkeybinder3 sqlite clang
 RUN source ~/.cargo/env && cargo install --force cargo-make duckscript_cli
 
 # Build AppFlowy
@@ -57,6 +57,7 @@ WORKDIR /appflowy
 RUN cd frontend && \
     source ~/.cargo/env && \
     cargo make appflowy-flutter-deps-tools && \
+    cargo make flutter_clean && \
     cargo make -p production-linux-x86_64 appflowy-linux
 
 

Vissa filer visades inte eftersom för många filer har ändrats