fix: windows integration test (#2917)

* fix:windows integration test

* fix: load asset

* fix: windows test

* fix: test

* test: refactor the folder test

---------

Co-authored-by: vedon <vedon.fu@gmail.com>
Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
This commit is contained in:
Nathan.fooo 2023-07-02 23:37:30 +08:00 committed by GitHub
parent 2c513be305
commit f0d5f51703
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 300 additions and 564 deletions

View File

@ -33,7 +33,7 @@ jobs:
if: github.event.pull_request.draft != true if: github.event.pull_request.draft != true
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest] os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}

View File

@ -10,21 +10,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('database', () { group('database', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('update calendar layout', (tester) async { testWidgets('update calendar layout', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();

View File

@ -11,21 +11,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('grid cell', () { group('grid cell', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('edit text cell', (tester) async { testWidgets('edit text cell', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();

View File

@ -10,21 +10,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('grid page', () { group('grid page', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('rename existing field', (tester) async { testWidgets('rename existing field', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();

View File

@ -5,27 +5,11 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart'; import 'util/database_test_op.dart';
import 'util/util.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('grid', () { 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 { testWidgets('add text filter', (tester) async {
await tester.openV020database(); await tester.openV020database();

View File

@ -14,21 +14,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('grid', () { group('grid', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('row details page opens', (tester) async { testWidgets('row details page opens', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();

View File

@ -8,21 +8,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('grid', () { group('grid', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('create row of the grid', (tester) async { testWidgets('create row of the grid', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();

View File

@ -9,21 +9,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('grid', () { group('grid', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('update layout', (tester) async { testWidgets('update layout', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();

View File

@ -1,66 +1,15 @@
import 'dart:io';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart'; 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:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.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() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('database', () { 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 { testWidgets('import v0.2.0 database data', (tester) async {
await tester.initializeAppFlowy(); await tester.openV020database();
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 // check the text cell
final textCells = <String>['A', 'B', 'C', 'D', 'E', '', '', '', '', '']; final textCells = <String>['A', 'B', 'C', 'D', 'E', '', '', '', '', ''];

View File

@ -3,27 +3,11 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart'; import 'util/database_test_op.dart';
import 'util/util.dart';
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('grid', () { 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 sort', (tester) async { testWidgets('add text sort', (tester) async {
await tester.openV020database(); await tester.openV020database();
// create a filter // create a filter

View File

@ -11,21 +11,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('database', () { group('database', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('create linked view', (tester) async { testWidgets('create linked view', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();

View File

@ -9,21 +9,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('cover image', () { group('cover image', () {
const location = 'cover_image';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('document cover tests', (tester) async { testWidgets('document cover tests', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();

View File

@ -10,21 +10,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('document', () { group('document', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('create a new document when launching app in first time', testWidgets('create a new document when launching app in first time',
(tester) async { (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();

View File

@ -14,17 +14,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('database view in document', () { group('database view in document', () {
const location = 'database_view';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('insert a referenced grid', (tester) async { testWidgets('insert a referenced grid', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();

View File

@ -11,17 +11,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('inline page view in document', () { group('inline page view in document', () {
const location = 'inline_page';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('insert a inline page - grid', (tester) async { testWidgets('insert a inline page - grid', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();

View File

@ -13,17 +13,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('edit document', () { group('edit document', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('redo & undo', (tester) async { testWidgets('redo & undo', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();

View File

@ -12,23 +12,8 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('import files', () { group('import files', () {
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 multiple markdown files', (tester) async { testWidgets('import multiple markdown files', (tester) async {
await tester.initializeAppFlowy(); final context = await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();
// expect to see a readme page // expect to see a readme page
@ -38,18 +23,21 @@ void main() {
await tester.tapImportButton(); await tester.tapImportButton();
final testFileNames = ['test1.md', 'test2.md']; final testFileNames = ['test1.md', 'test2.md'];
final fileLocation = await tester.currentFileLocation(); final paths = <String>[];
for (final fileName in testFileNames) { for (final fileName in testFileNames) {
final str = await rootBundle.loadString( final str = await rootBundle.loadString(
p.join( 'assets/test/workspaces/markdowns/$fileName',
'assets/test/workspaces/markdowns',
fileName,
),
); );
File(p.join(fileLocation, fileName)).writeAsStringSync(str); final path = p.join(context.applicationDataDirectory, fileName);
paths.add(path);
File(path).writeAsStringSync(str);
} }
// mock get files // mock get files
await mockPickFilePaths(testFileNames, name: location); await mockPickFilePaths(
paths: testFileNames
.map((e) => p.join(context.applicationDataDirectory, e))
.toList(),
);
await tester.tapTextAndMarkdownButton(); await tester.tapTextAndMarkdownButton();

View File

@ -8,16 +8,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('document', () { group('document', () {
const location = 'appflowy';
setUpAll(() async {
await TestFolder.setTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets( testWidgets(
'change the language successfully when launching the app for the first time', 'change the language successfully when launching the app for the first time',
(tester) async { (tester) async {

View File

@ -12,17 +12,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('outline block test', () { group('outline block test', () {
const location = 'outline_test';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('insert an outline block', (tester) async { testWidgets('insert an outline block', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();

View File

@ -3,7 +3,7 @@ import 'dart:io';
import 'package:appflowy/plugins/document/presentation/share/share_button.dart'; import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import 'util/mock/mock_file_picker.dart'; import 'util/mock/mock_file_picker.dart';
import 'util/util.dart'; import 'util/util.dart';
@ -11,30 +11,17 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('share markdown in document page', () { group('share markdown in document page', () {
const location = 'markdown';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('click the share button in document page', (tester) async { testWidgets('click the share button in document page', (tester) async {
await tester.initializeAppFlowy(); final context = await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();
// expect to see a readme page // expect to see a readme page
tester.expectToSeePageName(readme); tester.expectToSeePageName(readme);
// mock the file picker // mock the file picker
final path = await mockSaveFilePath(location, 'test.md'); final path = await mockSaveFilePath(
p.join(context.applicationDataDirectory, 'test.md'),
);
// click the share button and select markdown // click the share button and select markdown
await tester.tapShareButton(); await tester.tapShareButton();
await tester.tapMarkdownButton(); await tester.tapMarkdownButton();
@ -52,7 +39,7 @@ void main() {
testWidgets( testWidgets(
'share the markdown after renaming the document name', 'share the markdown after renaming the document name',
(tester) async { (tester) async {
await tester.initializeAppFlowy(); final context = await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();
// expect to see a readme page // expect to see a readme page
@ -65,8 +52,12 @@ void main() {
final shareButton = find.byType(ShareActionList); final shareButton = find.byType(ShareActionList);
final shareButtonState = final shareButtonState =
tester.state(shareButton) as ShareActionListState; tester.state(shareButton) as ShareActionListState;
final path = final path = await mockSaveFilePath(
await mockSaveFilePath(location, '${shareButtonState.name}.md'); p.join(
context.applicationDataDirectory,
'${shareButtonState.name}.md',
),
);
// click the share button and select markdown // click the share button and select markdown
await tester.tapShareButton(); await tester.tapShareButton();

View File

@ -1,48 +1,36 @@
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/startup/tasks/prelude.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import 'util/mock/mock_file_picker.dart'; import 'util/mock/mock_file_picker.dart';
import 'util/util.dart'; import 'util/util.dart';
import 'package:path/path.dart' as p;
void main() { void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized(); IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('customize the folder path', () { group('customize the folder path', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('switch to B from A, then switch to A again', (tester) async { testWidgets('switch to B from A, then switch to A again', (tester) async {
final userA = uuid(); const userA = 'UserA';
final userB = uuid(); const userB = 'UserB';
await TestFolder.cleanTestLocation(userA); final initialPath = p.join(userA, appFlowyDataFolder);
await TestFolder.cleanTestLocation(userB); final context = await tester.initializeAppFlowy(
await TestFolder.setTestLocation(p.join(userA, appFlowyDataFolder)); pathExtension: initialPath,
);
await tester.initializeAppFlowy(); // remove the last extension
final rootPath = context.applicationDataDirectory.replaceFirst(
initialPath,
'',
);
await tester.tapGoButton(); await tester.tapGoButton();
tester.expectToSeeHomePage(); tester.expectToSeeHomePage();
// switch to user B // switch to user B
{ {
// set user name to userA // set user name for userA
await tester.openSettings(); await tester.openSettings();
await tester.openSettingsPage(SettingsPage.user); await tester.openSettingsPage(SettingsPage.user);
await tester.enterUserName(userA); await tester.enterUserName(userA);
@ -51,12 +39,14 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// mock the file_picker result // mock the file_picker result
await mockGetDirectoryPath(userB); await mockGetDirectoryPath(
p.join(rootPath, userB),
);
await tester.tapCustomLocationButton(); await tester.tapCustomLocationButton();
await tester.pumpAndSettle(); await tester.pumpAndSettle();
tester.expectToSeeHomePage(); tester.expectToSeeHomePage();
// set user name to userB // set user name for userB
await tester.openSettings(); await tester.openSettings();
await tester.openSettingsPage(SettingsPage.user); await tester.openSettingsPage(SettingsPage.user);
await tester.enterUserName(userB); await tester.enterUserName(userB);
@ -68,7 +58,9 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// mock the file_picker result // mock the file_picker result
await mockGetDirectoryPath(userA); await mockGetDirectoryPath(
p.join(rootPath, userA),
);
await tester.tapCustomLocationButton(); await tester.tapCustomLocationButton();
await tester.pumpAndSettle(); await tester.pumpAndSettle();
@ -83,16 +75,15 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// mock the file_picker result // mock the file_picker result
await mockGetDirectoryPath(userB); await mockGetDirectoryPath(
p.join(rootPath, userB),
);
await tester.tapCustomLocationButton(); await tester.tapCustomLocationButton();
await tester.pumpAndSettle(); await tester.pumpAndSettle();
tester.expectToSeeHomePage(); tester.expectToSeeHomePage();
tester.expectToSeeUserName(userB); tester.expectToSeeUserName(userB);
} }
await TestFolder.cleanTestLocation(userA);
await TestFolder.cleanTestLocation(userB);
}); });
testWidgets('reset to default location', (tester) async { testWidgets('reset to default location', (tester) async {
@ -109,8 +100,8 @@ void main() {
await tester.restoreLocation(); await tester.restoreLocation();
expect( expect(
await TestFolder.defaultDevelopmentLocation(), await appFlowyApplicationDataDirectory().then((value) => value.path),
await TestFolder.currentLocation(), await getIt<ApplicationDataStorage>().getPath(),
); );
}); });
}); });

View File

@ -1,81 +1,77 @@
import 'dart:io'; import 'dart:io';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/startup/entry_point.dart'; import 'package:appflowy/startup/entry_point.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/startup/tasks/prelude.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class TestFolder { class FlowyTestContext {
/// Location / Path FlowyTestContext({
required this.applicationDataDirectory,
});
/// Set a given AppFlowy data storage location under test environment. final String applicationDataDirectory;
///
/// To pass null means clear the location.
///
/// The file_picker is a system component and can't be tapped, so using logic instead of tapping.
///
static Future<void> setTestLocation(String? name) async {
final location = await testLocation(name);
SharedPreferences.setMockInitialValues({
KVKeys.pathLocation: location.path,
});
return;
}
/// Clean the location.
static Future<void> cleanTestLocation(String? name) async {
final dir = await testLocation(name);
await dir.delete(recursive: true);
return;
}
/// Get current using location.
static Future<String> currentLocation() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(KVKeys.pathLocation)!;
}
/// Get default location under development environment.
static Future<String> defaultDevelopmentLocation() async {
final dir = await appFlowyApplicationDataDirectory();
return dir.path;
}
/// Get default location under test environment.
static Future<Directory> testLocation(String? name) async {
final dir = await getApplicationDocumentsDirectory();
var path = '${dir.path}/flowy_test';
if (name != null) {
path += '/$name';
}
return Directory(path).create(recursive: true);
}
} }
extension AppFlowyTestBase on WidgetTester { extension AppFlowyTestBase on WidgetTester {
Future<void> initializeAppFlowy() async { Future<FlowyTestContext> initializeAppFlowy({
// use to append after the application data directory
String? pathExtension,
}) async {
mockHotKeyManagerHandlers();
final directory = await mockApplicationDataStorage(
pathExtension: pathExtension,
);
WidgetsFlutterBinding.ensureInitialized();
await FlowyRunner.run(
FlowyApp(),
IntegrationMode.integrationTest,
);
await wait(3000);
await pumpAndSettle(const Duration(seconds: 2));
return FlowyTestContext(
applicationDataDirectory: directory,
);
}
void mockHotKeyManagerHandlers() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(const MethodChannel('hotkey_manager'), .setMockMethodCallHandler(const MethodChannel('hotkey_manager'),
(MethodCall methodCall) async { (MethodCall methodCall) async {
if (methodCall.method == 'unregisterAll') { if (methodCall.method == 'unregisterAll') {
// do nothing // do nothing
} }
return; return;
}); });
}
WidgetsFlutterBinding.ensureInitialized(); Future<String> mockApplicationDataStorage({
await FlowyRunner.run(FlowyApp(), IntegrationMode.integrationTest); // use to append after the application data directory
String? pathExtension,
}) async {
final dir = await getTemporaryDirectory();
await wait(3000); // Use a random uuid to avoid conflict.
await pumpAndSettle(const Duration(seconds: 2)); String path = '${dir.path}/appflowy_integration_test/${uuid()}';
if (pathExtension != null && pathExtension.isNotEmpty) {
path = '$path/$pathExtension';
}
final directory = Directory(path);
if (!directory.existsSync()) {
await directory.create(recursive: true);
}
MockApplicationDataStorage.initialPath = directory.path;
return directory.path;
} }
Future<void> tapButton( Future<void> tapButton(

View File

@ -17,11 +17,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'util.dart'; import 'util.dart';
extension CommonOperations on WidgetTester { extension CommonOperations on WidgetTester {
/// Get current file location of AppFlowy.
Future<String> currentFileLocation() async {
return TestFolder.currentLocation();
}
/// Tap the GetStart button on the launch page. /// Tap the GetStart button on the launch page.
Future<void> tapGoButton() async { Future<void> tapGoButton() async {
final goButton = find.byType(GoButton); final goButton = find.byType(GoButton);

View File

@ -73,7 +73,7 @@ import 'mock/mock_file_picker.dart';
extension AppFlowyDatabaseTest on WidgetTester { extension AppFlowyDatabaseTest on WidgetTester {
Future<void> openV020database() async { Future<void> openV020database() async {
await initializeAppFlowy(); final context = await initializeAppFlowy();
await tapGoButton(); await tapGoButton();
// expect to see a readme page // expect to see a readme page
@ -83,18 +83,25 @@ extension AppFlowyDatabaseTest on WidgetTester {
await tapImportButton(); await tapImportButton();
final testFileNames = ['v020.afdb']; final testFileNames = ['v020.afdb'];
final fileLocation = await currentFileLocation(); final paths = <String>[];
for (final fileName in testFileNames) { for (final fileName in testFileNames) {
final str = await rootBundle.loadString( // Don't use the p.join to build the path that used in loadString. It
p.join( // is not working on windows.
'assets/test/workspaces/database', final str = await rootBundle
fileName, .loadString("assets/test/workspaces/database/$fileName");
),
// Write the content to the file.
final path = p.join(
context.applicationDataDirectory,
fileName,
); );
File(p.join(fileLocation, fileName)).writeAsStringSync(str); paths.add(path);
File(path).writeAsStringSync(str);
} }
// mock get files // mock get files
await mockPickFilePaths(testFileNames, name: 'import_files'); await mockPickFilePaths(
paths: paths,
);
await tapDatabaseRawDataButton(); await tapDatabaseRawDataButton();
await openPage('v020'); await openPage('v020');
} }

View File

@ -1,10 +1,6 @@
import 'dart:io';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/file_picker/file_picker_service.dart'; import 'package:appflowy/util/file_picker/file_picker_service.dart';
import 'package:file_picker/file_picker.dart' as fp; import 'package:file_picker/file_picker.dart' as fp;
import 'package:path/path.dart' as p;
import '../util.dart';
class MockFilePicker implements FilePickerService { class MockFilePicker implements FilePickerService {
MockFilePicker({ MockFilePicker({
@ -56,20 +52,21 @@ class MockFilePicker implements FilePickerService {
} }
} }
Future<void> mockGetDirectoryPath(String? name) async { Future<void> mockGetDirectoryPath(
final dir = await TestFolder.testLocation(name); String path,
) async {
getIt.unregister<FilePickerService>(); getIt.unregister<FilePickerService>();
getIt.registerFactory<FilePickerService>( getIt.registerFactory<FilePickerService>(
() => MockFilePicker( () => MockFilePicker(
mockPath: dir.path, mockPath: path,
), ),
); );
return; return;
} }
Future<String> mockSaveFilePath(String? name, String fileName) async { Future<String> mockSaveFilePath(
final dir = await TestFolder.testLocation(name); String path,
final path = p.join(dir.path, fileName); ) async {
getIt.unregister<FilePickerService>(); getIt.unregister<FilePickerService>();
getIt.registerFactory<FilePickerService>( getIt.registerFactory<FilePickerService>(
() => MockFilePicker( () => MockFilePicker(
@ -79,18 +76,16 @@ Future<String> mockSaveFilePath(String? name, String fileName) async {
return path; return path;
} }
Future<List<String>> mockPickFilePaths( Future<List<String>> mockPickFilePaths({
List<String> fileNames, { required List<String> paths,
String? name,
String? customPath,
}) async { }) async {
late final Directory dir; // late final Directory dir;
if (customPath != null) { // if (customPath != null) {
dir = Directory(customPath); // dir = Directory(customPath);
} else { // } else {
dir = await TestFolder.testLocation(name); // dir = await TestFolder.testLocation(applicationDataPath, name);
} // }
final paths = fileNames.map((e) => p.join(dir.path, e)).toList(); // final paths = fileNames.map((e) => p.join(dir.path, e)).toList();
getIt.unregister<FilePickerService>(); getIt.unregister<FilePickerService>();
getIt.registerFactory<FilePickerService>( getIt.registerFactory<FilePickerService>(
() => MockFilePicker( () => MockFilePicker(

View File

@ -2,7 +2,7 @@ import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/file_picker/file_picker_service.dart'; import 'package:appflowy/util/file_picker/file_picker_service.dart';
import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:file_picker/file_picker.dart' as fp; import 'package:file_picker/file_picker.dart' as fp;

View File

@ -6,6 +6,7 @@ import 'package:appflowy/plugins/database_view/application/field/field_service.d
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart'; import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/auth/supabase_auth_service.dart'; import 'package:appflowy/user/application/auth/supabase_auth_service.dart';
import 'package:appflowy/user/application/user_listener.dart'; import 'package:appflowy/user/application/user_listener.dart';
@ -13,7 +14,6 @@ import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy/util/file_picker/file_picker_impl.dart'; import 'package:appflowy/util/file_picker/file_picker_impl.dart';
import 'package:appflowy/util/file_picker/file_picker_service.dart'; import 'package:appflowy/util/file_picker/file_picker_service.dart';
import 'package:appflowy/plugins/document/application/prelude.dart'; import 'package:appflowy/plugins/document/application/prelude.dart';
import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
import 'package:appflowy/workspace/application/user/prelude.dart'; import 'package:appflowy/workspace/application/user/prelude.dart';
import 'package:appflowy/workspace/application/workspace/prelude.dart'; import 'package:appflowy/workspace/application/workspace/prelude.dart';
import 'package:appflowy/workspace/application/edit_panel/edit_panel_bloc.dart'; import 'package:appflowy/workspace/application/edit_panel/edit_panel_bloc.dart';
@ -32,26 +32,35 @@ import 'package:get_it/get_it.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
class DependencyResolver { class DependencyResolver {
static Future<void> resolve(GetIt getIt) async { static Future<void> resolve(
GetIt getIt,
IntegrationMode mode,
) async {
_resolveUserDeps(getIt); _resolveUserDeps(getIt);
_resolveHomeDeps(getIt); _resolveHomeDeps(getIt);
_resolveFolderDeps(getIt); _resolveFolderDeps(getIt);
_resolveDocDeps(getIt); _resolveDocDeps(getIt);
_resolveGridDeps(getIt); _resolveGridDeps(getIt);
_resolveCommonService(getIt, mode);
_resolveCommonService(getIt);
} }
} }
void _resolveCommonService(GetIt getIt) async { void _resolveCommonService(
GetIt getIt,
IntegrationMode mode,
) async {
// getIt.registerFactory<KeyValueStorage>(() => RustKeyValue()); // getIt.registerFactory<KeyValueStorage>(() => RustKeyValue());
getIt.registerFactory<KeyValueStorage>(() => DartKeyValue()); getIt.registerFactory<KeyValueStorage>(() => DartKeyValue());
getIt.registerFactory<FilePickerService>(() => FilePicker()); getIt.registerFactory<FilePickerService>(() => FilePicker());
getIt.registerFactory<ApplicationDataStorage>(() => ApplicationDataStorage()); if (mode.isTest) {
getIt.registerFactory<ApplicationDataStorage>(
() => MockApplicationDataStorage(),
);
} else {
getIt.registerFactory<ApplicationDataStorage>(
() => ApplicationDataStorage(),
);
}
getIt.registerFactoryAsync<OpenAIRepository>( getIt.registerFactoryAsync<OpenAIRepository>(
() async { () async {

View File

@ -1,7 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:appflowy/env/env.dart'; import 'package:appflowy/env/env.dart';
import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy_backend/appflowy_backend.dart'; import 'package:appflowy_backend/appflowy_backend.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -18,8 +18,14 @@ abstract class EntryPoint {
Widget create(LaunchConfiguration config); Widget create(LaunchConfiguration config);
} }
class FlowyRunnerContext {
final Directory applicationDataDirectory;
FlowyRunnerContext({required this.applicationDataDirectory});
}
class FlowyRunner { class FlowyRunner {
static Future<void> run( static Future<FlowyRunnerContext> run(
EntryPoint f, EntryPoint f,
IntegrationMode mode, { IntegrationMode mode, {
LaunchConfiguration config = const LaunchConfiguration( LaunchConfiguration config = const LaunchConfiguration(
@ -32,11 +38,10 @@ class FlowyRunner {
// Specify the env // Specify the env
initGetIt(getIt, mode, f, config); initGetIt(getIt, mode, f, config);
final directory = await getIt<ApplicationDataStorage>() final applicationDataDirectory =
.getPath() await getIt<ApplicationDataStorage>().getPath().then(
.then((value) => Directory(value)); (value) => Directory(value),
);
// final directory = await appFlowyDocumentDirectory();
// add task // add task
final launcher = getIt<AppLauncher>(); final launcher = getIt<AppLauncher>();
@ -49,13 +54,13 @@ class FlowyRunner {
// init the app window // init the app window
const InitAppWindowTask(), const InitAppWindowTask(),
// Init Rust SDK // Init Rust SDK
InitRustSDKTask(directory: directory), InitRustSDKTask(directory: applicationDataDirectory),
// Load Plugins, like document, grid ... // Load Plugins, like document, grid ...
const PluginLoadTask(), const PluginLoadTask(),
// init the app widget // init the app widget
// ignore in test mode // ignore in test mode
if (!mode.isTest()) ...[ if (!mode.isUnitTest) ...[
const HotKeyTask(), const HotKeyTask(),
InitSupabaseTask( InitSupabaseTask(
url: Env.supabaseUrl, url: Env.supabaseUrl,
@ -70,6 +75,10 @@ class FlowyRunner {
], ],
); );
await launcher.launch(); // execute the tasks await launcher.launch(); // execute the tasks
return FlowyRunnerContext(
applicationDataDirectory: applicationDataDirectory,
);
} }
} }
@ -94,7 +103,7 @@ Future<void> initGetIt(
); );
getIt.registerSingleton<PluginSandbox>(PluginSandbox()); getIt.registerSingleton<PluginSandbox>(PluginSandbox());
await DependencyResolver.resolve(getIt); await DependencyResolver.resolve(getIt, env);
} }
class LaunchContext { class LaunchContext {
@ -145,23 +154,24 @@ class AppLauncher {
enum IntegrationMode { enum IntegrationMode {
develop, develop,
release, release,
test, unitTest,
integrationTest, integrationTest;
}
extension IntegrationEnvExt on IntegrationMode { // test mode
bool isTest() { bool get isTest => isUnitTest || isIntegrationTest;
return this == IntegrationMode.test; bool get isUnitTest => this == IntegrationMode.unitTest;
} bool get isIntegrationTest => this == IntegrationMode.integrationTest;
bool isIntegrationTest() { // release mode
return this == IntegrationMode.integrationTest; bool get isRelease => this == IntegrationMode.release;
}
// develop mode
bool get isDevelop => this == IntegrationMode.develop;
} }
IntegrationMode integrationEnv() { IntegrationMode integrationEnv() {
if (Platform.environment.containsKey('FLUTTER_TEST')) { if (Platform.environment.containsKey('FLUTTER_TEST')) {
return IntegrationMode.test; return IntegrationMode.unitTest;
} }
if (kReleaseMode) { if (kReleaseMode) {

View File

@ -8,5 +8,6 @@ class InitLocalizationTask extends LaunchTask {
@override @override
Future<void> initialize(LaunchContext context) async { Future<void> initialize(LaunchContext context) async {
await EasyLocalization.ensureInitialized(); await EasyLocalization.ensureInitialized();
EasyLocalization.logger.enableBuildModes = [];
} }
} }

View File

@ -38,7 +38,7 @@ AppFlowyEnv getAppFlowyEnv() {
final collabTableConfig = final collabTableConfig =
CollabTableConfig(enable: true, table_name: Env.supabaseCollabTable); CollabTableConfig(enable: true, table_name: Env.supabaseCollabTable);
final supbaseDBConfig = SupabaseDBConfig( final supabaseDBConfig = SupabaseDBConfig(
url: Env.supabaseUrl, url: Env.supabaseUrl,
key: Env.supabaseKey, key: Env.supabaseKey,
jwt_secret: Env.supabaseJwtSecret, jwt_secret: Env.supabaseJwtSecret,
@ -47,7 +47,7 @@ AppFlowyEnv getAppFlowyEnv() {
return AppFlowyEnv( return AppFlowyEnv(
supabase_config: supabaseConfig, supabase_config: supabaseConfig,
supabase_db_config: supbaseDBConfig, supabase_db_config: supabaseDBConfig,
); );
} }
@ -62,7 +62,7 @@ Future<Directory> appFlowyApplicationDataDirectory() async {
case IntegrationMode.release: case IntegrationMode.release:
final Directory documentsDir = await getApplicationSupportDirectory(); final Directory documentsDir = await getApplicationSupportDirectory();
return Directory(path.join(documentsDir.path, 'data')).create(); return Directory(path.join(documentsDir.path, 'data')).create();
case IntegrationMode.test: case IntegrationMode.unitTest:
case IntegrationMode.integrationTest: case IntegrationMode.integrationTest:
return Directory(path.join(Directory.current.path, '.sandbox')); return Directory(path.join(Directory.current.path, '.sandbox'));
} }

View File

@ -27,7 +27,7 @@ class InitAppWindowTask extends LaunchTask with WindowListener {
windowManager.addListener(this); windowManager.addListener(this);
Size windowSize = await WindowSizeManager().getSize(); Size windowSize = await WindowSizeManager().getSize();
if (context.env.isIntegrationTest()) { if (context.env.isIntegrationTest) {
windowSize = const Size(1600, 1200); windowSize = const Size(1600, 1200);
} }

View File

@ -1,6 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:appflowy/util/file_picker/file_picker_service.dart'; import 'package:appflowy/util/file_picker/file_picker_service.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.dart';
@ -12,7 +13,6 @@ import 'package:google_fonts/google_fonts.dart';
import '../../../generated/locale_keys.g.dart'; import '../../../generated/locale_keys.g.dart';
import '../../../startup/startup.dart'; import '../../../startup/startup.dart';
import '../../../workspace/application/settings/settings_location_cubit.dart';
import '../../../workspace/presentation/home/toast.dart'; import '../../../workspace/presentation/home/toast.dart';
enum _FolderPage { enum _FolderPage {

View File

@ -53,7 +53,7 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
/// changed. Fallback to [en] locale if [newLocale] is not supported. /// changed. Fallback to [en] locale if [newLocale] is not supported.
void setLocale(BuildContext context, Locale newLocale) { void setLocale(BuildContext context, Locale newLocale) {
if (!context.supportedLocales.contains(newLocale)) { if (!context.supportedLocales.contains(newLocale)) {
Log.warn("Unsupported locale: $newLocale, Fallback to locale: en"); // Log.warn("Unsupported locale: $newLocale, Fallback to locale: en");
newLocale = const Locale('en'); newLocale = const Locale('en');
} }

View File

@ -0,0 +1,105 @@
import 'dart:io';
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_backend/log.dart';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart' as p;
import '../../../startup/tasks/prelude.dart';
const appFlowyDataFolder = "AppFlowyDataDoNotRename";
class ApplicationDataStorage {
ApplicationDataStorage();
String? _cachePath;
/// Set the custom path to store the data.
/// If the path is not exists, the path will be created.
/// If the path is invalid, the path will be set to the default path.
Future<void> setCustomPath(String path) async {
if (kIsWeb || Platform.isAndroid || Platform.isIOS) {
Log.info('LocalFileStorage is not supported on this platform.');
return;
}
if (Platform.isMacOS) {
// remove the prefix `/Volumes/*`
path = path.replaceFirst(RegExp(r'^/Volumes/[^/]+'), '');
} else if (Platform.isWindows) {
path = path.replaceAll('/', '\\');
}
// If the path is not ends with `AppFlowyData`, we will append the
// `AppFlowyData` to the path. If the path is ends with `AppFlowyData`,
// which means the path is the custom path.
if (p.basename(path) != appFlowyDataFolder) {
path = p.join(path, appFlowyDataFolder);
}
// create the directory if not exists.
final directory = Directory(path);
if (!directory.existsSync()) {
await directory.create(recursive: true);
}
setPath(path);
}
Future<void> setPath(String path) async {
if (kIsWeb || Platform.isAndroid || Platform.isIOS) {
Log.info('LocalFileStorage is not supported on this platform.');
return;
}
await getIt<KeyValueStorage>().set(KVKeys.pathLocation, path);
// clear the cache path, and not set the cache path to the new path because the set path may be invalid
_cachePath = null;
}
Future<String> getPath() async {
if (_cachePath != null) {
return _cachePath!;
}
final response = await getIt<KeyValueStorage>().get(KVKeys.pathLocation);
String path = await response.fold(
(error) async {
// return the default path if the path is not set
final directory = await appFlowyApplicationDataDirectory();
return directory.path;
},
(path) => path,
);
_cachePath = path;
// if the path is not exists means the path is invalid, so we should clear the kv store
if (!Directory(path).existsSync()) {
await getIt<KeyValueStorage>().clear();
final directory = await appFlowyApplicationDataDirectory();
path = directory.path;
}
return path;
}
}
class MockApplicationDataStorage extends ApplicationDataStorage {
MockApplicationDataStorage();
// this value will be clear after setup
// only for the initial step
@visibleForTesting
static String? initialPath;
@override
Future<String> getPath() async {
final path = initialPath;
if (path != null) {
initialPath = null;
return Future.value(path);
}
return super.getPath();
}
}

View File

@ -1 +1,2 @@
export 'settings_dialog_bloc.dart'; export 'settings_dialog_bloc.dart';
export 'application_data_storage.dart';

View File

@ -1,13 +1,8 @@
import 'dart:io';
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:path/path.dart' as p;
import '../../../startup/tasks/prelude.dart'; import '../../../startup/tasks/prelude.dart';
@ -27,7 +22,7 @@ class SettingsLocationCubit extends Cubit<SettingsLocationState> {
Future<void> resetDataStoragePathToApplicationDefault() async { Future<void> resetDataStoragePathToApplicationDefault() async {
final directory = await appFlowyApplicationDataDirectory(); final directory = await appFlowyApplicationDataDirectory();
await getIt<ApplicationDataStorage>()._setPath(directory.path); await getIt<ApplicationDataStorage>().setPath(directory.path);
emit(SettingsLocationState.didReceivedPath(directory.path)); emit(SettingsLocationState.didReceivedPath(directory.path));
} }
@ -41,79 +36,3 @@ class SettingsLocationCubit extends Cubit<SettingsLocationState> {
emit(SettingsLocationState.didReceivedPath(path)); emit(SettingsLocationState.didReceivedPath(path));
} }
} }
const appFlowyDataFolder = "AppFlowyDataDoNotRename";
class ApplicationDataStorage {
ApplicationDataStorage();
String? _cachePath;
/// Set the custom path to store the data.
/// If the path is not exists, the path will be created.
/// If the path is invalid, the path will be set to the default path.
Future<void> setCustomPath(String path) async {
if (kIsWeb || Platform.isAndroid || Platform.isIOS) {
Log.info('LocalFileStorage is not supported on this platform.');
return;
}
if (Platform.isMacOS) {
// remove the prefix `/Volumes/*`
path = path.replaceFirst(RegExp(r'^/Volumes/[^/]+'), '');
} else if (Platform.isWindows) {
path = path.replaceAll('/', '\\');
}
// If the path is not ends with `AppFlowyData`, we will append the
// `AppFlowyData` to the path. If the path is ends with `AppFlowyData`,
// which means the path is the custom path.
if (p.basename(path) != appFlowyDataFolder) {
path = p.join(path, appFlowyDataFolder);
}
// create the directory if not exists.
final directory = Directory(path);
if (!directory.existsSync()) {
await directory.create(recursive: true);
}
_setPath(path);
}
Future<void> _setPath(String path) async {
if (kIsWeb || Platform.isAndroid || Platform.isIOS) {
Log.info('LocalFileStorage is not supported on this platform.');
return;
}
await getIt<KeyValueStorage>().set(KVKeys.pathLocation, path);
// clear the cache path, and not set the cache path to the new path because the set path may be invalid
_cachePath = null;
}
Future<String> getPath() async {
if (_cachePath != null) {
return _cachePath!;
}
final response = await getIt<KeyValueStorage>().get(KVKeys.pathLocation);
String path = await response.fold(
(error) async {
// return the default path if the path is not set
final directory = await appFlowyApplicationDataDirectory();
return directory.path;
},
(path) => path,
);
_cachePath = path;
// if the path is not exists means the path is invalid, so we should clear the kv store
if (!Directory(path).existsSync()) {
await getIt<KeyValueStorage>().clear();
final directory = await appFlowyApplicationDataDirectory();
path = directory.path;
}
return path;
}
}

View File

@ -36,7 +36,7 @@ class AppFlowyUnitTest {
await FlowyRunner.run( await FlowyRunner.run(
FlowyTestApp(), FlowyTestApp(),
IntegrationMode.test, IntegrationMode.unitTest,
); );
final test = AppFlowyUnitTest(); final test = AppFlowyUnitTest();