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
strategy:
matrix:
os: [ubuntu-latest]
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}

View File

@ -10,21 +10,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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 {
await tester.initializeAppFlowy();
await tester.tapGoButton();

View File

@ -11,21 +11,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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 {
await tester.initializeAppFlowy();
await tester.tapGoButton();

View File

@ -10,21 +10,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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 {
await tester.initializeAppFlowy();
await tester.tapGoButton();

View File

@ -5,27 +5,11 @@ 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();

View File

@ -14,21 +14,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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 {
await tester.initializeAppFlowy();
await tester.tapGoButton();

View File

@ -8,21 +8,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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 {
await tester.initializeAppFlowy();
await tester.tapGoButton();

View File

@ -9,21 +9,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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 {
await tester.initializeAppFlowy();
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: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: '',
// );
await tester.openV020database();
// check the text cell
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 '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 sort', (tester) async {
await tester.openV020database();
// create a filter

View File

@ -11,21 +11,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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 {
await tester.initializeAppFlowy();
await tester.tapGoButton();

View File

@ -9,21 +9,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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 {
await tester.initializeAppFlowy();
await tester.tapGoButton();

View File

@ -10,21 +10,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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',
(tester) async {
await tester.initializeAppFlowy();

View File

@ -14,17 +14,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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 {
await tester.initializeAppFlowy();
await tester.tapGoButton();

View File

@ -11,17 +11,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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 {
await tester.initializeAppFlowy();
await tester.tapGoButton();

View File

@ -13,17 +13,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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 {
await tester.initializeAppFlowy();
await tester.tapGoButton();

View File

@ -12,23 +12,8 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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 {
await tester.initializeAppFlowy();
final context = await tester.initializeAppFlowy();
await tester.tapGoButton();
// expect to see a readme page
@ -38,18 +23,21 @@ void main() {
await tester.tapImportButton();
final testFileNames = ['test1.md', 'test2.md'];
final fileLocation = await tester.currentFileLocation();
final paths = <String>[];
for (final fileName in testFileNames) {
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
await mockPickFilePaths(testFileNames, name: location);
await mockPickFilePaths(
paths: testFileNames
.map((e) => p.join(context.applicationDataDirectory, e))
.toList(),
);
await tester.tapTextAndMarkdownButton();

View File

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

View File

@ -12,17 +12,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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 {
await tester.initializeAppFlowy();
await tester.tapGoButton();

View File

@ -3,7 +3,7 @@ import 'dart:io';
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
import 'package:flutter_test/flutter_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/util.dart';
@ -11,30 +11,17 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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 {
await tester.initializeAppFlowy();
final context = await tester.initializeAppFlowy();
await tester.tapGoButton();
// expect to see a readme page
tester.expectToSeePageName(readme);
// 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
await tester.tapShareButton();
await tester.tapMarkdownButton();
@ -52,7 +39,7 @@ void main() {
testWidgets(
'share the markdown after renaming the document name',
(tester) async {
await tester.initializeAppFlowy();
final context = await tester.initializeAppFlowy();
await tester.tapGoButton();
// expect to see a readme page
@ -65,8 +52,12 @@ void main() {
final shareButton = find.byType(ShareActionList);
final shareButtonState =
tester.state(shareButton) as ShareActionListState;
final path =
await mockSaveFilePath(location, '${shareButtonState.name}.md');
final path = await mockSaveFilePath(
p.join(
context.applicationDataDirectory,
'${shareButtonState.name}.md',
),
);
// click the share button and select markdown
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/settings_location_cubit.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter_test/flutter_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/util.dart';
import 'package:path/path.dart' as p;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
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 {
final userA = uuid();
final userB = uuid();
const userA = 'UserA';
const userB = 'UserB';
await TestFolder.cleanTestLocation(userA);
await TestFolder.cleanTestLocation(userB);
await TestFolder.setTestLocation(p.join(userA, appFlowyDataFolder));
await tester.initializeAppFlowy();
final initialPath = p.join(userA, appFlowyDataFolder);
final context = await tester.initializeAppFlowy(
pathExtension: initialPath,
);
// remove the last extension
final rootPath = context.applicationDataDirectory.replaceFirst(
initialPath,
'',
);
await tester.tapGoButton();
tester.expectToSeeHomePage();
// switch to user B
{
// set user name to userA
// set user name for userA
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.user);
await tester.enterUserName(userA);
@ -51,12 +39,14 @@ void main() {
await tester.pumpAndSettle();
// mock the file_picker result
await mockGetDirectoryPath(userB);
await mockGetDirectoryPath(
p.join(rootPath, userB),
);
await tester.tapCustomLocationButton();
await tester.pumpAndSettle();
tester.expectToSeeHomePage();
// set user name to userB
// set user name for userB
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.user);
await tester.enterUserName(userB);
@ -68,7 +58,9 @@ void main() {
await tester.pumpAndSettle();
// mock the file_picker result
await mockGetDirectoryPath(userA);
await mockGetDirectoryPath(
p.join(rootPath, userA),
);
await tester.tapCustomLocationButton();
await tester.pumpAndSettle();
@ -83,16 +75,15 @@ void main() {
await tester.pumpAndSettle();
// mock the file_picker result
await mockGetDirectoryPath(userB);
await mockGetDirectoryPath(
p.join(rootPath, userB),
);
await tester.tapCustomLocationButton();
await tester.pumpAndSettle();
tester.expectToSeeHomePage();
tester.expectToSeeUserName(userB);
}
await TestFolder.cleanTestLocation(userA);
await TestFolder.cleanTestLocation(userB);
});
testWidgets('reset to default location', (tester) async {
@ -109,8 +100,8 @@ void main() {
await tester.restoreLocation();
expect(
await TestFolder.defaultDevelopmentLocation(),
await TestFolder.currentLocation(),
await appFlowyApplicationDataDirectory().then((value) => value.path),
await getIt<ApplicationDataStorage>().getPath(),
);
});
});

View File

@ -1,81 +1,77 @@
import 'dart:io';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/startup/entry_point.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: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';
import 'package:shared_preferences/shared_preferences.dart';
class TestFolder {
/// Location / Path
/// Set a given AppFlowy data storage location under test environment.
///
/// 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,
class FlowyTestContext {
FlowyTestContext({
required this.applicationDataDirectory,
});
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);
}
final String applicationDataDirectory;
}
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
.setMockMethodCallHandler(const MethodChannel('hotkey_manager'),
(MethodCall methodCall) async {
if (methodCall.method == 'unregisterAll') {
// do nothing
}
return;
});
}
WidgetsFlutterBinding.ensureInitialized();
await FlowyRunner.run(FlowyApp(), IntegrationMode.integrationTest);
Future<String> mockApplicationDataStorage({
// use to append after the application data directory
String? pathExtension,
}) async {
final dir = await getTemporaryDirectory();
await wait(3000);
await pumpAndSettle(const Duration(seconds: 2));
// Use a random uuid to avoid conflict.
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(

View File

@ -17,11 +17,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'util.dart';
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.
Future<void> tapGoButton() async {
final goButton = find.byType(GoButton);

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.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: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/grid/application/grid_header_bloc.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/supabase_auth_service.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_service.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/workspace/prelude.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;
class DependencyResolver {
static Future<void> resolve(GetIt getIt) async {
static Future<void> resolve(
GetIt getIt,
IntegrationMode mode,
) async {
_resolveUserDeps(getIt);
_resolveHomeDeps(getIt);
_resolveFolderDeps(getIt);
_resolveDocDeps(getIt);
_resolveGridDeps(getIt);
_resolveCommonService(getIt);
_resolveCommonService(getIt, mode);
}
}
void _resolveCommonService(GetIt getIt) async {
void _resolveCommonService(
GetIt getIt,
IntegrationMode mode,
) async {
// getIt.registerFactory<KeyValueStorage>(() => RustKeyValue());
getIt.registerFactory<KeyValueStorage>(() => DartKeyValue());
getIt.registerFactory<FilePickerService>(() => FilePicker());
getIt.registerFactory<ApplicationDataStorage>(() => ApplicationDataStorage());
if (mode.isTest) {
getIt.registerFactory<ApplicationDataStorage>(
() => MockApplicationDataStorage(),
);
} else {
getIt.registerFactory<ApplicationDataStorage>(
() => ApplicationDataStorage(),
);
}
getIt.registerFactoryAsync<OpenAIRepository>(
() async {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import 'dart:io';
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:flowy_infra/image.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 '../../../startup/startup.dart';
import '../../../workspace/application/settings/settings_location_cubit.dart';
import '../../../workspace/presentation/home/toast.dart';
enum _FolderPage {

View File

@ -53,7 +53,7 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
/// changed. Fallback to [en] locale if [newLocale] is not supported.
void setLocale(BuildContext context, Locale 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');
}

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 '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_backend/log.dart';
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:path/path.dart' as p;
import '../../../startup/tasks/prelude.dart';
@ -27,7 +22,7 @@ class SettingsLocationCubit extends Cubit<SettingsLocationState> {
Future<void> resetDataStoragePathToApplicationDefault() async {
final directory = await appFlowyApplicationDataDirectory();
await getIt<ApplicationDataStorage>()._setPath(directory.path);
await getIt<ApplicationDataStorage>().setPath(directory.path);
emit(SettingsLocationState.didReceivedPath(directory.path));
}
@ -41,79 +36,3 @@ class SettingsLocationCubit extends Cubit<SettingsLocationState> {
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(
FlowyTestApp(),
IntegrationMode.test,
IntegrationMode.unitTest,
);
final test = AppFlowyUnitTest();