chore(version): update app version to 1.3.5+85 and improve test coverage reporting in CI

This commit is contained in:
mso7nueh
2025-12-08 11:19:08 +03:00
parent abe4985f0e
commit c2a506cdbf
148 changed files with 12395 additions and 1889 deletions

View File

@@ -1,73 +1,109 @@
import 'package:eatvio_app/core/cubit/base_cubit.dart';
import 'package:flutter_test/flutter_test.dart';
// Простой тестовый cubit для проверки BaseCubit
class TestState {
final String? value;
TestState({this.value});
}
// Test implementation of BaseCubit
class TestCubit extends BaseCubit<String> {
TestCubit() : super('initial');
class TestCubit extends BaseCubit<TestState> {
TestCubit() : super(TestState());
void testHandleError(Object error, [StackTrace? stackTrace]) {
handleError(error, stackTrace);
void setState(String state) {
emit(state);
}
Future<String?> testSafeExecute() async {
return await safeExecute(() async => 'success');
Future<String?> executeOperation() async {
return await safeExecute(() async {
await Future.delayed(Duration(milliseconds: 10));
return 'success';
});
}
Future<String?> testSafeExecuteWithError() async {
return await safeExecute(() async => throw Exception('Test error'));
Future<String?> executeOperationWithState() async {
return await safeExecuteWithState(
() async {
await Future.delayed(Duration(milliseconds: 10));
return 'result';
},
loadingState: 'loading',
successState: (result) => 'success: $result',
errorState: (error) => 'error: $error',
);
}
Future<String?> testSafeExecuteWithState() async {
return await safeExecuteWithState<String>(
() async => 'result',
loadingState: TestState(value: 'loading'),
successState: (result) => TestState(value: result),
errorState: (error) => TestState(value: 'error'),
Future<String?> executeFailingOperation() async {
return await safeExecute(() async {
throw Exception('Test error');
});
}
Future<String?> executeFailingOperationWithState() async {
return await safeExecuteWithState(
() async {
throw Exception('Test error');
},
loadingState: 'loading',
successState: (result) => 'success: $result',
errorState: (error) => 'error: $error',
);
}
}
void main() {
group('BaseCubit', () {
late TestCubit testCubit;
late TestCubit cubit;
setUp(() {
testCubit = TestCubit();
cubit = TestCubit();
});
tearDown(() async {
if (!testCubit.isClosed) {
await testCubit.close();
}
tearDown(() {
cubit.close();
});
test('handleError - обрабатывает ошибку без исключений', () {
expect(() => testCubit.testHandleError(Exception('Test error')), returnsNormally);
test('initial state is set correctly', () {
expect(cubit.state, 'initial');
});
test('safeExecute - успешно выполняет операцию', () async {
final result = await testCubit.testSafeExecute();
test('setState updates state', () {
cubit.setState('new state');
expect(cubit.state, 'new state');
});
test('safeExecute returns result on success', () async {
final result = await cubit.executeOperation();
expect(result, 'success');
});
test('safeExecute - возвращает null при ошибке', () async {
final result = await testCubit.testSafeExecuteWithError();
test('safeExecute returns null on error', () async {
final result = await cubit.executeFailingOperation();
expect(result, isNull);
});
test('safeExecuteWithState - устанавливает состояния правильно', () async {
final result = await testCubit.testSafeExecuteWithState();
test('safeExecuteWithState emits loading then success state', () async {
expectLater(
cubit.stream,
emitsInOrder([
'loading',
predicate<String>((state) => state.contains('success')),
]),
);
expect(result, 'result');
expect(testCubit.state.value, 'result');
await cubit.executeOperationWithState();
});
test('safeExecuteWithState emits loading then error state on failure', () async {
expectLater(
cubit.stream,
emitsInOrder([
'loading',
predicate<String>((state) => state.contains('error')),
]),
);
await cubit.executeFailingOperationWithState();
});
test('safeExecuteWithState returns null on error', () async {
final result = await cubit.executeFailingOperationWithState();
expect(result, isNull);
});
});
}

View File

@@ -0,0 +1,22 @@
import 'package:eatvio_app/core/cubit/marketing/marketing_cubit_state.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('MarketingCubitState', () {
test('creates state with const constructor', () {
const state = MarketingCubitState();
expect(state, isA<MarketingCubitState>());
});
test('can create multiple instances', () {
const state1 = MarketingCubitState();
const state2 = MarketingCubitState();
expect(state1, isA<MarketingCubitState>());
expect(state2, isA<MarketingCubitState>());
expect(state1 == state2, true);
});
});
}

View File

@@ -5,94 +5,109 @@ import 'package:eatvio_app/core/prefs_keys.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'marketing_cubit_test.mocks.dart';
@GenerateNiceMocks([MockSpec<Prefs>()])
@GenerateMocks([Prefs])
void main() {
group('MarketingCubit', () {
late MarketingCubit marketingCubit;
late MockPrefs mockPrefs;
late MarketingCubit cubit;
setUp(() async {
SharedPreferences.setMockInitialValues({});
setUp(() {
mockPrefs = MockPrefs();
marketingCubit = MarketingCubit(mockPrefs);
cubit = MarketingCubit(mockPrefs);
});
tearDown(() async {
if (!marketingCubit.isClosed) {
await marketingCubit.close();
}
tearDown(() {
cubit.close();
});
test('начальное состояние - MarketingCubitState', () {
expect(marketingCubit.state, const MarketingCubitState());
test('initial state is MarketingCubitState', () {
expect(cubit.state, isA<MarketingCubitState>());
});
test('checkRegistrationDate - возвращает false если registrationDate отсутствует', () async {
test('returns false when registration date is null', () async {
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenReturn(null);
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown))
.thenReturn(false);
final result = await marketingCubit.checkRegistrationDate();
final result = await cubit.checkRegistrationDate();
expect(result, isFalse);
expect(result, false);
verify(mockPrefs.getInt(PrefsKeys.registrationDate)).called(1);
});
test('checkRegistrationDate - возвращает false если маркетинговый bottom sheet уже был показан', () async {
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenReturn(DateTime.now().millisecondsSinceEpoch);
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).thenReturn(true);
test('returns false when marketing bottom sheet has been shown', () async {
final now = DateTime.now();
when(mockPrefs.getInt(PrefsKeys.registrationDate))
.thenReturn(now.millisecondsSinceEpoch);
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown))
.thenReturn(true);
final result = await marketingCubit.checkRegistrationDate();
final result = await cubit.checkRegistrationDate();
expect(result, isFalse);
expect(result, false);
verify(mockPrefs.getInt(PrefsKeys.registrationDate)).called(1);
verify(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).called(1);
verify(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown))
.called(1);
});
test('checkRegistrationDate - возвращает false если прошло менее 14 дней', () async {
final registrationDate = DateTime.now().subtract(const Duration(days: 10));
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenReturn(registrationDate.millisecondsSinceEpoch);
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).thenReturn(false);
test('returns false when less than 14 days have passed', () async {
final date = DateTime.now().subtract(const Duration(days: 10));
when(mockPrefs.getInt(PrefsKeys.registrationDate))
.thenReturn(date.millisecondsSinceEpoch);
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown))
.thenReturn(false);
final result = await marketingCubit.checkRegistrationDate();
final result = await cubit.checkRegistrationDate();
expect(result, isFalse);
expect(result, false);
verify(mockPrefs.getInt(PrefsKeys.registrationDate)).called(1);
verify(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).called(1);
verifyNever(mockPrefs.setBool(any, any));
verify(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown))
.called(1);
});
test('checkRegistrationDate - возвращает true если прошло 14 дней или больше', () async {
final registrationDate = DateTime.now().subtract(const Duration(days: 14));
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenReturn(registrationDate.millisecondsSinceEpoch);
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).thenReturn(false);
when(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true)).thenAnswer((_) async => Future.value());
test('returns true and sets flag when 14 or more days have passed', () async {
final date = DateTime.now().subtract(const Duration(days: 14));
when(mockPrefs.getInt(PrefsKeys.registrationDate))
.thenReturn(date.millisecondsSinceEpoch);
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown))
.thenReturn(false);
when(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true))
.thenAnswer((_) async => Future.value());
final result = await marketingCubit.checkRegistrationDate();
final result = await cubit.checkRegistrationDate();
expect(result, isTrue);
expect(result, true);
verify(mockPrefs.getInt(PrefsKeys.registrationDate)).called(1);
verify(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).called(1);
verify(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true)).called(1);
verify(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown))
.called(1);
verify(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true))
.called(1);
});
test('checkRegistrationDate - устанавливает флаг после показа bottom sheet', () async {
final registrationDate = DateTime.now().subtract(const Duration(days: 15));
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenReturn(registrationDate.millisecondsSinceEpoch);
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown)).thenReturn(false);
when(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true)).thenAnswer((_) async => Future.value());
test('returns true when more than 14 days have passed', () async {
final date = DateTime.now().subtract(const Duration(days: 20));
when(mockPrefs.getInt(PrefsKeys.registrationDate))
.thenReturn(date.millisecondsSinceEpoch);
when(mockPrefs.getBool(PrefsKeys.marketingBottomSheetHasBeenShown))
.thenReturn(false);
when(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true))
.thenAnswer((_) async => Future.value());
await marketingCubit.checkRegistrationDate();
final result = await cubit.checkRegistrationDate();
verify(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true)).called(1);
expect(result, true);
verify(mockPrefs.setBool(PrefsKeys.marketingBottomSheetHasBeenShown, true))
.called(1);
});
test('checkRegistrationDate - пробрасывает исключение', () async {
when(mockPrefs.getInt(PrefsKeys.registrationDate)).thenThrow(Exception('Ошибка чтения'));
test('rethrows exception when error occurs', () async {
when(mockPrefs.getInt(PrefsKeys.registrationDate))
.thenThrow(Exception('Test error'));
expect(() => marketingCubit.checkRegistrationDate(), throwsException);
expect(() => cubit.checkRegistrationDate(), throwsException);
});
});
}

View File

@@ -0,0 +1,64 @@
import 'package:eatvio_app/core/cubit/state_utils.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('StateUtils', () {
test('isLoading returns true for loading state', () {
expect(StateUtils.isLoading('loading'), isTrue);
expect(StateUtils.isLoading('Loading'), isTrue);
expect(StateUtils.isLoading('StateLoading'), isTrue);
expect(StateUtils.isLoading('state_loading'), isTrue);
});
test('isLoading returns false for non-loading state', () {
expect(StateUtils.isLoading('loaded'), isFalse);
expect(StateUtils.isLoading('error'), isFalse);
expect(StateUtils.isLoading('success'), isFalse);
});
test('isLoaded returns true for loaded state', () {
expect(StateUtils.isLoaded('loaded'), isTrue);
expect(StateUtils.isLoaded('Loaded'), isTrue);
expect(StateUtils.isLoaded('StateLoaded'), isTrue);
expect(StateUtils.isLoaded('state_loaded'), isTrue);
});
test('isLoaded returns false for non-loaded state', () {
expect(StateUtils.isLoaded('loading'), isFalse);
expect(StateUtils.isLoaded('error'), isFalse);
expect(StateUtils.isLoaded('initial'), isFalse);
});
test('isError returns true for error state', () {
expect(StateUtils.isError('error'), isTrue);
expect(StateUtils.isError('Error'), isTrue);
expect(StateUtils.isError('StateError'), isTrue);
expect(StateUtils.isError('state_error'), isTrue);
});
test('isError returns false for non-error state', () {
expect(StateUtils.isError('loading'), isFalse);
expect(StateUtils.isError('loaded'), isFalse);
expect(StateUtils.isError('success'), isFalse);
});
test('getErrorMessage returns null for non-error state', () {
expect(StateUtils.getErrorMessage('loading'), isNull);
expect(StateUtils.getErrorMessage('loaded'), isNull);
expect(StateUtils.getErrorMessage('success'), isNull);
});
test('getErrorMessage extracts error message from error state', () {
final errorState = 'error: Something went wrong';
final message = StateUtils.getErrorMessage(errorState);
expect(message, isNotNull);
expect(message, contains('Something'));
});
test('getErrorMessage handles various error state formats', () {
expect(StateUtils.getErrorMessage('error: test'), isNotNull);
expect(StateUtils.getErrorMessage('Error: test message'), isNotNull);
});
});
}