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
This commit is contained in:
Nathan.fooo 2023-06-15 22:43:07 +08:00 committed by GitHub
parent 430325c731
commit d96a1d8bd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 732 additions and 140 deletions

View File

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

View File

@ -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 }}

View File

@ -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}",,,
,,,,,,,

View File

@ -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",

View File

@ -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');

View File

@ -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();
});
});
}

View File

@ -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();

View File

@ -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,
);
}
});
});
}

View File

@ -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();

View File

@ -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
}
.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));
}

View File

@ -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.

View File

@ -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();
}
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 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<void> 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<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, 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<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');
}

View File

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

View File

@ -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,

View File

@ -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,

View File

@ -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(

View File

@ -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,
),
),
),
);
}
}

View File

@ -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() {

View File

@ -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'));
}
}

View File

@ -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,

View File

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

View File

@ -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');

View File

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

View File

@ -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),

View File

@ -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;

View File

@ -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,
),

View File

@ -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:

View File

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

View File

@ -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();

View File

@ -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 {

View File

@ -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 {

View File

@ -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,
}
}

View File

@ -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)]

View File

@ -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

View File

@ -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