mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
parent
430325c731
commit
d96a1d8bd4
3
.github/workflows/flutter_ci.yaml
vendored
3
.github/workflows/flutter_ci.yaml
vendored
@ -124,4 +124,5 @@ jobs:
|
||||
name: appflowy
|
||||
flags: appflowy_flutter_unit_test
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
verbose: true
|
||||
os: ${{ matrix.os }}
|
||||
|
48
.github/workflows/integration_test.yml
vendored
48
.github/workflows/integration_test.yml
vendored
@ -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 }}
|
||||
|
@ -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}",,,
|
||||
,,,,,,,
|
@ -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",
|
||||
|
@ -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');
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
@ -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();
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@ -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();
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -6,5 +6,8 @@ import 'startup/startup.dart';
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await FlowyRunner.run(FlowyApp());
|
||||
await FlowyRunner.run(
|
||||
FlowyApp(),
|
||||
integrationEnv(),
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -109,6 +109,7 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
|
||||
Future<void> _relaunchAppAndAutoRegister() async {
|
||||
await FlowyRunner.run(
|
||||
FlowyApp(),
|
||||
integrationEnv(),
|
||||
config: const LaunchConfiguration(
|
||||
autoRegistrationSupported: true,
|
||||
),
|
||||
|
@ -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');
|
||||
|
@ -89,6 +89,7 @@ class AddButton extends StatelessWidget {
|
||||
case ImportType.historyDocument:
|
||||
case ImportType.historyDatabase:
|
||||
case ImportType.databaseCSV:
|
||||
case ImportType.databaseRawData:
|
||||
onSelected(
|
||||
action.pluginBuilder,
|
||||
name,
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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:
|
||||
|
@ -182,4 +182,5 @@ flutter:
|
||||
- assets/test/workspaces/
|
||||
- assets/template/
|
||||
- assets/test/workspaces/markdowns/
|
||||
- assets/test/workspaces/database/
|
||||
# END: EXCLUDE_IN_RELEASE
|
||||
|
@ -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();
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user