mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 09:15:48 -06:00
Added test for login page
This commit is contained in:
@@ -53,6 +53,7 @@ android {
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
@@ -77,4 +78,8 @@ flutter {
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
void main() {
|
||||
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
testWidgets('screenshot', (WidgetTester tester) async {
|
||||
// Build the app.
|
||||
|
||||
// This is required prior to taking the screenshot (Android only).
|
||||
await binding.convertFlutterSurfaceToImage();
|
||||
|
||||
// Trigger a frame.
|
||||
await tester.pumpAndSettle();
|
||||
await binding.takeScreenshot('screenshot-1');
|
||||
});
|
||||
}
|
||||
235
integration_test/login_integration_test.dart
Normal file
235
integration_test/login_integration_test.dart
Normal file
@@ -0,0 +1,235 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status.service.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/di_test_mocks.mocks.dart';
|
||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
|
||||
import 'src/framework.dart';
|
||||
|
||||
void main() async {
|
||||
final t = await initializeTestingFramework(languageCode: 'de');
|
||||
|
||||
const testServerUrl = 'https://example.com';
|
||||
const testUsername = 'user';
|
||||
const testPassword = 'pass';
|
||||
|
||||
final serverAddressField = find.byKey(const ValueKey('login-server-address'));
|
||||
final usernameField = find.byKey(const ValueKey('login-username'));
|
||||
final passwordField = find.byKey(const ValueKey('login-password'));
|
||||
final loginBtn = find.byKey(const ValueKey('login-login-button'));
|
||||
|
||||
testWidgets('Test successful login flow', (WidgetTester tester) async {
|
||||
await initAndLaunchTestApp(tester, () async {
|
||||
// Initialize dat for mocked classes
|
||||
when((getIt<ConnectivityStatusService>()).connectivityChanges())
|
||||
.thenAnswer((i) => Stream.value(true));
|
||||
when((getIt<LocalVault>() as MockLocalVault)
|
||||
.loadAuthenticationInformation())
|
||||
.thenAnswer((realInvocation) async => null);
|
||||
when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
||||
.thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||
preferredLocaleSubtag: 'en',
|
||||
preferredThemeMode: ThemeMode.light,
|
||||
isLocalAuthenticationEnabled: false,
|
||||
preferredViewType: ViewType.list,
|
||||
showInboxOnStartup: false,
|
||||
));
|
||||
when(getIt<PaperlessAuthenticationApi>().login(
|
||||
username: testUsername,
|
||||
password: testPassword,
|
||||
serverUrl: testServerUrl,
|
||||
)).thenAnswer((i) => Future.value("eyTestToken"));
|
||||
|
||||
await getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
await getIt<AuthenticationCubit>().initialize();
|
||||
});
|
||||
|
||||
// Mocked classes
|
||||
|
||||
await t.binding.waitUntilFirstFrameRasterized;
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(serverAddressField, testServerUrl);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(usernameField, testUsername);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(passwordField, testPassword);
|
||||
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(loginBtn);
|
||||
|
||||
verify(getIt<PaperlessAuthenticationApi>().login(
|
||||
username: testUsername,
|
||||
password: testPassword,
|
||||
serverUrl: testServerUrl,
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
testWidgets('Test login validation missing password',
|
||||
(WidgetTester tester) async {
|
||||
await initAndLaunchTestApp(tester, () async {
|
||||
when((getIt<ConnectivityStatusService>() as MockConnectivityStatusService)
|
||||
.connectivityChanges())
|
||||
.thenAnswer((i) => Stream.value(true));
|
||||
when((getIt<LocalVault>() as MockLocalVault)
|
||||
.loadAuthenticationInformation())
|
||||
.thenAnswer((realInvocation) async => null);
|
||||
|
||||
when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
||||
.thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||
preferredLocaleSubtag: 'en',
|
||||
preferredThemeMode: ThemeMode.light,
|
||||
isLocalAuthenticationEnabled: false,
|
||||
preferredViewType: ViewType.list,
|
||||
showInboxOnStartup: false,
|
||||
));
|
||||
|
||||
await getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
await getIt<AuthenticationCubit>().initialize();
|
||||
});
|
||||
// Mocked classes
|
||||
|
||||
// Initialize dat for mocked classes
|
||||
|
||||
await t.binding.waitUntilFirstFrameRasterized;
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(serverAddressField, testServerUrl);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(usernameField, testUsername);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(loginBtn);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
verifyNever(
|
||||
(getIt<PaperlessAuthenticationApi>() as MockPaperlessAuthenticationApi)
|
||||
.login(
|
||||
username: testUsername,
|
||||
password: testPassword,
|
||||
serverUrl: testServerUrl,
|
||||
));
|
||||
expect(
|
||||
find.textContaining(t.translations.loginPagePasswordValidatorMessageText),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Test login validation missing username',
|
||||
(WidgetTester tester) async {
|
||||
await initAndLaunchTestApp(tester, () async {
|
||||
when((getIt<ConnectivityStatusService>() as MockConnectivityStatusService)
|
||||
.connectivityChanges())
|
||||
.thenAnswer((i) => Stream.value(true));
|
||||
when((getIt<LocalVault>() as MockLocalVault)
|
||||
.loadAuthenticationInformation())
|
||||
.thenAnswer((realInvocation) async => null);
|
||||
when((getIt<LocalVault>() as MockLocalVault).loadApplicationSettings())
|
||||
.thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||
preferredLocaleSubtag: 'en',
|
||||
preferredThemeMode: ThemeMode.light,
|
||||
isLocalAuthenticationEnabled: false,
|
||||
preferredViewType: ViewType.list,
|
||||
showInboxOnStartup: false,
|
||||
));
|
||||
await getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
await getIt<AuthenticationCubit>().initialize();
|
||||
});
|
||||
|
||||
await t.binding.waitUntilFirstFrameRasterized;
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(serverAddressField, testServerUrl);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(passwordField, testPassword);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(loginBtn);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
verifyNever(
|
||||
(getIt<PaperlessAuthenticationApi>() as MockPaperlessAuthenticationApi)
|
||||
.login(
|
||||
username: testUsername,
|
||||
password: testPassword,
|
||||
serverUrl: testServerUrl,
|
||||
));
|
||||
expect(
|
||||
find.textContaining(t.translations.loginPageUsernameValidatorMessageText),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Test login validation missing server address',
|
||||
(WidgetTester tester) async {
|
||||
initAndLaunchTestApp(tester, () async {
|
||||
when((getIt<ConnectivityStatusService>()).connectivityChanges())
|
||||
.thenAnswer((i) => Stream.value(true));
|
||||
|
||||
when((getIt<LocalVault>()).loadAuthenticationInformation())
|
||||
.thenAnswer((realInvocation) async => null);
|
||||
|
||||
when((getIt<LocalVault>()).loadApplicationSettings())
|
||||
.thenAnswer((realInvocation) async => ApplicationSettingsState(
|
||||
preferredLocaleSubtag: 'en',
|
||||
preferredThemeMode: ThemeMode.light,
|
||||
isLocalAuthenticationEnabled: false,
|
||||
preferredViewType: ViewType.list,
|
||||
showInboxOnStartup: false,
|
||||
));
|
||||
|
||||
await getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
await getIt<AuthenticationCubit>().initialize();
|
||||
});
|
||||
|
||||
await t.binding.waitUntilFirstFrameRasterized;
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(usernameField, testUsername);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.enterText(passwordField, testPassword);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(loginBtn);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
verifyNever(getIt<PaperlessAuthenticationApi>().login(
|
||||
username: testUsername,
|
||||
password: testPassword,
|
||||
serverUrl: testServerUrl,
|
||||
));
|
||||
expect(
|
||||
find.textContaining(
|
||||
t.translations.loginPageServerUrlValidatorMessageText),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
}
|
||||
41
integration_test/src/framework.dart
Normal file
41
integration_test/src/framework.dart
Normal file
@@ -0,0 +1,41 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/main.dart';
|
||||
|
||||
Future<TestingFrameworkVariables> initializeTestingFramework(
|
||||
{String languageCode = 'en'}) async {
|
||||
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
configureDependencies('test');
|
||||
final translations = await S.load(
|
||||
Locale.fromSubtags(
|
||||
languageCode: languageCode,
|
||||
),
|
||||
);
|
||||
return TestingFrameworkVariables(
|
||||
binding: binding,
|
||||
translations: translations,
|
||||
);
|
||||
}
|
||||
|
||||
class TestingFrameworkVariables {
|
||||
final IntegrationTestWidgetsFlutterBinding binding;
|
||||
final S translations;
|
||||
|
||||
TestingFrameworkVariables({
|
||||
required this.binding,
|
||||
required this.translations,
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> initAndLaunchTestApp(
|
||||
WidgetTester tester,
|
||||
Future<void> Function() initializationCallback,
|
||||
) async {
|
||||
await initializationCallback();
|
||||
runApp(const PaperlessMobileEntrypoint());
|
||||
}
|
||||
@@ -7,28 +7,30 @@ import 'package:injectable/injectable.dart';
|
||||
@singleton
|
||||
class ConnectivityCubit extends Cubit<ConnectivityState> {
|
||||
final ConnectivityStatusService connectivityStatusService;
|
||||
late final StreamSubscription<bool> _sub;
|
||||
StreamSubscription<bool>? _sub;
|
||||
|
||||
ConnectivityCubit(this.connectivityStatusService)
|
||||
: super(ConnectivityState.undefined);
|
||||
|
||||
Future<void> initialize() async {
|
||||
final bool isConnected =
|
||||
await connectivityStatusService.isConnectedToInternet();
|
||||
emit(isConnected
|
||||
? ConnectivityState.connected
|
||||
: ConnectivityState.notConnected);
|
||||
_sub =
|
||||
connectivityStatusService.connectivityChanges().listen((isConnected) {
|
||||
if (_sub == null) {
|
||||
final bool isConnected =
|
||||
await connectivityStatusService.isConnectedToInternet();
|
||||
emit(isConnected
|
||||
? ConnectivityState.connected
|
||||
: ConnectivityState.notConnected);
|
||||
});
|
||||
_sub =
|
||||
connectivityStatusService.connectivityChanges().listen((isConnected) {
|
||||
emit(isConnected
|
||||
? ConnectivityState.connected
|
||||
: ConnectivityState.notConnected);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_sub.cancel();
|
||||
_sub?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import 'package:http_interceptor/http_interceptor.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
@injectable
|
||||
@dev
|
||||
@prod
|
||||
class AuthenticationInterceptor implements InterceptorContract {
|
||||
final LocalVault _localVault;
|
||||
AuthenticationInterceptor(this._localVault);
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:injectable/injectable.dart';
|
||||
const interceptedRoutes = ['thumb/'];
|
||||
|
||||
@injectable
|
||||
@dev
|
||||
@prod
|
||||
class ResponseConversionInterceptor implements InterceptorContract {
|
||||
@override
|
||||
Future<BaseRequest> interceptRequest({required BaseRequest request}) async =>
|
||||
|
||||
@@ -13,6 +13,8 @@ import 'package:injectable/injectable.dart';
|
||||
/// Convenience class which handles timeout errors.
|
||||
///
|
||||
@Injectable(as: BaseClient)
|
||||
@dev
|
||||
@prod
|
||||
@Named("timeoutClient")
|
||||
class TimeoutClient implements BaseClient {
|
||||
final ConnectivityStatusService connectivityStatusService;
|
||||
|
||||
@@ -9,7 +9,7 @@ abstract class ConnectivityStatusService {
|
||||
Stream<bool> connectivityChanges();
|
||||
}
|
||||
|
||||
@Injectable(as: ConnectivityStatusService)
|
||||
@Injectable(as: ConnectivityStatusService, env: ['prod', 'dev'])
|
||||
class ConnectivityStatusServiceImpl implements ConnectivityStatusService {
|
||||
final Connectivity connectivity;
|
||||
|
||||
|
||||
@@ -8,15 +8,27 @@ import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
@singleton
|
||||
class LocalVault {
|
||||
abstract class LocalVault {
|
||||
Future<void> storeAuthenticationInformation(AuthenticationInformation auth);
|
||||
Future<AuthenticationInformation?> loadAuthenticationInformation();
|
||||
Future<ClientCertificate?> loadCertificate();
|
||||
Future<bool> storeApplicationSettings(ApplicationSettingsState settings);
|
||||
Future<ApplicationSettingsState?> loadApplicationSettings();
|
||||
Future<void> clear();
|
||||
}
|
||||
|
||||
@Injectable(as: LocalVault)
|
||||
@prod
|
||||
@dev
|
||||
class LocalVaultImpl implements LocalVault {
|
||||
static const applicationSettingsKey = "applicationSettings";
|
||||
static const authenticationKey = "authentication";
|
||||
|
||||
final EncryptedSharedPreferences sharedPreferences;
|
||||
|
||||
LocalVault(this.sharedPreferences);
|
||||
LocalVaultImpl(this.sharedPreferences);
|
||||
|
||||
@override
|
||||
Future<void> storeAuthenticationInformation(
|
||||
AuthenticationInformation auth,
|
||||
) async {
|
||||
@@ -26,6 +38,7 @@ class LocalVault {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AuthenticationInformation?> loadAuthenticationInformation() async {
|
||||
if ((await sharedPreferences.getString(authenticationKey)).isEmpty) {
|
||||
return null;
|
||||
@@ -35,11 +48,13 @@ class LocalVault {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ClientCertificate?> loadCertificate() async {
|
||||
return loadAuthenticationInformation()
|
||||
.then((value) => value?.clientCertificate);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> storeApplicationSettings(ApplicationSettingsState settings) {
|
||||
return sharedPreferences.setString(
|
||||
applicationSettingsKey,
|
||||
@@ -47,6 +62,7 @@ class LocalVault {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ApplicationSettingsState?> loadApplicationSettings() async {
|
||||
final settings = await sharedPreferences.getString(applicationSettingsKey);
|
||||
if (settings.isEmpty) {
|
||||
@@ -58,6 +74,7 @@ class LocalVault {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clear() {
|
||||
return sharedPreferences.clear();
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ import 'package:get_it/get_it.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
final getIt = GetIt.instance..allowReassignment;
|
||||
|
||||
@InjectableInit(
|
||||
initializerName: r'$initGetIt', // default
|
||||
preferRelativeImports: true, // default
|
||||
asExtension: false, // default
|
||||
)
|
||||
void configureDependencies() => $initGetIt(getIt);
|
||||
void configureDependencies(String environment) =>
|
||||
$initGetIt(getIt, environment: environment);
|
||||
|
||||
///
|
||||
/// Registers new security context, which will be used by the HttpClient, see [RegisterModule].
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/interceptor/authentication.interceptor.dart';
|
||||
@@ -16,18 +17,33 @@ import 'package:local_auth/local_auth.dart';
|
||||
@module
|
||||
abstract class RegisterModule {
|
||||
@singleton
|
||||
@dev
|
||||
@prod
|
||||
LocalAuthentication get localAuthentication => LocalAuthentication();
|
||||
|
||||
@singleton
|
||||
@dev
|
||||
@prod
|
||||
EncryptedSharedPreferences get encryptedSharedPreferences =>
|
||||
EncryptedSharedPreferences();
|
||||
|
||||
@singleton
|
||||
@dev
|
||||
@prod
|
||||
@test
|
||||
SecurityContext get securityContext => SecurityContext();
|
||||
|
||||
@singleton
|
||||
@dev
|
||||
@prod
|
||||
Connectivity get connectivity => Connectivity();
|
||||
|
||||
///
|
||||
/// Factory method creating an [HttpClient] with the currently registered [SecurityContext].
|
||||
///
|
||||
@injectable
|
||||
@dev
|
||||
@prod
|
||||
HttpClient getHttpClient(SecurityContext securityContext) =>
|
||||
HttpClient(context: securityContext)
|
||||
..connectionTimeout = const Duration(seconds: 10);
|
||||
@@ -35,6 +51,9 @@ abstract class RegisterModule {
|
||||
///
|
||||
/// Factory method creating a [InterceptedClient] on top of the currently registered [HttpClient].
|
||||
///
|
||||
@injectable
|
||||
@dev
|
||||
@prod
|
||||
BaseClient getBaseClient(
|
||||
AuthenticationInterceptor authInterceptor,
|
||||
ResponseConversionInterceptor responseConversionInterceptor,
|
||||
@@ -50,28 +69,46 @@ abstract class RegisterModule {
|
||||
client: IOClient(client),
|
||||
);
|
||||
|
||||
@injectable
|
||||
@dev
|
||||
@prod
|
||||
CacheManager getCacheManager(BaseClient client) => CacheManager(
|
||||
Config('cacheKey', fileService: HttpFileService(httpClient: client)));
|
||||
|
||||
@injectable
|
||||
@dev
|
||||
@prod
|
||||
PaperlessAuthenticationApi authenticationModule(BaseClient client) =>
|
||||
PaperlessAuthenticationApiImpl(client);
|
||||
|
||||
@injectable
|
||||
@dev
|
||||
@prod
|
||||
PaperlessLabelsApi labelsModule(
|
||||
@Named('timeoutClient') BaseClient timeoutClient,
|
||||
) =>
|
||||
PaperlessLabelApiImpl(timeoutClient);
|
||||
|
||||
@injectable
|
||||
@dev
|
||||
@prod
|
||||
PaperlessDocumentsApi documentsModule(
|
||||
@Named('timeoutClient') BaseClient timeoutClient,
|
||||
HttpClient httpClient,
|
||||
) =>
|
||||
PaperlessDocumentsApiImpl(timeoutClient, httpClient);
|
||||
|
||||
@injectable
|
||||
@dev
|
||||
@prod
|
||||
PaperlessSavedViewsApi savedViewsModule(
|
||||
@Named('timeoutClient') BaseClient timeoutClient,
|
||||
) =>
|
||||
PaperlessSavedViewsApiImpl(timeoutClient);
|
||||
|
||||
@injectable
|
||||
@dev
|
||||
@prod
|
||||
PaperlessServerStatsApi serverStatsModule(
|
||||
@Named('timeoutClient') BaseClient timeoutClient,
|
||||
) =>
|
||||
|
||||
69
lib/di_test_mocks.dart
Normal file
69
lib/di_test_mocks.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
|
||||
@GenerateNiceMocks([
|
||||
MockSpec<PaperlessDocumentsApi>(),
|
||||
MockSpec<PaperlessLabelsApi>(),
|
||||
MockSpec<PaperlessSavedViewsApi>(),
|
||||
MockSpec<PaperlessAuthenticationApi>(),
|
||||
MockSpec<PaperlessServerStatsApi>(),
|
||||
MockSpec<LocalVault>(),
|
||||
MockSpec<EncryptedSharedPreferences>(),
|
||||
MockSpec<ConnectivityStatusService>(),
|
||||
MockSpec<LocalAuthentication>(),
|
||||
])
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status.service.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
import 'di_test_mocks.mocks.dart';
|
||||
|
||||
@module
|
||||
abstract class DiMocksModule {
|
||||
// All fields must be singleton in order to verify behavior in tests.
|
||||
@singleton
|
||||
@test
|
||||
CacheManager get testCacheManager => CacheManager(Config('testKey'));
|
||||
|
||||
@singleton
|
||||
@test
|
||||
PaperlessDocumentsApi get mockDocumentsApi => MockPaperlessDocumentsApi();
|
||||
|
||||
@singleton
|
||||
@test
|
||||
PaperlessLabelsApi get mockLabelsApi => MockPaperlessLabelsApi();
|
||||
|
||||
@singleton
|
||||
@test
|
||||
PaperlessSavedViewsApi get mockSavedViewsApi => MockPaperlessSavedViewsApi();
|
||||
|
||||
@singleton
|
||||
@test
|
||||
PaperlessAuthenticationApi get mockAuthenticationApi =>
|
||||
MockPaperlessAuthenticationApi();
|
||||
|
||||
@singleton
|
||||
@test
|
||||
PaperlessServerStatsApi get mockServerStatsApi =>
|
||||
MockPaperlessServerStatsApi();
|
||||
|
||||
@singleton
|
||||
@test
|
||||
LocalVault get mockLocalVault => MockLocalVault();
|
||||
|
||||
@singleton
|
||||
@test
|
||||
EncryptedSharedPreferences get mockSharedPreferences =>
|
||||
MockEncryptedSharedPreferences();
|
||||
|
||||
@singleton
|
||||
@test
|
||||
ConnectivityStatusService get mockConnectivityStatusService =>
|
||||
MockConnectivityStatusService();
|
||||
|
||||
@singleton
|
||||
@test
|
||||
LocalAuthentication get localAuthentication => MockLocalAuthentication();
|
||||
}
|
||||
@@ -72,6 +72,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
Widget _buildLoginButton() {
|
||||
return ElevatedButton(
|
||||
key: const ValueKey('login-login-button'),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStatePropertyAll(
|
||||
Theme.of(context).colorScheme.primaryContainer,
|
||||
|
||||
@@ -23,6 +23,7 @@ class _ClientCertificateFormFieldState
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FormBuilderField<ClientCertificate?>(
|
||||
key: const ValueKey('login-client-cert'),
|
||||
initialValue: null,
|
||||
validator: (value) {
|
||||
if (value == null) {
|
||||
@@ -70,6 +71,7 @@ class _ClientCertificateFormFieldState
|
||||
),
|
||||
if (_selectedFile != null) ...[
|
||||
ObscuredInputTextFormField(
|
||||
key: const ValueKey('login-client-cert-passphrase'),
|
||||
initialValue: field.value?.passphrase,
|
||||
onChanged: (value) => field.didChange(
|
||||
field.value?.copyWith(passphrase: value),
|
||||
|
||||
@@ -21,6 +21,7 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FormBuilderTextField(
|
||||
key: const ValueKey('login-server-address'),
|
||||
name: ServerAddressFormField.fkServerAddress,
|
||||
validator: FormBuilderValidators.required(
|
||||
errorText: S.of(context).loginPageServerUrlValidatorMessageText,
|
||||
|
||||
@@ -24,6 +24,7 @@ class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||
child: Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
key: const ValueKey('login-username'),
|
||||
textCapitalization: TextCapitalization.words,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
// USERNAME
|
||||
@@ -41,6 +42,7 @@ class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||
),
|
||||
),
|
||||
ObscuredInputTextFormField(
|
||||
key: const ValueKey('login-password'),
|
||||
label: S.of(context).loginPagePasswordFieldLabel,
|
||||
onChanged: (password) => field.didChange(
|
||||
field.value?.copyWith(password: password) ??
|
||||
|
||||
@@ -33,7 +33,7 @@ import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
|
||||
void main() async {
|
||||
Future<void> startAppProd() async {
|
||||
Bloc.observer = BlocChangesObserver();
|
||||
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
|
||||
@@ -42,25 +42,22 @@ void main() async {
|
||||
// Required for self signed client certificates
|
||||
HttpOverrides.global = X509HttpOverrides();
|
||||
|
||||
configureDependencies();
|
||||
configureDependencies('prod');
|
||||
// Remove temporarily downloaded files.
|
||||
(await FileService.temporaryDirectory).deleteSync(recursive: true);
|
||||
kPackageInfo = await PackageInfo.fromPlatform();
|
||||
// Load application settings and stored authentication data
|
||||
getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ConnectivityCubit>().initialize();
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
await getIt<AuthenticationCubit>().initialize();
|
||||
|
||||
// Preload asset images
|
||||
// WARNING: This seems to bloat up the app up to almost 200mb!
|
||||
// await Future.forEach<AssetImage>(
|
||||
// AssetImages.values.map((e) => e.image),
|
||||
// (img) => loadImage(img),
|
||||
// );
|
||||
|
||||
runApp(const PaperlessMobileEntrypoint());
|
||||
}
|
||||
|
||||
void main() async {
|
||||
await startAppProd();
|
||||
}
|
||||
|
||||
class PaperlessMobileEntrypoint extends StatefulWidget {
|
||||
const PaperlessMobileEntrypoint({Key? key}) : super(key: key);
|
||||
|
||||
@@ -114,11 +111,7 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
),
|
||||
),
|
||||
themeMode: settings.preferredThemeMode,
|
||||
supportedLocales: const [
|
||||
Locale('en'), // Default if system locale is not available
|
||||
Locale('de'),
|
||||
Locale('cs'),
|
||||
],
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
locale: Locale.fromSubtags(
|
||||
languageCode: settings.preferredLocaleSubtag),
|
||||
localizationsDelegates: const [
|
||||
|
||||
@@ -610,7 +610,7 @@ packages:
|
||||
name: form_builder_validators
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "8.3.0"
|
||||
version: "8.4.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -62,7 +62,7 @@ dependencies:
|
||||
git:
|
||||
url: https://github.com/flutter-form-builder-ecosystem/form_builder_extra_fields.git
|
||||
ref: main
|
||||
form_builder_validators: ^8.3.0
|
||||
form_builder_validators: ^8.4.0
|
||||
infinite_scroll_pagination: ^3.2.0
|
||||
sliding_up_panel: ^2.0.0+1
|
||||
package_info_plus: ^1.4.3+1
|
||||
|
||||
Reference in New Issue
Block a user