Browse Source
This alligns the code with our documentations. Signed-off-by: Nikolas Rimikis <leptopoda@users.noreply.github.com>pull/1037/head
69 changed files with 646 additions and 641 deletions
@ -1,5 +1,5 @@ |
|||||||
export 'package:neon/src/bloc/bloc.dart'; |
export 'package:neon/src/bloc/bloc.dart'; |
||||||
export 'package:neon/src/bloc/result.dart'; |
export 'package:neon/src/bloc/result.dart'; |
||||||
// TODO: Remove access to the AccountsBloc. Apps should not need to access this |
// TODO: Remove access to the AccountsBloc. Clients should not need to access this |
||||||
export 'package:neon/src/blocs/accounts.dart' show AccountsBloc; |
export 'package:neon/src/blocs/accounts.dart' show AccountsBloc; |
||||||
export 'package:neon/src/blocs/timer.dart' hide TimerBlocEvents, TimerBlocStates; |
export 'package:neon/src/blocs/timer.dart' hide TimerBlocEvents, TimerBlocStates; |
||||||
|
@ -1,3 +1,3 @@ |
|||||||
export 'package:neon/src/models/account.dart' hide Credentials, LoginQRcode; |
export 'package:neon/src/models/account.dart' hide Credentials, LoginQRcode; |
||||||
export 'package:neon/src/models/app_implementation.dart'; |
export 'package:neon/src/models/client_implementation.dart'; |
||||||
export 'package:neon/src/models/notifications_interface.dart'; |
export 'package:neon/src/models/notifications_interface.dart'; |
||||||
|
@ -1,232 +0,0 @@ |
|||||||
import 'dart:async'; |
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart'; |
|
||||||
import 'package:meta/meta.dart'; |
|
||||||
import 'package:neon/src/bloc/bloc.dart'; |
|
||||||
import 'package:neon/src/bloc/result.dart'; |
|
||||||
import 'package:neon/src/blocs/accounts.dart'; |
|
||||||
import 'package:neon/src/blocs/capabilities.dart'; |
|
||||||
import 'package:neon/src/models/account.dart'; |
|
||||||
import 'package:neon/src/models/app_implementation.dart'; |
|
||||||
import 'package:neon/src/models/notifications_interface.dart'; |
|
||||||
import 'package:neon/src/settings/models/options_collection.dart'; |
|
||||||
import 'package:neon/src/utils/request_manager.dart'; |
|
||||||
import 'package:nextcloud/core.dart' as core; |
|
||||||
import 'package:nextcloud/nextcloud.dart'; |
|
||||||
import 'package:provider/provider.dart'; |
|
||||||
import 'package:rxdart/rxdart.dart'; |
|
||||||
|
|
||||||
@internal |
|
||||||
abstract interface class AppsBlocEvents { |
|
||||||
/// Sets the active app using the [appID]. |
|
||||||
/// |
|
||||||
/// If the app is already the active app nothing will happen. |
|
||||||
/// When using [skipAlreadySet] nothing will be done if there already is an active app. |
|
||||||
void setActiveApp(final String appID, {final bool skipAlreadySet = false}); |
|
||||||
} |
|
||||||
|
|
||||||
@internal |
|
||||||
abstract interface class AppsBlocStates { |
|
||||||
BehaviorSubject<Result<Iterable<AppImplementation>>> get appImplementations; |
|
||||||
|
|
||||||
BehaviorSubject<Result<NotificationsAppInterface?>> get notificationsAppImplementation; |
|
||||||
|
|
||||||
BehaviorSubject<AppImplementation> get activeApp; |
|
||||||
|
|
||||||
BehaviorSubject<void> get openNotifications; |
|
||||||
|
|
||||||
BehaviorSubject<Map<String, String?>> get appVersions; |
|
||||||
} |
|
||||||
|
|
||||||
@internal |
|
||||||
class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates { |
|
||||||
AppsBloc( |
|
||||||
this._capabilitiesBloc, |
|
||||||
this._accountsBloc, |
|
||||||
this._account, |
|
||||||
this._allAppImplementations, |
|
||||||
) { |
|
||||||
_apps.listen((final result) { |
|
||||||
appImplementations |
|
||||||
.add(result.transform((final data) => _filteredAppImplementations(data.map((final a) => a.id)))); |
|
||||||
}); |
|
||||||
|
|
||||||
appImplementations.listen((final result) async { |
|
||||||
if (!result.hasData) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// dispose unsupported apps |
|
||||||
for (final app in _allAppImplementations) { |
|
||||||
if (result.requireData.tryFind(app.id) == null) { |
|
||||||
app.blocsCache.remove(_account); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
final options = _accountsBloc.getOptionsFor(_account); |
|
||||||
final initialApp = options.initialApp.value ?? _getInitialAppFallback(); |
|
||||||
if (initialApp != null) { |
|
||||||
await setActiveApp(initialApp, skipAlreadySet: true); |
|
||||||
} |
|
||||||
|
|
||||||
unawaited(_checkCompatibility()); |
|
||||||
}); |
|
||||||
|
|
||||||
_capabilitiesBloc.capabilities.listen((final result) { |
|
||||||
notificationsAppImplementation.add( |
|
||||||
result.transform( |
|
||||||
(final data) => data.capabilities.notificationsCapabilities?.notifications != null |
|
||||||
? _findAppImplementation(AppIDs.notifications) |
|
||||||
: null, |
|
||||||
), |
|
||||||
); |
|
||||||
|
|
||||||
unawaited(_checkCompatibility()); |
|
||||||
}); |
|
||||||
|
|
||||||
unawaited(refresh()); |
|
||||||
} |
|
||||||
|
|
||||||
/// Determines the appid of initial app. |
|
||||||
/// |
|
||||||
/// It requires [appImplementations] to have both a value and data. |
|
||||||
/// |
|
||||||
/// The files app is always installed and can not be removed so it will be used, but in the |
|
||||||
/// case this changes at a later point the first supported app will be returned. |
|
||||||
/// |
|
||||||
/// Returns null when no app is supported by the server. |
|
||||||
String? _getInitialAppFallback() { |
|
||||||
final supportedApps = appImplementations.value.requireData; |
|
||||||
|
|
||||||
for (final fallback in {AppIDs.dashboard, AppIDs.files}) { |
|
||||||
if (supportedApps.tryFind(fallback) != null) { |
|
||||||
return fallback; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (supportedApps.isNotEmpty) { |
|
||||||
return supportedApps.first.id; |
|
||||||
} |
|
||||||
|
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> _checkCompatibility() async { |
|
||||||
final apps = appImplementations.valueOrNull; |
|
||||||
final capabilities = _capabilitiesBloc.capabilities.valueOrNull; |
|
||||||
|
|
||||||
// ignore cached data |
|
||||||
if (capabilities == null || apps == null || !capabilities.hasUncachedData || !apps.hasUncachedData) { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
final notSupported = <String, String?>{}; |
|
||||||
|
|
||||||
try { |
|
||||||
final (coreSupported, coreMinimumVersion) = _account.client.core.isSupported(capabilities.requireData); |
|
||||||
if (!coreSupported) { |
|
||||||
notSupported['core'] = coreMinimumVersion.toString(); |
|
||||||
} |
|
||||||
} catch (e, s) { |
|
||||||
debugPrint(e.toString()); |
|
||||||
debugPrint(s.toString()); |
|
||||||
} |
|
||||||
|
|
||||||
for (final app in apps.requireData) { |
|
||||||
try { |
|
||||||
final (supported, minimumVersion) = await app.isSupported(_account, capabilities.requireData); |
|
||||||
if (!(supported ?? true)) { |
|
||||||
notSupported[app.id] = minimumVersion; |
|
||||||
} |
|
||||||
} catch (e, s) { |
|
||||||
debugPrint(e.toString()); |
|
||||||
debugPrint(s.toString()); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (notSupported.isNotEmpty) { |
|
||||||
appVersions.add(notSupported); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
T? _findAppImplementation<T extends AppImplementation>(final String id) { |
|
||||||
final matches = _filteredAppImplementations([id]); |
|
||||||
if (matches.isNotEmpty) { |
|
||||||
return matches.single as T; |
|
||||||
} |
|
||||||
|
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
Iterable<AppImplementation> _filteredAppImplementations(final Iterable<String> appIds) => |
|
||||||
_allAppImplementations.where((final a) => appIds.contains(a.id)); |
|
||||||
|
|
||||||
final CapabilitiesBloc _capabilitiesBloc; |
|
||||||
final AccountsBloc _accountsBloc; |
|
||||||
final Account _account; |
|
||||||
final Iterable<AppImplementation> _allAppImplementations; |
|
||||||
final _apps = BehaviorSubject<Result<List<core.NavigationEntry>>>(); |
|
||||||
|
|
||||||
@override |
|
||||||
void dispose() { |
|
||||||
unawaited(_apps.close()); |
|
||||||
unawaited(appImplementations.close()); |
|
||||||
unawaited(notificationsAppImplementation.close()); |
|
||||||
unawaited(activeApp.close()); |
|
||||||
unawaited(openNotifications.close()); |
|
||||||
unawaited(appVersions.close()); |
|
||||||
|
|
||||||
super.dispose(); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
BehaviorSubject<AppImplementation> activeApp = BehaviorSubject(); |
|
||||||
|
|
||||||
@override |
|
||||||
BehaviorSubject<Result<Iterable<AppImplementation<Bloc, NextcloudAppOptions>>>> appImplementations = |
|
||||||
BehaviorSubject(); |
|
||||||
|
|
||||||
@override |
|
||||||
BehaviorSubject<Result<NotificationsAppInterface?>> notificationsAppImplementation = BehaviorSubject(); |
|
||||||
|
|
||||||
@override |
|
||||||
BehaviorSubject<void> openNotifications = BehaviorSubject(); |
|
||||||
|
|
||||||
@override |
|
||||||
BehaviorSubject<Map<String, String?>> appVersions = BehaviorSubject(); |
|
||||||
|
|
||||||
@override |
|
||||||
Future<void> refresh() async { |
|
||||||
await RequestManager.instance.wrapNextcloud( |
|
||||||
_account.id, |
|
||||||
'apps-apps', |
|
||||||
_apps, |
|
||||||
_account.client.core.navigation.getAppsNavigationRaw(), |
|
||||||
(final response) => response.body.ocs.data.toList(), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Future<void> setActiveApp(final String appID, {final bool skipAlreadySet = false}) async { |
|
||||||
if (appID == AppIDs.notifications) { |
|
||||||
openNotifications.add(null); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
final apps = await appImplementations.firstWhere((final a) => a.hasData); |
|
||||||
final app = apps.requireData.tryFind(appID); |
|
||||||
if (app != null) { |
|
||||||
if ((!activeApp.hasValue || !skipAlreadySet) && activeApp.valueOrNull?.id != appID) { |
|
||||||
activeApp.add(app); |
|
||||||
} |
|
||||||
} else { |
|
||||||
throw Exception('App $appID not found'); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
T getAppBloc<T extends Bloc>(final AppImplementation<T, dynamic> appImplementation) => |
|
||||||
appImplementation.getBloc(_account); |
|
||||||
|
|
||||||
List<Provider<Bloc>> get appBlocProviders => |
|
||||||
_allAppImplementations.map((final appImplementation) => appImplementation.blocProvider).toList(); |
|
||||||
} |
|
@ -0,0 +1,232 @@ |
|||||||
|
import 'dart:async'; |
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart'; |
||||||
|
import 'package:meta/meta.dart'; |
||||||
|
import 'package:neon/src/bloc/bloc.dart'; |
||||||
|
import 'package:neon/src/bloc/result.dart'; |
||||||
|
import 'package:neon/src/blocs/accounts.dart'; |
||||||
|
import 'package:neon/src/blocs/capabilities.dart'; |
||||||
|
import 'package:neon/src/models/account.dart'; |
||||||
|
import 'package:neon/src/models/client_implementation.dart'; |
||||||
|
import 'package:neon/src/models/notifications_interface.dart'; |
||||||
|
import 'package:neon/src/settings/models/options_collection.dart'; |
||||||
|
import 'package:neon/src/utils/request_manager.dart'; |
||||||
|
import 'package:nextcloud/core.dart' as core; |
||||||
|
import 'package:nextcloud/nextcloud.dart'; |
||||||
|
import 'package:provider/provider.dart'; |
||||||
|
import 'package:rxdart/rxdart.dart'; |
||||||
|
|
||||||
|
@internal |
||||||
|
abstract interface class ClientsBlocEvents { |
||||||
|
/// Sets the active client using the [clientID]. |
||||||
|
/// |
||||||
|
/// If the client is already the active client nothing will happen. |
||||||
|
/// When using [skipAlreadySet] nothing will be done if there already is an active client. |
||||||
|
void setActiveClient(final String clientID, {final bool skipAlreadySet = false}); |
||||||
|
} |
||||||
|
|
||||||
|
@internal |
||||||
|
abstract interface class ClientsBlocStates { |
||||||
|
BehaviorSubject<Result<Iterable<ClientImplementation>>> get clientImplementations; |
||||||
|
|
||||||
|
BehaviorSubject<Result<NotificationsClientInterface?>> get notificationsClientImplementation; |
||||||
|
|
||||||
|
BehaviorSubject<ClientImplementation> get activeClient; |
||||||
|
|
||||||
|
BehaviorSubject<void> get openNotifications; |
||||||
|
|
||||||
|
BehaviorSubject<Map<String, String?>> get clientVersions; |
||||||
|
} |
||||||
|
|
||||||
|
@internal |
||||||
|
class ClientsBloc extends InteractiveBloc implements ClientsBlocEvents, ClientsBlocStates { |
||||||
|
ClientsBloc( |
||||||
|
this._capabilitiesBloc, |
||||||
|
this._accountsBloc, |
||||||
|
this._account, |
||||||
|
this._allClientImplementations, |
||||||
|
) { |
||||||
|
_clients.listen((final result) { |
||||||
|
clientImplementations |
||||||
|
.add(result.transform((final data) => _filteredClientImplementations(data.map((final c) => c.id)))); |
||||||
|
}); |
||||||
|
|
||||||
|
clientImplementations.listen((final result) async { |
||||||
|
if (!result.hasData) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// dispose unsupported clients |
||||||
|
for (final client in _allClientImplementations) { |
||||||
|
if (result.requireData.tryFind(client.id) == null) { |
||||||
|
client.blocsCache.remove(_account); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
final options = _accountsBloc.getOptionsFor(_account); |
||||||
|
final initialClient = options.initialClient.value ?? _getInitialClientFallback(); |
||||||
|
if (initialClient != null) { |
||||||
|
await setActiveClient(initialClient, skipAlreadySet: true); |
||||||
|
} |
||||||
|
|
||||||
|
unawaited(_checkCompatibility()); |
||||||
|
}); |
||||||
|
|
||||||
|
_capabilitiesBloc.capabilities.listen((final result) { |
||||||
|
notificationsClientImplementation.add( |
||||||
|
result.transform( |
||||||
|
(final data) => data.capabilities.notificationsCapabilities?.notifications != null |
||||||
|
? _findClientImplementation(AppIDs.notifications) |
||||||
|
: null, |
||||||
|
), |
||||||
|
); |
||||||
|
|
||||||
|
unawaited(_checkCompatibility()); |
||||||
|
}); |
||||||
|
|
||||||
|
unawaited(refresh()); |
||||||
|
} |
||||||
|
|
||||||
|
/// Determines the clientid of initial client. |
||||||
|
/// |
||||||
|
/// It requires [clientImplementations] to have both a value and data. |
||||||
|
/// |
||||||
|
/// The files client is always installed and can not be removed so it will be used, but in the |
||||||
|
/// case this changes at a later point the first supported client will be returned. |
||||||
|
/// |
||||||
|
/// Returns null when no client is supported by the server. |
||||||
|
String? _getInitialClientFallback() { |
||||||
|
final supportedClients = clientImplementations.value.requireData; |
||||||
|
|
||||||
|
for (final fallback in {AppIDs.dashboard, AppIDs.files}) { |
||||||
|
if (supportedClients.tryFind(fallback) != null) { |
||||||
|
return fallback; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (supportedClients.isNotEmpty) { |
||||||
|
return supportedClients.first.id; |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
Future<void> _checkCompatibility() async { |
||||||
|
final clients = clientImplementations.valueOrNull; |
||||||
|
final capabilities = _capabilitiesBloc.capabilities.valueOrNull; |
||||||
|
|
||||||
|
// ignore cached data |
||||||
|
if (capabilities == null || clients == null || !capabilities.hasUncachedData || !clients.hasUncachedData) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
final notSupported = <String, String?>{}; |
||||||
|
|
||||||
|
try { |
||||||
|
final (coreSupported, coreMinimumVersion) = _account.client.core.isSupported(capabilities.requireData); |
||||||
|
if (!coreSupported) { |
||||||
|
notSupported['core'] = coreMinimumVersion.toString(); |
||||||
|
} |
||||||
|
} catch (e, s) { |
||||||
|
debugPrint(e.toString()); |
||||||
|
debugPrint(s.toString()); |
||||||
|
} |
||||||
|
|
||||||
|
for (final client in clients.requireData) { |
||||||
|
try { |
||||||
|
final (supported, minimumVersion) = await client.isSupported(_account, capabilities.requireData); |
||||||
|
if (!(supported ?? true)) { |
||||||
|
notSupported[client.id] = minimumVersion; |
||||||
|
} |
||||||
|
} catch (e, s) { |
||||||
|
debugPrint(e.toString()); |
||||||
|
debugPrint(s.toString()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (notSupported.isNotEmpty) { |
||||||
|
clientVersions.add(notSupported); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
T? _findClientImplementation<T extends ClientImplementation>(final String id) { |
||||||
|
final matches = _filteredClientImplementations([id]); |
||||||
|
if (matches.isNotEmpty) { |
||||||
|
return matches.single as T; |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
Iterable<ClientImplementation> _filteredClientImplementations(final Iterable<String> clientIds) => |
||||||
|
_allClientImplementations.where((final c) => clientIds.contains(c.id)); |
||||||
|
|
||||||
|
final CapabilitiesBloc _capabilitiesBloc; |
||||||
|
final AccountsBloc _accountsBloc; |
||||||
|
final Account _account; |
||||||
|
final Iterable<ClientImplementation> _allClientImplementations; |
||||||
|
final _clients = BehaviorSubject<Result<List<core.NavigationEntry>>>(); |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
unawaited(_clients.close()); |
||||||
|
unawaited(clientImplementations.close()); |
||||||
|
unawaited(notificationsClientImplementation.close()); |
||||||
|
unawaited(activeClient.close()); |
||||||
|
unawaited(openNotifications.close()); |
||||||
|
unawaited(clientVersions.close()); |
||||||
|
|
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
BehaviorSubject<ClientImplementation> activeClient = BehaviorSubject(); |
||||||
|
|
||||||
|
@override |
||||||
|
BehaviorSubject<Result<Iterable<ClientImplementation<Bloc, NextcloudClientOptions>>>> clientImplementations = |
||||||
|
BehaviorSubject(); |
||||||
|
|
||||||
|
@override |
||||||
|
BehaviorSubject<Result<NotificationsClientInterface?>> notificationsClientImplementation = BehaviorSubject(); |
||||||
|
|
||||||
|
@override |
||||||
|
BehaviorSubject<void> openNotifications = BehaviorSubject(); |
||||||
|
|
||||||
|
@override |
||||||
|
BehaviorSubject<Map<String, String?>> clientVersions = BehaviorSubject(); |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> refresh() async { |
||||||
|
await RequestManager.instance.wrapNextcloud( |
||||||
|
_account.id, |
||||||
|
'apps-apps', |
||||||
|
_clients, |
||||||
|
_account.client.core.navigation.getAppsNavigationRaw(), |
||||||
|
(final response) => response.body.ocs.data.toList(), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Future<void> setActiveClient(final String clientID, {final bool skipAlreadySet = false}) async { |
||||||
|
if (clientID == AppIDs.notifications) { |
||||||
|
openNotifications.add(null); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
final clients = await clientImplementations.firstWhere((final c) => c.hasData); |
||||||
|
final client = clients.requireData.tryFind(clientID); |
||||||
|
if (client != null) { |
||||||
|
if ((!activeClient.hasValue || !skipAlreadySet) && activeClient.valueOrNull?.id != clientID) { |
||||||
|
activeClient.add(client); |
||||||
|
} |
||||||
|
} else { |
||||||
|
throw Exception('Client $clientID not found'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
T getClientBloc<T extends Bloc>(final ClientImplementation<T, dynamic> clientImplementation) => |
||||||
|
clientImplementation.getBloc(_account); |
||||||
|
|
||||||
|
List<Provider<Bloc>> get clientBlocProviders => |
||||||
|
_allClientImplementations.map((final clientImplementation) => clientImplementation.blocProvider).toList(); |
||||||
|
} |
@ -1,30 +0,0 @@ |
|||||||
import 'package:mocktail/mocktail.dart'; |
|
||||||
import 'package:neon/src/models/app_implementation.dart'; |
|
||||||
import 'package:test/test.dart'; |
|
||||||
|
|
||||||
// ignore: missing_override_of_must_be_overridden, avoid_implementing_value_types |
|
||||||
class AppImplementationMock extends Mock implements AppImplementation {} |
|
||||||
|
|
||||||
void main() { |
|
||||||
group('group name', () { |
|
||||||
test('AccountFind', () { |
|
||||||
final app1 = AppImplementationMock(); |
|
||||||
final app2 = AppImplementationMock(); |
|
||||||
|
|
||||||
final apps = { |
|
||||||
app1, |
|
||||||
app2, |
|
||||||
}; |
|
||||||
|
|
||||||
when(() => app1.id).thenReturn('app1'); |
|
||||||
when(() => app2.id).thenReturn('app2'); |
|
||||||
|
|
||||||
expect(apps.tryFind(null), isNull); |
|
||||||
expect(apps.tryFind('invalidID'), isNull); |
|
||||||
expect(apps.tryFind(app2.id), equals(app2)); |
|
||||||
|
|
||||||
expect(() => apps.find('invalidID'), throwsA(isA<StateError>())); |
|
||||||
expect(apps.find(app2.id), equals(app2)); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
@ -0,0 +1,30 @@ |
|||||||
|
import 'package:mocktail/mocktail.dart'; |
||||||
|
import 'package:neon/src/models/client_implementation.dart'; |
||||||
|
import 'package:test/test.dart'; |
||||||
|
|
||||||
|
// ignore: missing_override_of_must_be_overridden, avoid_implementing_value_types |
||||||
|
class ClientImplementationMock extends Mock implements ClientImplementation {} |
||||||
|
|
||||||
|
void main() { |
||||||
|
group('group name', () { |
||||||
|
test('AccountFind', () { |
||||||
|
final client1 = ClientImplementationMock(); |
||||||
|
final client2 = ClientImplementationMock(); |
||||||
|
|
||||||
|
final apps = { |
||||||
|
client1, |
||||||
|
client2, |
||||||
|
}; |
||||||
|
|
||||||
|
when(() => client1.id).thenReturn('app1'); |
||||||
|
when(() => client2.id).thenReturn('app2'); |
||||||
|
|
||||||
|
expect(apps.tryFind(null), isNull); |
||||||
|
expect(apps.tryFind('invalidID'), isNull); |
||||||
|
expect(apps.tryFind(client2.id), equals(client2)); |
||||||
|
|
||||||
|
expect(() => apps.find('invalidID'), throwsA(isA<StateError>())); |
||||||
|
expect(apps.find(client2.id), equals(client2)); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
Loading…
Reference in new issue