diff --git a/.github/workflows/flutter_ci.yaml b/.github/workflows/flutter_ci.yaml index 0cd9086ee1..0b8a9e141a 100644 --- a/.github/workflows/flutter_ci.yaml +++ b/.github/workflows/flutter_ci.yaml @@ -124,4 +124,5 @@ jobs: name: appflowy flags: appflowy_flutter_unit_test fail_ci_if_error: true - verbose: true \ No newline at end of file + verbose: true + os: ${{ matrix.os }} diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 37d947c6ec..2171a5f4c3 100644 --- a/.github/workflows/integration_test.yml +++ b/.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 }} diff --git a/frontend/appflowy_flutter/assets/test/workspaces/database/v020.afdb b/frontend/appflowy_flutter/assets/test/workspaces/database/v020.afdb new file mode 100644 index 0000000000..04a49dead3 --- /dev/null +++ b/frontend/appflowy_flutter/assets/test/workspaces/database/v020.afdb @@ -0,0 +1,11 @@ +"{""id"":""2_OVWb"",""name"":""Name"",""field_type"":0,""visibility"":true,""width"":150,""type_options"":{""0"":{""data"":""""}},""is_primary"":true}","{""id"":""xjmOSi"",""name"":""Type"",""field_type"":3,""visibility"":true,""width"":150,""type_options"":{""3"":{""content"":""{\""options\"":[{\""id\"":\""t1WZ\"",\""name\"":\""s6\"",\""color\"":\""Lime\""},{\""id\"":\""GzNa\"",\""name\"":\""s5\"",\""color\"":\""Yellow\""},{\""id\"":\""l_8w\"",\""name\"":\""s4\"",\""color\"":\""Orange\""},{\""id\"":\""TzVT\"",\""name\"":\""s3\"",\""color\"":\""LightPink\""},{\""id\"":\""b5WF\"",\""name\"":\""s2\"",\""color\"":\""Pink\""},{\""id\"":\""AcHA\"",\""name\"":\""s1\"",\""color\"":\""Purple\""}],\""disable_color\"":false}""}},""is_primary"":false}","{""id"":""Hpbiwr"",""name"":""Done"",""field_type"":5,""visibility"":true,""width"":150,""type_options"":{""5"":{""is_selected"":false}},""is_primary"":false}","{""id"":""F7WLnw"",""name"":""checklist"",""field_type"":7,""visibility"":true,""width"":120,""type_options"":{""0"":{""data"":""""},""7"":{}},""is_primary"":false}","{""id"":""KABhMe"",""name"":""number"",""field_type"":1,""visibility"":true,""width"":120,""type_options"":{""1"":{""format"":0,""symbol"":""RUB"",""scale"":0,""name"":""Number""},""0"":{""scale"":0,""data"":"""",""format"":0,""name"":""Number"",""symbol"":""RUB""}},""is_primary"":false}","{""id"":""lEn6Bv"",""name"":""date"",""field_type"":2,""visibility"":true,""width"":120,""type_options"":{""2"":{""field_type"":2,""time_format"":1,""timezone_id"":"""",""date_format"":3},""0"":{""field_type"":2,""date_format"":3,""time_format"":1,""data"":"""",""timezone_id"":""""}},""is_primary"":false}","{""id"":""B8Prnx"",""name"":""url"",""field_type"":6,""visibility"":true,""width"":120,""type_options"":{""6"":{""content"":"""",""url"":""""},""0"":{""content"":"""",""data"":"""",""url"":""""}},""is_primary"":false}","{""id"":""MwUow4"",""name"":""multi-select"",""field_type"":4,""visibility"":true,""width"":240,""type_options"":{""0"":{""content"":""{\""options\"":[],\""disable_color\"":false}"",""data"":""""},""4"":{""content"":""{\""options\"":[{\""id\"":\""__Us\"",\""name\"":\""m7\"",\""color\"":\""Green\""},{\""id\"":\""n9-g\"",\""name\"":\""m6\"",\""color\"":\""Lime\""},{\""id\"":\""KFYu\"",\""name\"":\""m5\"",\""color\"":\""Yellow\""},{\""id\"":\""KftP\"",\""name\"":\""m4\"",\""color\"":\""Orange\""},{\""id\"":\""5lWo\"",\""name\"":\""m3\"",\""color\"":\""LightPink\""},{\""id\"":\""Djrz\"",\""name\"":\""m2\"",\""color\"":\""Pink\""},{\""id\"":\""2uRu\"",\""name\"":\""m1\"",\""color\"":\""Purple\""}],\""disable_color\"":false}""}},""is_primary"":false}" +"{""field_type"":0,""created_at"":1686793246,""data"":""A"",""last_modified"":1686793246}","{""last_modified"":1686793275,""created_at"":1686793261,""data"":""AcHA"",""field_type"":3}","{""created_at"":1686793241,""field_type"":5,""last_modified"":1686793241,""data"":""Yes""}","{""data"":""{\""options\"":[{\""id\"":\""pi1A\"",\""name\"":\""t1\"",\""color\"":\""Purple\""},{\""id\"":\""6Pym\"",\""name\"":\""t2\"",\""color\"":\""Purple\""},{\""id\"":\""erEe\"",\""name\"":\""t3\"",\""color\"":\""Purple\""}],\""selected_option_ids\"":[\""pi1A\"",\""6Pym\""]}"",""created_at"":1686793302,""field_type"":7,""last_modified"":1686793308}","{""created_at"":1686793333,""field_type"":1,""data"":""-1"",""last_modified"":1686793333}","{""last_modified"":1686793370,""field_type"":2,""data"":""1685583770"",""include_time"":false,""created_at"":1686793370}","{""created_at"":1686793395,""data"":""appflowy.io"",""field_type"":6,""last_modified"":1686793399,""url"":""https://appflowy.io""}","{""last_modified"":1686793446,""field_type"":4,""data"":""2uRu"",""created_at"":1686793428}" +"{""last_modified"":1686793247,""data"":""B"",""field_type"":0,""created_at"":1686793247}","{""created_at"":1686793278,""data"":""b5WF"",""field_type"":3,""last_modified"":1686793278}","{""created_at"":1686793292,""last_modified"":1686793292,""data"":""Yes"",""field_type"":5}","{""data"":""{\""options\"":[{\""id\"":\""YHDO\"",\""name\"":\""t1\"",\""color\"":\""Purple\""},{\""id\"":\""QjtW\"",\""name\"":\""t2\"",\""color\"":\""Purple\""},{\""id\"":\""K2nM\"",\""name\"":\""t3\"",\""color\"":\""Purple\""}],\""selected_option_ids\"":[\""YHDO\""]}"",""field_type"":7,""last_modified"":1686793318,""created_at"":1686793311}","{""data"":""-2"",""last_modified"":1686793335,""created_at"":1686793335,""field_type"":1}","{""field_type"":2,""data"":""1685670174"",""include_time"":false,""created_at"":1686793374,""last_modified"":1686793374}","{""last_modified"":1686793403,""field_type"":6,""created_at"":1686793399,""url"":"""",""data"":""no url""}","{""data"":""2uRu,Djrz"",""field_type"":4,""last_modified"":1686793449,""created_at"":1686793449}" +"{""data"":""C"",""created_at"":1686793248,""last_modified"":1686793248,""field_type"":0}","{""created_at"":1686793280,""field_type"":3,""data"":""TzVT"",""last_modified"":1686793280}","{""data"":""Yes"",""last_modified"":1686793292,""field_type"":5,""created_at"":1686793292}","{""last_modified"":1686793329,""field_type"":7,""created_at"":1686793322,""data"":""{\""options\"":[{\""id\"":\""iWM1\"",\""name\"":\""t1\"",\""color\"":\""Purple\""},{\""id\"":\""WDvF\"",\""name\"":\""t2\"",\""color\"":\""Purple\""},{\""id\"":\""w3k7\"",\""name\"":\""t3\"",\""color\"":\""Purple\""}],\""selected_option_ids\"":[\""iWM1\"",\""WDvF\"",\""w3k7\""]}""}","{""field_type"":1,""last_modified"":1686793339,""data"":""0.1"",""created_at"":1686793339}","{""last_modified"":1686793377,""data"":""1685756577"",""created_at"":1686793377,""include_time"":false,""field_type"":2}","{""created_at"":1686793403,""field_type"":6,""data"":""appflowy.io"",""last_modified"":1686793408,""url"":""https://appflowy.io""}","{""data"":""2uRu,Djrz,5lWo"",""created_at"":1686793453,""last_modified"":1686793454,""field_type"":4}" +"{""data"":""D"",""last_modified"":1686793249,""created_at"":1686793249,""field_type"":0}","{""data"":""l_8w"",""created_at"":1686793284,""last_modified"":1686793284,""field_type"":3}","{""data"":""Yes"",""created_at"":1686793293,""last_modified"":1686793293,""field_type"":5}",,"{""field_type"":1,""last_modified"":1686793341,""created_at"":1686793341,""data"":""0.2""}","{""created_at"":1686793379,""last_modified"":1686793379,""field_type"":2,""data"":""1685842979"",""include_time"":false}","{""last_modified"":1686793419,""field_type"":6,""created_at"":1686793408,""data"":""https://github.com/AppFlowy-IO/"",""url"":""https://github.com/AppFlowy-IO/""}","{""data"":""2uRu,Djrz,5lWo"",""last_modified"":1686793459,""field_type"":4,""created_at"":1686793459}" +"{""field_type"":0,""last_modified"":1686793250,""created_at"":1686793250,""data"":""E""}","{""field_type"":3,""last_modified"":1686793290,""created_at"":1686793290,""data"":""GzNa""}","{""last_modified"":1686793294,""created_at"":1686793294,""data"":""Yes"",""field_type"":5}",,"{""created_at"":1686793346,""field_type"":1,""last_modified"":1686793346,""data"":""1""}","{""last_modified"":1686793383,""data"":""1685929383"",""field_type"":2,""include_time"":false,""created_at"":1686793383}","{""field_type"":6,""url"":"""",""data"":"""",""last_modified"":1686793421,""created_at"":1686793419}","{""field_type"":4,""last_modified"":1686793465,""data"":""2uRu,Djrz,5lWo,KFYu,KftP"",""created_at"":1686793463}" +"{""field_type"":0,""created_at"":1686793251,""data"":"""",""last_modified"":1686793289}",,,,"{""data"":""2"",""field_type"":1,""created_at"":1686793347,""last_modified"":1686793347}","{""include_time"":false,""data"":""1685929385"",""last_modified"":1686793385,""field_type"":2,""created_at"":1686793385}",, +"{""created_at"":1686793254,""field_type"":0,""last_modified"":1686793288,""data"":""""}",,,,"{""created_at"":1686793351,""last_modified"":1686793351,""data"":""10"",""field_type"":1}","{""include_time"":false,""data"":""1686879792"",""field_type"":2,""created_at"":1686793392,""last_modified"":1686793392}",, +,,,,"{""last_modified"":1686793354,""created_at"":1686793354,""field_type"":1,""data"":""11""}",,, +,,,,"{""field_type"":1,""last_modified"":1686793356,""data"":""12"",""created_at"":1686793356}",,, +,,,,,,, diff --git a/frontend/appflowy_flutter/assets/translations/en.json b/frontend/appflowy_flutter/assets/translations/en.json index e408557310..78d6ab0090 100644 --- a/frontend/appflowy_flutter/assets/translations/en.json +++ b/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", diff --git a/frontend/appflowy_flutter/integration_test/database_field_test.dart b/frontend/appflowy_flutter/integration_test/database_field_test.dart index 4e9a5446ed..ec4f1f4d35 100644 --- a/frontend/appflowy_flutter/integration_test/database_field_test.dart +++ b/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'); diff --git a/frontend/appflowy_flutter/integration_test/database_filter_test.dart b/frontend/appflowy_flutter/integration_test/database_filter_test.dart new file mode 100644 index 0000000000..aaf1cfc0f3 --- /dev/null +++ b/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(); + }); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/database_row_page_test.dart b/frontend/appflowy_flutter/integration_test/database_row_page_test.dart index a424225ec7..c9bf59cbbd 100644 --- a/frontend/appflowy_flutter/integration_test/database_row_page_test.dart +++ b/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(); diff --git a/frontend/appflowy_flutter/integration_test/database_share_test.dart b/frontend/appflowy_flutter/integration_test/database_share_test.dart new file mode 100644 index 0000000000..15a3a33548 --- /dev/null +++ b/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 = ['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 = [ + 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 = [ + '-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 = [ + '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 = [ + '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> 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 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 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, + ); + } + }); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/runner.dart b/frontend/appflowy_flutter/integration_test/runner.dart index 30879ba9fe..12bb3f7163 100644 --- a/frontend/appflowy_flutter/integration_test/runner.dart +++ b/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(); diff --git a/frontend/appflowy_flutter/integration_test/util/base.dart b/frontend/appflowy_flutter/integration_test/util/base.dart index 55035968ad..62d9af9baa 100644 --- a/frontend/appflowy_flutter/integration_test/util/base.dart +++ b/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 initializeAppFlowy() async { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler( - const MethodChannel('hotkey_manager'), - (MethodCall methodCall) async { - if (methodCall.method == 'unregisterAll') { - // do nothing - } + .setMockMethodCallHandler(const MethodChannel('hotkey_manager'), + (MethodCall methodCall) async { + if (methodCall.method == 'unregisterAll') { + // do nothing + } - return; - }, - ); + return; + }); + + WidgetsFlutterBinding.ensureInitialized(); + await FlowyRunner.run(FlowyApp(), IntegrationMode.integrationTest); - await app.main(); await wait(3000); await pumpAndSettle(const Duration(seconds: 2)); } diff --git a/frontend/appflowy_flutter/integration_test/util/common_operations.dart b/frontend/appflowy_flutter/integration_test/util/common_operations.dart index 4c0d3a7b99..0ef1ecd8f6 100644 --- a/frontend/appflowy_flutter/integration_test/util/common_operations.dart +++ b/frontend/appflowy_flutter/integration_test/util/common_operations.dart @@ -140,6 +140,11 @@ extension CommonOperations on WidgetTester { } } + /// open the page with given name. + Future openPage(String name) async { + await tapButton(findPageName(name)); + } + /// Tap the ... button beside the page name. /// /// Must call [hoverOnPageName] first. diff --git a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart index 63b84fccdb..a4ad7fe628 100644 --- a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart +++ b/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 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 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(); } + 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 tapCheckboxCellInGrid({ required int rowIndex, }) 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); 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 = find.descendant( - of: findRow.at(rowIndex), - matching: findCell, - ); - + final cell = cellFinder(rowIndex, fieldType); expect(cell, findsOneWidget); - await tapButton(cell); + await tapButton(cell, warnIfMissed: false); } + /// The [fieldName] must be uqniue in the grid. Future assertCellContent({ required int rowIndex, required FieldType fieldType, required String content, }) async { - final findRow = find.byType(GridRow); - final findCell = finderForFieldType(fieldType); - final cell = find.descendant( - of: findRow.at(rowIndex), - matching: findCell, - ); - + final findCell = cellFinder(rowIndex, fieldType); final findContent = find.descendant( - of: cell, + of: findCell, matching: find.text(content), + skipOffstage: false, ); - expect(findContent, findsOneWidget); + final text = find.descendant( + of: find.byType(TextField), + matching: findContent, + skipOffstage: false, + ); + + expect(text, findsOneWidget); + } + + Future 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 assertMultiSelectOption({ + required int rowIndex, + required List 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 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 assertDateCellInGrid({ + required int rowIndex, + required FieldType fieldType, + required String content, + }) async { + final findRow = find.byType(GridRow, skipOffstage: false); + final findCell = find.descendant( + of: findRow.at(rowIndex), + matching: find.byWidgetPredicate( + (widget) => widget is GridDateCell && widget.fieldType == fieldType, + ), + skipOffstage: false, + ); + + final dateCellText = find.descendant( + of: findCell, + matching: find.byType(GridDateCellText), + ); + + 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 selectDay({ @@ -290,8 +406,13 @@ extension AppFlowyDatabaseTest on WidgetTester { /// Must call [tapTypeOptionButton] first. Future 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 assertNumberOfRowsInGridPage(int num) async { - expect(find.byType(GridRow), findsNWidgets(num)); + expect( + find.byType(GridRow, skipOffstage: false), + findsNWidgets(num), + ); } Future assertDocumentExistInRowDetailPage() async { @@ -353,9 +477,7 @@ extension AppFlowyDatabaseTest on WidgetTester { Future dismissFieldEditor() async { await sendKeyEvent(LogicalKeyboardKey.escape); - await sendKeyEvent(LogicalKeyboardKey.escape); - await sendKeyEvent(LogicalKeyboardKey.escape); - await pumpAndSettle(); + await pumpAndSettle(const Duration(milliseconds: 200)); } Future findFieldEditor(dynamic matcher) async { @@ -405,6 +527,82 @@ extension AppFlowyDatabaseTest on WidgetTester { await tapButton(find.byType(SettingButton)); } + Future tapDatabaseFilterButton() async { + await tapButton(find.byType(FilterButton)); + } + + Future 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 tapFilterButtonInGrid(String filterName) async { + final findFilter = find.byType(FilterMenuItem); + final button = find.descendant( + of: findFilter, + matching: find.text(filterName), + ); + + await tapButton(button); + } + + Future 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 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 tapDeleteTextFilterButtonInGrid() async { + await tapButton(find.text(LocaleKeys.grid_settings_deleteFilter.tr())); + } + + Future tapCheckboxFilterButtonInGrid() async { + await tapButton(find.byType(CheckboxFilterConditionList)); + } + + Future tapCheckedButtonOnCheckboxFilter() async { + final button = find.descendant( + of: find.byType(HoverButton), + matching: find.text(LocaleKeys.grid_checkboxFilter_isChecked.tr()), + ); + + await tapButton(button); + } + + Future 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 tapDatabaseLayoutButton() async { final findSettingItem = find.byType(DatabaseSettingItem); @@ -439,6 +637,10 @@ extension AppFlowyDatabaseTest on WidgetTester { Future assertCurrentDatabaseLayoutType(DatabaseLayoutPB layout) async { expect(finderForDatabaseLayoutType(layout), findsOneWidget); } + + Future 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'); } diff --git a/frontend/appflowy_flutter/lib/main.dart b/frontend/appflowy_flutter/lib/main.dart index a22d2d0581..4da98d2eae 100644 --- a/frontend/appflowy_flutter/lib/main.dart +++ b/frontend/appflowy_flutter/lib/main.dart @@ -6,5 +6,8 @@ import 'startup/startup.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - await FlowyRunner.run(FlowyApp()); + await FlowyRunner.run( + FlowyApp(), + integrationEnv(), + ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart index e28da08127..4bbb535762 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart @@ -109,7 +109,7 @@ class _CheckboxFilterEditorState extends State { 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 { } } -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, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart index e87651cd88..e9c377b5f7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart @@ -60,7 +60,7 @@ class _GridCreateFilterListState extends State { 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, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart index e29423fca2..4731c72d2f 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart +++ b/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( diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart index faede408f2..7695529e26 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart +++ b/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 { value: _cellBloc, child: BlocBuilder( 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 { @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, + ), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/startup/startup.dart b/frontend/appflowy_flutter/lib/startup/startup.dart index 6f85f962f3..1d60142864 100644 --- a/frontend/appflowy_flutter/lib/startup/startup.dart +++ b/frontend/appflowy_flutter/lib/startup/startup.dart @@ -20,7 +20,8 @@ abstract class EntryPoint { class FlowyRunner { static Future 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() .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() { diff --git a/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart b/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart index 9a0f8d55f6..4d138e1781 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart @@ -61,6 +61,7 @@ Future 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')); } } diff --git a/frontend/appflowy_flutter/lib/startup/tasks/windows.dart b/frontend/appflowy_flutter/lib/startup/tasks/windows.dart index f864eae4e2..0dec1377fd 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/windows.dart +++ b/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, diff --git a/frontend/appflowy_flutter/lib/user/presentation/skip_log_in_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/skip_log_in_screen.dart index 07a6172ad5..ee4e17ddbe 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/skip_log_in_screen.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/skip_log_in_screen.dart @@ -109,6 +109,7 @@ class _SkipLogInScreenState extends State { Future _relaunchAppAndAutoRegister() async { await FlowyRunner.run( FlowyApp(), + integrationEnv(), config: const LaunchConfiguration( autoRegistrationSupported: true, ), diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/share/import_service.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/share/import_service.dart index 9b74c45457..ffb24288f3 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/share/import_service.dart +++ b/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'); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/add_button.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/add_button.dart index cfe578f5e6..7e0055be70 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/add_button.dart +++ b/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, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/import/import_panel.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/import/import_panel.dart index 9e3a73061c..68c2ff4ede 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/import/import_panel.dart +++ b/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), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/import/import_type.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/import/import_type.dart index a51892dd0e..6463019977 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/header/import/import_type.dart +++ b/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 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; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart index df4c2c3663..c75930a97f 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart +++ b/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().setPath(path); await FlowyRunner.run( FlowyApp(), + integrationEnv(), config: const LaunchConfiguration( autoRegistrationSupported: true, ), @@ -249,6 +250,7 @@ class _RecoverDefaultStorageButtonState await context.read().setPath(path); await FlowyRunner.run( FlowyApp(), + integrationEnv(), config: const LaunchConfiguration( autoRegistrationSupported: true, ), diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 8061ceaacd..bc276887b4 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/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: diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 624670cdad..cdd6c4fcb3 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/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 diff --git a/frontend/appflowy_flutter/test/util.dart b/frontend/appflowy_flutter/test/util.dart index e93e62c27d..137613cddd 100644 --- a/frontend/appflowy_flutter/test/util.dart +++ b/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(); diff --git a/frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs b/frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs index 78c6efb568..c4839fbfcf 100644 --- a/frontend/rust-lib/flowy-core/src/deps_resolve/folder2_deps.rs +++ b/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 { diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs index e8eee9fa73..d11a25a502 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs +++ b/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) -> Self { diff --git a/frontend/rust-lib/flowy-folder2/src/entities/import.rs b/frontend/rust-lib/flowy-folder2/src/entities/import.rs index 0593ee3b65..00ff772ef8 100644 --- a/frontend/rust-lib/flowy-folder2/src/entities/import.rs +++ b/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 for ImportType { @@ -17,6 +18,7 @@ impl From for ImportType { match pb { ImportTypePB::HistoryDocument => ImportType::HistoryDocument, ImportTypePB::HistoryDatabase => ImportType::HistoryDatabase, + ImportTypePB::RawDatabase => ImportType::RawDatabase, ImportTypePB::CSV => ImportType::CSV, } } diff --git a/frontend/rust-lib/flowy-folder2/src/share/import.rs b/frontend/rust-lib/flowy-folder2/src/share/import.rs index 6e663b1960..af11f32006 100644 --- a/frontend/rust-lib/flowy-folder2/src/share/import.rs +++ b/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)] diff --git a/frontend/rust-lib/flowy-test/tests/database/test.rs b/frontend/rust-lib/flowy-test/tests/database/test.rs index 48d598604a..38c28deac0 100644 --- a/frontend/rust-lib/flowy-test/tests/database/test.rs +++ b/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 diff --git a/frontend/scripts/docker-buildfiles/Dockerfile b/frontend/scripts/docker-buildfiles/Dockerfile index 27ab9f88fe..f232c57cf6 100644 --- a/frontend/scripts/docker-buildfiles/Dockerfile +++ b/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