fix: stabilize auth and home error flows
This commit is contained in:
@@ -55,6 +55,27 @@ class CurrentHomeNotifier extends Notifier<HomeConfig?> {
|
|||||||
await _initApi(home);
|
await _initApi(home);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Выбрать дом как активный и сразу проверить auth-state.
|
||||||
|
/// Если `auth/me` падает, откатываемся к предыдущему дому и auth-state.
|
||||||
|
Future<void> select(HomeConfig home) async {
|
||||||
|
final previousHome = state;
|
||||||
|
final previousAuthState = ref.read(authInfoProvider);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await switchTo(home);
|
||||||
|
await ref.read(authInfoProvider.notifier).load(failOnError: true);
|
||||||
|
} catch (error) {
|
||||||
|
await _restoreSelection(previousHome);
|
||||||
|
ref.read(authInfoProvider.notifier).restore(previousAuthState);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> clear() async {
|
||||||
|
await ref.read(settingsServiceProvider).setCurrentHomeId(null);
|
||||||
|
state = null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Инициализировать API-клиент текущим домом
|
/// Инициализировать API-клиент текущим домом
|
||||||
Future<void> _initApi(HomeConfig home) async {
|
Future<void> _initApi(HomeConfig home) async {
|
||||||
final apiKey = await ref
|
final apiKey = await ref
|
||||||
@@ -62,6 +83,18 @@ class CurrentHomeNotifier extends Notifier<HomeConfig?> {
|
|||||||
.requireHomeApiKey(home.id);
|
.requireHomeApiKey(home.id);
|
||||||
ref.read(apiProvider).init(home.url, apiKey);
|
ref.read(apiProvider).init(home.url, apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _restoreSelection(HomeConfig? home) async {
|
||||||
|
if (home == null) {
|
||||||
|
await clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final svc = ref.read(settingsServiceProvider);
|
||||||
|
await svc.setCurrentHomeId(home.id);
|
||||||
|
state = home;
|
||||||
|
await _initApi(home);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Список домов ────────────────────────────────────────────
|
// ─── Список домов ────────────────────────────────────────────
|
||||||
@@ -798,26 +831,35 @@ class ApiKeysNotifier extends Notifier<LoadState<List<ApiKeyInfo>>> {
|
|||||||
|
|
||||||
// ─── Информация об авторизации ────────────────────────────────
|
// ─── Информация об авторизации ────────────────────────────────
|
||||||
|
|
||||||
final authInfoProvider = NotifierProvider<AuthInfoNotifier, AuthInfo?>(
|
final authInfoProvider =
|
||||||
|
NotifierProvider<AuthInfoNotifier, LoadState<AuthInfo?>>(
|
||||||
() => AuthInfoNotifier(),
|
() => AuthInfoNotifier(),
|
||||||
);
|
);
|
||||||
|
|
||||||
class AuthInfoNotifier extends Notifier<AuthInfo?> {
|
class AuthInfoNotifier extends Notifier<LoadState<AuthInfo?>> {
|
||||||
@override
|
@override
|
||||||
AuthInfo? build() => null;
|
LoadState<AuthInfo?> build() => const LoadState.idle(null);
|
||||||
|
|
||||||
Future<void> load({bool failOnError = false}) async {
|
Future<AuthInfo?> load({bool failOnError = false}) async {
|
||||||
|
state = LoadState.loading(state.data);
|
||||||
try {
|
try {
|
||||||
final api = ref.read(apiProvider);
|
final api = ref.read(apiProvider);
|
||||||
final res = await api.getAuthMe();
|
final res = await api.getAuthMe();
|
||||||
state = AuthInfo.fromApi(res.data);
|
final authInfo = AuthInfo.fromApi(res.data);
|
||||||
|
state = LoadState.data(authInfo);
|
||||||
|
return authInfo;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Ошибка загрузки auth/me: $e");
|
state = LoadState.error(null, describeLoadError(e));
|
||||||
if (failOnError) rethrow;
|
if (failOnError) rethrow;
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isAdmin => state?.isAdmin == true;
|
void clear() => state = const LoadState.idle(null);
|
||||||
|
|
||||||
|
void restore(LoadState<AuthInfo?> restoredState) => state = restoredState;
|
||||||
|
|
||||||
|
bool get isAdmin => state.data?.isAdmin == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Геофенс: управление фоновым таском ─────────────────────
|
// ─── Геофенс: управление фоновым таском ─────────────────────
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import '../app/error_message.dart';
|
||||||
import '../models/home_config.dart';
|
import '../models/home_config.dart';
|
||||||
import '../providers/providers.dart';
|
import '../providers/providers.dart';
|
||||||
import '../services/api_client.dart';
|
import '../services/api_client.dart';
|
||||||
@@ -321,7 +322,7 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
|
|||||||
|
|
||||||
final currentHome = ref.read(currentHomeProvider);
|
final currentHome = ref.read(currentHomeProvider);
|
||||||
if (currentHome?.id == home.id) {
|
if (currentHome?.id == home.id) {
|
||||||
await ref.read(currentHomeProvider.notifier).switchTo(home);
|
await ref.read(currentHomeProvider.notifier).select(home);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Синхронизировать фоновый таск с новыми настройками
|
// Синхронизировать фоновый таск с новыми настройками
|
||||||
@@ -331,9 +332,13 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
|
|||||||
if (mounted) Navigator.of(context).pop();
|
if (mounted) Navigator.of(context).pop();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
context,
|
SnackBar(
|
||||||
).showSnackBar(SnackBar(content: Text('Не удалось проверить дом: $e')));
|
content: Text(
|
||||||
|
'Не удалось сохранить дом: ${describeLoadError(e)}',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _saving = false);
|
if (mounted) setState(() => _saving = false);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import '../app/error_message.dart';
|
||||||
import '../models/home_config.dart';
|
import '../models/home_config.dart';
|
||||||
import '../providers/providers.dart';
|
import '../providers/providers.dart';
|
||||||
import '../widgets/build_info_text.dart';
|
import '../widgets/build_info_text.dart';
|
||||||
@@ -17,6 +18,8 @@ class HomesScreen extends ConsumerStatefulWidget {
|
|||||||
|
|
||||||
class _HomesScreenState extends ConsumerState<HomesScreen> {
|
class _HomesScreenState extends ConsumerState<HomesScreen> {
|
||||||
late final UserLocationNotifier _userLocationNotifier;
|
late final UserLocationNotifier _userLocationNotifier;
|
||||||
|
String? _switchingHomeId;
|
||||||
|
String? _deletingHomeId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -53,6 +56,9 @@ class _HomesScreenState extends ConsumerState<HomesScreen> {
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final home = homes[index];
|
final home = homes[index];
|
||||||
final isActive = currentHome?.id == home.id;
|
final isActive = currentHome?.id == home.id;
|
||||||
|
final isSwitching = _switchingHomeId == home.id;
|
||||||
|
final isDeleting = _deletingHomeId == home.id;
|
||||||
|
final isBusy = isSwitching || isDeleting;
|
||||||
final distKm = location.distanceToKm(
|
final distKm = location.distanceToKm(
|
||||||
home.latitude,
|
home.latitude,
|
||||||
home.longitude,
|
home.longitude,
|
||||||
@@ -61,6 +67,7 @@ class _HomesScreenState extends ConsumerState<HomesScreen> {
|
|||||||
return Card(
|
return Card(
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
|
enabled: !isBusy,
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
Icons.home,
|
Icons.home,
|
||||||
color: isActive
|
color: isActive
|
||||||
@@ -87,6 +94,15 @@ class _HomesScreenState extends ConsumerState<HomesScreen> {
|
|||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
if (isBusy)
|
||||||
|
const SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else ...[
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.edit,
|
Icons.edit,
|
||||||
@@ -104,8 +120,9 @@ class _HomesScreenState extends ConsumerState<HomesScreen> {
|
|||||||
onPressed: () => _confirmDelete(context, home),
|
onPressed: () => _confirmDelete(context, home),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
],
|
||||||
),
|
),
|
||||||
onTap: () => _selectHome(context, home),
|
onTap: isBusy ? null : () => _selectHome(context, home),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -126,9 +143,12 @@ class _HomesScreenState extends ConsumerState<HomesScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _selectHome(BuildContext context, HomeConfig home) async {
|
void _selectHome(BuildContext context, HomeConfig home) async {
|
||||||
|
if (_switchingHomeId != null || _deletingHomeId != null) return;
|
||||||
|
|
||||||
|
setState(() => _switchingHomeId = home.id);
|
||||||
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
try {
|
try {
|
||||||
await ref.read(currentHomeProvider.notifier).switchTo(home);
|
await ref.read(currentHomeProvider.notifier).select(home);
|
||||||
await ref.read(authInfoProvider.notifier).load(failOnError: true);
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.of(context).pushReplacement(
|
Navigator.of(context).pushReplacement(
|
||||||
MaterialPageRoute(builder: (_) => const RemoteScreen()),
|
MaterialPageRoute(builder: (_) => const RemoteScreen()),
|
||||||
@@ -136,9 +156,17 @@ class _HomesScreenState extends ConsumerState<HomesScreen> {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
ScaffoldMessenger.of(
|
messenger.showSnackBar(
|
||||||
context,
|
SnackBar(
|
||||||
).showSnackBar(SnackBar(content: Text('Не удалось выбрать дом: $e')));
|
content: Text(
|
||||||
|
'Не удалось выбрать дом: ${describeLoadError(e)}',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _switchingHomeId = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,8 +197,7 @@ class _HomesScreenState extends ConsumerState<HomesScreen> {
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
Navigator.of(ctx).pop();
|
Navigator.of(ctx).pop();
|
||||||
await ref.read(homesProvider.notifier).remove(home.id);
|
await _deleteHome(context, home);
|
||||||
await syncGeofenceTask(ref.read(homesProvider));
|
|
||||||
},
|
},
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Удалить',
|
'Удалить',
|
||||||
@@ -181,6 +208,37 @@ class _HomesScreenState extends ConsumerState<HomesScreen> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _deleteHome(BuildContext context, HomeConfig home) async {
|
||||||
|
if (_switchingHomeId != null || _deletingHomeId != null) return;
|
||||||
|
|
||||||
|
final deletedCurrentHome = ref.read(currentHomeProvider)?.id == home.id;
|
||||||
|
setState(() => _deletingHomeId = home.id);
|
||||||
|
final messenger = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ref.read(homesProvider.notifier).remove(home.id);
|
||||||
|
await ref.read(currentHomeProvider.notifier).load();
|
||||||
|
if (deletedCurrentHome) {
|
||||||
|
ref.read(authInfoProvider.notifier).clear();
|
||||||
|
}
|
||||||
|
await syncGeofenceTask(ref.read(homesProvider));
|
||||||
|
} catch (e) {
|
||||||
|
if (context.mounted) {
|
||||||
|
messenger.showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
'Не удалось удалить дом: ${describeLoadError(e)}',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _deletingHomeId = null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EmptyHomesView extends StatelessWidget {
|
class _EmptyHomesView extends StatelessWidget {
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ class _RemoteScreenState extends ConsumerState<RemoteScreen> {
|
|||||||
final groups = ref.watch(groupsProvider);
|
final groups = ref.watch(groupsProvider);
|
||||||
final groupsLoadState = ref.watch(groupsLoadStateProvider);
|
final groupsLoadState = ref.watch(groupsLoadStateProvider);
|
||||||
final currentHome = ref.watch(currentHomeProvider);
|
final currentHome = ref.watch(currentHomeProvider);
|
||||||
final authInfo = ref.watch(authInfoProvider);
|
final authInfoState = ref.watch(authInfoProvider);
|
||||||
final isAdmin = authInfo?.isAdmin == true;
|
final isAdmin = authInfoState.data?.isAdmin == true;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
|||||||
@@ -2,9 +2,30 @@ import 'package:dio/dio.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:ignis_app/app/load_state.dart';
|
import 'package:ignis_app/app/load_state.dart';
|
||||||
|
import 'package:ignis_app/models/home_config.dart';
|
||||||
import 'package:ignis_app/models/ignis_group.dart';
|
import 'package:ignis_app/models/ignis_group.dart';
|
||||||
import 'package:ignis_app/providers/providers.dart';
|
import 'package:ignis_app/providers/providers.dart';
|
||||||
|
import 'package:ignis_app/services/credentials_storage.dart';
|
||||||
import 'package:ignis_app/services/api_client.dart';
|
import 'package:ignis_app/services/api_client.dart';
|
||||||
|
import 'package:ignis_app/services/settings_service.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class InMemoryCredentialsStorage implements CredentialsStorage {
|
||||||
|
final Map<String, String> _apiKeys = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> getApiKey(String homeId) async => _apiKeys[homeId];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setApiKey(String homeId, String apiKey) async {
|
||||||
|
_apiKeys[homeId] = apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> deleteApiKey(String homeId) async {
|
||||||
|
_apiKeys.remove(homeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class FakeIgnisApi extends IgnisApi {
|
class FakeIgnisApi extends IgnisApi {
|
||||||
Object? groupsData;
|
Object? groupsData;
|
||||||
@@ -183,9 +204,18 @@ class FakeIgnisApi extends IgnisApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
ProviderContainer containerWith(FakeIgnisApi api) {
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
ProviderContainer containerWith(
|
||||||
|
FakeIgnisApi api, {
|
||||||
|
SettingsService? settingsService,
|
||||||
|
}) {
|
||||||
|
final overrides = [apiProvider.overrideWithValue(api)];
|
||||||
|
if (settingsService != null) {
|
||||||
|
overrides.add(settingsServiceProvider.overrideWithValue(settingsService));
|
||||||
|
}
|
||||||
final container = ProviderContainer(
|
final container = ProviderContainer(
|
||||||
overrides: [apiProvider.overrideWithValue(api)],
|
overrides: overrides,
|
||||||
);
|
);
|
||||||
addTearDown(container.dispose);
|
addTearDown(container.dispose);
|
||||||
return container;
|
return container;
|
||||||
@@ -448,11 +478,32 @@ void main() {
|
|||||||
await container.read(authInfoProvider.notifier).load();
|
await container.read(authInfoProvider.notifier).load();
|
||||||
|
|
||||||
final state = container.read(authInfoProvider);
|
final state = container.read(authInfoProvider);
|
||||||
expect(state?.isAdmin, isTrue);
|
expect(state.status, LoadStatus.data);
|
||||||
expect(state?.name, 'owner');
|
expect(state.data?.isAdmin, isTrue);
|
||||||
|
expect(state.data?.name, 'owner');
|
||||||
expect(container.read(authInfoProvider.notifier).isAdmin, isTrue);
|
expect(container.read(authInfoProvider.notifier).isAdmin, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('auth info load error clears stale auth state and exposes message', () async {
|
||||||
|
final api = FakeIgnisApi(authData: {'is_admin': true, 'name': 'owner'});
|
||||||
|
final container = containerWith(api);
|
||||||
|
|
||||||
|
await container.read(authInfoProvider.notifier).load();
|
||||||
|
api.authError = DioException(
|
||||||
|
requestOptions: RequestOptions(path: '/auth/me'),
|
||||||
|
type: DioExceptionType.connectionError,
|
||||||
|
message: 'No route to host',
|
||||||
|
);
|
||||||
|
|
||||||
|
await container.read(authInfoProvider.notifier).load();
|
||||||
|
|
||||||
|
final state = container.read(authInfoProvider);
|
||||||
|
expect(state.status, LoadStatus.error);
|
||||||
|
expect(state.data, isNull);
|
||||||
|
expect(state.errorMessage, contains('Backend недоступен'));
|
||||||
|
expect(container.read(authInfoProvider.notifier).isAdmin, isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
test('api keys load exposes empty state', () async {
|
test('api keys load exposes empty state', () async {
|
||||||
final api = FakeIgnisApi(apiKeysData: {'keys': <Object>[]});
|
final api = FakeIgnisApi(apiKeysData: {'keys': <Object>[]});
|
||||||
final container = containerWith(api);
|
final container = containerWith(api);
|
||||||
@@ -630,4 +681,54 @@ void main() {
|
|||||||
expect(error.message, contains('Backend недоступен'));
|
expect(error.message, contains('Backend недоступен'));
|
||||||
expect(api.controlGroupParams, containsPair('brightness', 42));
|
expect(api.controlGroupParams, containsPair('brightness', 42));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('home selection rolls back current home and auth state on auth failure', () async {
|
||||||
|
SharedPreferences.setMockInitialValues({
|
||||||
|
'ignis_homes':
|
||||||
|
'[{"id":"home-1","name":"Home 1","url":"https://one.example","geofenceEnabled":false},'
|
||||||
|
'{"id":"home-2","name":"Home 2","url":"https://two.example","geofenceEnabled":false}]',
|
||||||
|
'ignis_current_home_id': 'home-1',
|
||||||
|
});
|
||||||
|
|
||||||
|
final settingsService = SettingsService(
|
||||||
|
credentialsStorage: InMemoryCredentialsStorage(),
|
||||||
|
);
|
||||||
|
await settingsService.setHomeApiKey('home-1', 'key-1');
|
||||||
|
await settingsService.setHomeApiKey('home-2', 'key-2');
|
||||||
|
|
||||||
|
final api = FakeIgnisApi(authData: {'is_admin': true, 'name': 'owner'});
|
||||||
|
final container = containerWith(api, settingsService: settingsService);
|
||||||
|
|
||||||
|
await container.read(currentHomeProvider.notifier).load();
|
||||||
|
await container.read(authInfoProvider.notifier).load();
|
||||||
|
|
||||||
|
final secondHome = HomeConfig(
|
||||||
|
id: 'home-2',
|
||||||
|
name: 'Home 2',
|
||||||
|
url: 'https://two.example',
|
||||||
|
);
|
||||||
|
final authError = DioException(
|
||||||
|
requestOptions: RequestOptions(path: '/auth/me'),
|
||||||
|
type: DioExceptionType.badResponse,
|
||||||
|
response: Response(
|
||||||
|
requestOptions: RequestOptions(path: '/auth/me'),
|
||||||
|
statusCode: 403,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
api.authError = authError;
|
||||||
|
|
||||||
|
await expectLater(
|
||||||
|
container.read(currentHomeProvider.notifier).select(secondHome),
|
||||||
|
throwsA(same(authError)),
|
||||||
|
);
|
||||||
|
|
||||||
|
final currentHome = container.read(currentHomeProvider);
|
||||||
|
final authState = container.read(authInfoProvider);
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
|
expect(currentHome?.id, 'home-1');
|
||||||
|
expect(authState.status, LoadStatus.data);
|
||||||
|
expect(authState.data?.isAdmin, isTrue);
|
||||||
|
expect(prefs.getString('ignis_current_home_id'), 'home-1');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user