forked from eatvio/eatvio_app
fix: issue-1049, issue-1039, issue-1035, issue-1032, issue-995, issue-993
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:eatvio_app/core/notifications/notification_permission_requester.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('NotificationPermissionRequester', () {
|
||||
test('вызывает requestPermission один раз', () async {
|
||||
final requester = NotificationPermissionRequester();
|
||||
var calls = 0;
|
||||
|
||||
await requester.requestOnce(() async {
|
||||
calls++;
|
||||
});
|
||||
|
||||
expect(calls, 1);
|
||||
});
|
||||
|
||||
test('параллельные вызовы ждут один и тот же запрос', () async {
|
||||
final requester = NotificationPermissionRequester();
|
||||
var calls = 0;
|
||||
|
||||
Future<void> request() {
|
||||
return requester.requestOnce(() async {
|
||||
calls++;
|
||||
await Future<void>.delayed(const Duration(milliseconds: 50));
|
||||
});
|
||||
}
|
||||
|
||||
await Future.wait<void>([request(), request(), request()]);
|
||||
|
||||
expect(calls, 1);
|
||||
});
|
||||
|
||||
test('после завершения можно запросить снова', () async {
|
||||
final requester = NotificationPermissionRequester();
|
||||
var calls = 0;
|
||||
|
||||
Future<void> request() {
|
||||
return requester.requestOnce(() async {
|
||||
calls++;
|
||||
});
|
||||
}
|
||||
|
||||
await request();
|
||||
await request();
|
||||
|
||||
expect(calls, 2);
|
||||
});
|
||||
});
|
||||
|
||||
group('единая точка запроса разрешения на уведомления', () {
|
||||
test('старт приложения и CallKit не показывают отдельные системные окна', () {
|
||||
final initializer = File('lib/core/app_initializer.dart').readAsStringSync();
|
||||
final callkit = File('lib/features/video_calls/services/video_call_callkit_coordinator.dart').readAsStringSync();
|
||||
final firebaseApi = File('lib/core/firebase_api.dart').readAsStringSync();
|
||||
|
||||
expect(initializer.contains('requestPermission'), isFalse);
|
||||
expect(callkit.contains('requestNotificationPermission'), isFalse);
|
||||
expect(firebaseApi.contains('_requestNotificationPermission'), isTrue);
|
||||
expect(firebaseApi.contains('_firebaseMessaging.requestPermission()'), isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/cubit/complaining_cubit.dart';
|
||||
import 'package:eatvio_app/features/complaining/repositories/complaining_repository.dart';
|
||||
@@ -12,6 +15,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import 'complaining_bottom_sheet_smoke_test.mocks.dart';
|
||||
|
||||
@@ -59,7 +63,40 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(ComplainingBottomSheet), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('replaces send button with Loader while request is in progress', (tester) async {
|
||||
final completer = Completer<void>();
|
||||
when(
|
||||
repository.sendPlanComplain(
|
||||
slug: anyNamed('slug'),
|
||||
category: anyNamed('category'),
|
||||
body: anyNamed('body'),
|
||||
),
|
||||
).thenAnswer((_) => completer.future);
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
const ComplainingBottomSheet(
|
||||
complainedContentType: ComplainedContent.plan,
|
||||
slug: 'test-slug',
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Спам'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Отправить жалобу'), findsOneWidget);
|
||||
await tester.tap(find.text('Отправить жалобу'));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byType(Loader), findsOneWidget);
|
||||
expect(find.text('Отправить жалобу'), findsNothing);
|
||||
|
||||
completer.complete();
|
||||
await tester.pump();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:eatvio_app/core/enums/complained_content.dart';
|
||||
import 'package:eatvio_app/features/complaining/models/complain_model.dart';
|
||||
import 'package:eatvio_app/features/complaining/presentation/complaining_bottom_sheet.dart';
|
||||
@@ -97,6 +99,7 @@ void main() {
|
||||
await cubit.complain();
|
||||
|
||||
expect(cubit.state.wasSentSuccessfully, true);
|
||||
expect(cubit.state.isLoading, false);
|
||||
verify(() => mockRepository.sendDietComplain(
|
||||
id: id,
|
||||
category: any(named: 'category'),
|
||||
@@ -104,6 +107,30 @@ void main() {
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('complain - ставит isLoading на время запроса', () async {
|
||||
const id = 1;
|
||||
final completer = Completer<void>();
|
||||
|
||||
when(() => mockRepository.sendDietComplain(
|
||||
id: id,
|
||||
category: any(named: 'category'),
|
||||
body: any(named: 'body'),
|
||||
)).thenAnswer((_) => completer.future);
|
||||
|
||||
cubit.setContentTypeAndIdentifier(contentType: ComplainedContent.diet, id: id);
|
||||
cubit.setCategory(ComplainType.spam);
|
||||
|
||||
final future = cubit.complain();
|
||||
expect(cubit.state.isLoading, true);
|
||||
expect(cubit.state.wasSentSuccessfully, isNull);
|
||||
|
||||
completer.complete();
|
||||
await future;
|
||||
|
||||
expect(cubit.state.isLoading, false);
|
||||
expect(cubit.state.wasSentSuccessfully, true);
|
||||
});
|
||||
|
||||
test('complain - возвращает false при ошибке', () async {
|
||||
const id = 1;
|
||||
|
||||
@@ -119,6 +146,7 @@ void main() {
|
||||
await cubit.complain();
|
||||
|
||||
expect(cubit.state.wasSentSuccessfully, false);
|
||||
expect(cubit.state.isLoading, false);
|
||||
});
|
||||
|
||||
test('complain - отправляет жалобу на plan', () async {
|
||||
|
||||
@@ -4,9 +4,12 @@ import 'package:eatvio_app/features/diary/presentation/bloc/diary_state.dart';
|
||||
import 'package:eatvio_app/features/diary/presentation/widgets/calendar_widget.dart';
|
||||
import 'package:eatvio_app/generated/l10n.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:table_calendar/table_calendar.dart';
|
||||
|
||||
class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
|
||||
FakeDiaryCubit(super.initialState);
|
||||
@@ -16,9 +19,20 @@ class FakeDiaryCubit extends Cubit<DiaryState> implements DiaryCubit {
|
||||
|
||||
void main() {
|
||||
group('CalendarWidget smoke', () {
|
||||
Widget wrap(Widget child, DiaryCubit cubit) {
|
||||
setUpAll(() async {
|
||||
await initializeDateFormatting('ru', null);
|
||||
await initializeDateFormatting('ru_RU', null);
|
||||
});
|
||||
|
||||
Widget wrap(Widget child, DiaryCubit cubit, {double textScale = 1}) {
|
||||
return MaterialApp(
|
||||
theme: ThemeHelper().themeData(),
|
||||
builder: (context, appChild) {
|
||||
return MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(textScaler: TextScaler.linear(textScale)),
|
||||
child: appChild!,
|
||||
);
|
||||
},
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
@@ -37,9 +51,26 @@ void main() {
|
||||
final cubit = FakeDiaryCubit(const DiaryState.loading());
|
||||
await tester.pumpWidget(wrap(const CalendarWidget(), cubit));
|
||||
await tester.pump();
|
||||
// Just make sure it builds.
|
||||
expect(find.byType(CalendarWidget), findsOneWidget);
|
||||
await cubit.close();
|
||||
});
|
||||
|
||||
testWidgets('scales weekday row when system font is large', (tester) async {
|
||||
const textScale = 2.0;
|
||||
final cubit = FakeDiaryCubit(const DiaryState.loading());
|
||||
await tester.pumpWidget(wrap(const CalendarWidget(), cubit, textScale: textScale));
|
||||
await tester.pump();
|
||||
|
||||
final calendar = tester.widget<TableCalendar<dynamic>>(find.byType(TableCalendar<dynamic>));
|
||||
expect(calendar.daysOfWeekHeight, greaterThan(16 * textScale * 0.9));
|
||||
expect(calendar.rowHeight, 52 * textScale);
|
||||
|
||||
expect(find.text('пн'), findsWidgets);
|
||||
final weekday = tester.renderObject<RenderParagraph>(find.text('пн').first);
|
||||
expect(weekday.size.height, lessThanOrEqualTo(calendar.daysOfWeekHeight + 0.5));
|
||||
expect(tester.takeException(), isNull);
|
||||
|
||||
await cubit.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,6 +156,44 @@ void main() {
|
||||
expect(dietsCubit!.state.errorMessage, isNull);
|
||||
});
|
||||
|
||||
test('loadDiets - сбрасывает currentPage при повторной загрузке', () async {
|
||||
dietsCubit = DietsCubit(
|
||||
feedRepository: mockFeedRepository,
|
||||
dietsRepository: mockDietRepository,
|
||||
);
|
||||
|
||||
const dietsPagination = DietsPagination(
|
||||
data: [],
|
||||
links: Links(
|
||||
first: 'https://example.com?page=1',
|
||||
last: 'https://example.com?page=1',
|
||||
prev: null,
|
||||
next: null,
|
||||
),
|
||||
meta: Meta(
|
||||
currentPage: 1,
|
||||
from: null,
|
||||
lastPage: 1,
|
||||
links: [],
|
||||
perPage: 10,
|
||||
to: null,
|
||||
total: 0,
|
||||
),
|
||||
);
|
||||
|
||||
when(mockFeedRepository.getDiets(
|
||||
query: anyNamed('query'),
|
||||
types: anyNamed('types'),
|
||||
duration: anyNamed('duration'),
|
||||
sorting: anyNamed('sorting'),
|
||||
)).thenAnswer((_) async => dietsPagination);
|
||||
|
||||
dietsCubit!.currentPage = 4;
|
||||
await dietsCubit!.loadDiets();
|
||||
|
||||
expect(dietsCubit!.currentPage, 1);
|
||||
});
|
||||
|
||||
test('loadDiets - обработка ошибки', () async {
|
||||
dietsCubit = DietsCubit(
|
||||
feedRepository: mockFeedRepository,
|
||||
|
||||
@@ -131,6 +131,31 @@ void main() {
|
||||
expect(plansCubit!.state.errorMessage, isNull);
|
||||
});
|
||||
|
||||
test('loadPlans - сбрасывает currentPage при повторной загрузке', () async {
|
||||
plansCubit = PlansCubit(feedRepository: mockFeedRepository, planRepository: mockPlanRepository);
|
||||
|
||||
const plansPagination = PlanPaginationModel(
|
||||
data: [],
|
||||
links: Links(first: 'https://example.com?page=1', last: 'https://example.com?page=1', prev: null, next: null),
|
||||
meta: Meta(currentPage: 1, from: 1, lastPage: 1, links: [], perPage: 10, to: 1, total: 0),
|
||||
);
|
||||
|
||||
when(
|
||||
mockFeedRepository.getPlans(
|
||||
query: anyNamed('query'),
|
||||
types: anyNamed('types'),
|
||||
duration: anyNamed('duration'),
|
||||
minPrice: anyNamed('minPrice'),
|
||||
maxPrice: anyNamed('maxPrice'),
|
||||
),
|
||||
).thenAnswer((_) async => plansPagination);
|
||||
|
||||
plansCubit!.currentPage = 4;
|
||||
await plansCubit!.loadPlans();
|
||||
|
||||
expect(plansCubit!.currentPage, 1);
|
||||
});
|
||||
|
||||
test('loadPlans - обработка ошибки', () async {
|
||||
plansCubit = PlansCubit(feedRepository: mockFeedRepository, planRepository: mockPlanRepository);
|
||||
|
||||
|
||||
@@ -110,6 +110,18 @@ void main() {
|
||||
expect(recipesCubit!.state.errorMessage, isNull);
|
||||
});
|
||||
|
||||
test('loadRecipesList - сбрасывает currentPage при повторной загрузке', () async {
|
||||
recipesCubit = RecipesCubit(feedRepository: mockFeedRepository, recipeRepository: mockRecipeRepository, aboutRecipeRepository: mockAboutRecipeRepository);
|
||||
|
||||
when(mockFeedRepository.getRecipes(tags: anyNamed('tags'), query: anyNamed('query'))).thenAnswer((_) async => []);
|
||||
when(mockRecipeRepository.getRecipesTags()).thenAnswer((_) async => []);
|
||||
|
||||
recipesCubit!.currentPage = 4;
|
||||
await recipesCubit!.loadRecipesList();
|
||||
|
||||
expect(recipesCubit!.currentPage, 1);
|
||||
});
|
||||
|
||||
test('updateTags - добавляет и удаляет теги', () {
|
||||
recipesCubit = RecipesCubit(feedRepository: mockFeedRepository, recipeRepository: mockRecipeRepository, aboutRecipeRepository: mockAboutRecipeRepository);
|
||||
|
||||
|
||||
@@ -136,4 +136,19 @@ void main() {
|
||||
expect(find.byType(FeedScreen), findsOneWidget);
|
||||
});
|
||||
});
|
||||
|
||||
group('shouldReloadFeedAfterCreate', () {
|
||||
test('перезагружает ленту после публикации диеты или плана', () {
|
||||
expect(shouldReloadFeedAfterCreate(true), isTrue);
|
||||
});
|
||||
|
||||
test('перезагружает ленту после публикации рецепта', () {
|
||||
expect(shouldReloadFeedAfterCreate('active'), isTrue);
|
||||
});
|
||||
|
||||
test('не перезагружает ленту при отмене или черновике рецепта', () {
|
||||
expect(shouldReloadFeedAfterCreate(null), isFalse);
|
||||
expect(shouldReloadFeedAfterCreate('drafted'), isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_initializer.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/theme/theme_helper.dart';
|
||||
import 'package:eatvio_app/core/widgets/loader.dart';
|
||||
import 'package:eatvio_app/features/profile/models/profile_model.dart';
|
||||
import 'package:eatvio_app/features/settings/entities/settings_entity.dart';
|
||||
import 'package:eatvio_app/features/settings/models/profile_settings_model.dart';
|
||||
@@ -130,5 +133,52 @@ void main() {
|
||||
|
||||
expect(find.byType(GeneralInfoScreen), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('лоадер при сохранении появляется вместо кнопки, а не поверх экрана', (tester) async {
|
||||
final cubit = MockSettingsCubit();
|
||||
const state = SettingsState.loaded(
|
||||
profileSettings: mockProfileSettings,
|
||||
profileDetails: mockProfileDetails,
|
||||
profile: mockProfile,
|
||||
hasActiveConsultation: false,
|
||||
);
|
||||
final saveCompleter = Completer<void>();
|
||||
|
||||
when(cubit.state).thenReturn(state);
|
||||
when(cubit.stream).thenAnswer((_) => Stream.value(state));
|
||||
when(cubit.close()).thenAnswer((_) async => Future<void>.value());
|
||||
when(cubit.updateProfileDetails(profileDetailsModel: anyNamed('profileDetailsModel'))).thenAnswer((_) => saveCompleter.future);
|
||||
when(cubit.updateProfileSettings(profileSettingsModel: anyNamed('profileSettingsModel'))).thenAnswer((_) async {});
|
||||
|
||||
final settingsEntity = SettingsEntity(profileSettingsModel: mockProfileSettings, profileDetailsModel: mockProfileDetails, settingsCubit: cubit);
|
||||
|
||||
await tester.pumpWidget(
|
||||
BlocProvider<SettingsCubit>.value(
|
||||
value: cubit,
|
||||
child: wrap(GeneralInfoScreen(settingsEntity: settingsEntity)),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Сохранить'), findsOneWidget);
|
||||
expect(find.byType(Loader), findsNothing);
|
||||
|
||||
await tester.tap(find.text('Сохранить'));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Сохранить'), findsNothing);
|
||||
expect(find.byType(Loader), findsOneWidget);
|
||||
expect(find.text('Введите имя'), findsOneWidget);
|
||||
|
||||
final loaderSize = tester.getSize(find.byType(Loader));
|
||||
expect(loaderSize.height, lessThan(60));
|
||||
|
||||
saveCompleter.complete();
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
|
||||
expect(find.text('Сохранить'), findsOneWidget);
|
||||
expect(find.byType(Loader), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user