feat(push_notifications): issue-660, fix: issue-908

This commit is contained in:
misha
2026-10-14 08:16:20 +03:00
parent 98c175df44
commit dcd343e765
29 changed files with 2281 additions and 608 deletions

View File

@@ -23,7 +23,7 @@ void main() {
);
const state = NotificationSettingsStateLoaded(notificationSettings: settings);
expect(state.props, [settings]);
expect(state.props, [settings, null, null]);
expect(state.notificationSettings, settings);
});

View File

@@ -1,3 +1,4 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_cubit.dart';
import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings_cubit/notification_settings_state.dart';
import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart';
@@ -103,6 +104,51 @@ void main() {
final errorState = notificationSettingsCubit!.state as NotificationSettingsStateError;
expect(errorState.errorMessage, contains('Ошибка обновления'));
});
test('updateNotificationSettings - validation error keeps the screen and exposes an alert', () async {
notificationSettingsCubit = NotificationSettingsCubit(
notificationSettingsRepository: mockNotificationSettingsRepository,
);
when(mockNotificationSettingsRepository.getNotificationSettings()).thenAnswer((_) async => MenuFixtures.notificationSettingsModel());
await notificationSettingsCubit!.getNotificationSettings();
const message = 'Между приемами пищи должна быть разница минимум час';
when(mockNotificationSettingsRepository.updateNotificationSettings(any)).thenThrow(
DioException(
requestOptions: RequestOptions(path: '/api/profile/update_notification_settings'),
type: DioExceptionType.badResponse,
response: Response(
requestOptions: RequestOptions(path: '/api/profile/update_notification_settings'),
statusCode: 422,
data: {
'message': message,
'errors': {
'diary_reminder_notifications.second_reminder': [message],
},
},
),
),
);
await notificationSettingsCubit!.updateNotificationSettings({
'diary_reminder_notifications': {
'second_reminder': {'enabled': true, 'local_time_hours': 9, 'local_time_mins': 30},
},
});
expect(notificationSettingsCubit!.state, isA<NotificationSettingsStateLoaded>());
final loadedState = notificationSettingsCubit!.state as NotificationSettingsStateLoaded;
expect(loadedState.alertMessage, message);
expect(loadedState.notificationSettings.newUpdatesNotification, true);
when(mockNotificationSettingsRepository.updateNotificationSettings(any)).thenThrow(Exception('сеть'));
await notificationSettingsCubit!.updateNotificationSettings({'new_updates_notification': false});
final retried = notificationSettingsCubit!.state as NotificationSettingsStateLoaded;
expect(retried.alertMessage, 'Не удалось сохранить настройки уведомлений');
expect(retried.alertId, isNot(loadedState.alertId));
expect(retried.notificationSettings, loadedState.notificationSettings);
});
});
}

View File

@@ -0,0 +1,38 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/features/menu/presentation/notification_settings_user_message.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('notificationSettingsUpdateAlertMessage', () {
test('uses the Laravel validation message for a meal reminder gap', () {
const message = 'Между приемами пищи должна быть разница минимум час';
final error = DioException(
requestOptions: RequestOptions(
path: '/api/profile/update_notification_settings',
),
type: DioExceptionType.badResponse,
response: Response(
requestOptions: RequestOptions(
path: '/api/profile/update_notification_settings',
),
statusCode: 422,
data: {
'message': message,
'errors': {
'diary_reminder_notifications.second_reminder': [message],
},
},
),
);
expect(notificationSettingsUpdateAlertMessage(error), message);
});
test('uses a short message for other failures', () {
expect(
notificationSettingsUpdateAlertMessage(Exception('stack')),
notificationSettingsSaveFailedMessage,
);
});
});
}

View File

@@ -1,3 +1,4 @@
import 'package:dio/dio.dart';
import 'package:eatvio_app/core/app_config.dart';
import 'package:eatvio_app/core/app_initializer.dart';
import 'package:eatvio_app/core/locator_service.dart';
@@ -7,6 +8,7 @@ import 'package:eatvio_app/features/menu/presentation/bloc/notification_settings
import 'package:eatvio_app/features/menu/presentation/pages/notifications_settings_screen.dart';
import 'package:eatvio_app/features/menu/repositories/notification_settings_repository.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
@@ -111,5 +113,55 @@ void main() {
expect(find.textContaining('Ошибка'), findsOneWidget);
expect(find.text('Повторить'), findsOneWidget);
});
testWidgets('shows an alert instead of replacing the screen when reminder times are too close', (tester) async {
tester.view.physicalSize = const Size(800, 1600);
tester.view.devicePixelRatio = 1.0;
addTearDown(() => tester.view.resetPhysicalSize());
const message = 'Между приемами пищи должна быть разница минимум час';
final repository = MockNotificationSettingsRepository();
final cubit = NotificationSettingsCubit(notificationSettingsRepository: repository);
when(repository.getNotificationSettings()).thenAnswer((_) async => mockNotificationSettings);
when(repository.updateNotificationSettings(any)).thenThrow(
DioException(
requestOptions: RequestOptions(path: '/api/profile/update_notification_settings'),
type: DioExceptionType.badResponse,
response: Response(
requestOptions: RequestOptions(path: '/api/profile/update_notification_settings'),
statusCode: 422,
data: {
'message': message,
'errors': {
'diary_reminder_notifications.second_reminder': [message],
},
},
),
),
);
sl.registerLazySingleton<NotificationSettingsRepository>(() => repository);
sl.registerLazySingleton<NotificationSettingsCubit>(() => cubit);
await tester.pumpWidget(wrap(const NotificationsSettingsScreen()));
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
expect(find.text('Новые обновления'), findsOneWidget);
await tester.tap(find.byType(CupertinoSwitch).first);
await tester.pump();
await tester.pump(const Duration(milliseconds: 300));
expect(find.text(message), findsOneWidget);
expect(find.text('Новые обновления'), findsOneWidget);
expect(find.text('Повторить'), findsNothing);
await tester.tap(find.text('OK'));
await tester.pumpAndSettle();
expect(find.text('Новые обновления'), findsOneWidget);
});
});
}