import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:friflex_starter/app/ui_kit/app_snackbar.dart'; /// Тесты для виджета AppSnackBar void main() { group('AppSnackBar Widget Tests', () { late Widget testApp; /// Создание мок-темы с необходимыми цветами для снекбара ColorScheme createMockColorScheme() { return const ColorScheme.light().copyWith( error: Colors.red, primary: Colors.blue, secondary: Colors.green, ); } /// Создание мок-темы с правильными стилями текста TextTheme createMockTextTheme() { return const TextTheme(bodyMedium: TextStyle(fontSize: 14)); } setUp(() { testApp = MaterialApp( theme: ThemeData( colorScheme: createMockColorScheme(), textTheme: createMockTextTheme(), ), home: const Scaffold(body: Center(child: Text('Тестовый экран'))), ); }); testTester(String description, WidgetTesterCallback callback) { testWidgets(description, (tester) async { await tester.pumpWidget(testApp); await callback(tester); }); } group('AppSnackBar.showInfo', () { testTester('показывает снекбар с информацией и правильными стилями', ( tester, ) async { const infoMessage = 'Это просто сообщение'; AppSnackBar.showInfo( tester.element(find.byType(Scaffold)), message: infoMessage, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(find.byType(AppSnackBar), findsOneWidget); expect(find.text(infoMessage), findsOneWidget); // Проверяем иконку и её цвет final iconFinder = find.byType(Icon); expect(iconFinder, findsOneWidget); final icon = tester.widget(iconFinder); expect(icon.color, equals(Colors.black)); // Info иконка черная // Проверяем цвет фона final container = tester.widget( find.descendant( of: find.byType(GestureDetector), matching: find.byType(Container), ), ); final decoration = container.decoration as BoxDecoration; expect(decoration.color, equals(const Color(0xFFE6E6E6))); // Info фон }); }); group('AppSnackBar.showError', () { testTester('показывает снекбар с ошибкой и правильными стилями', ( tester, ) async { const errorMessage = 'Произошла ошибка'; AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: errorMessage, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(find.byType(AppSnackBar), findsOneWidget); expect(find.text(errorMessage), findsOneWidget); // Проверяем иконку и её цвет final iconFinder = find.byType(Icon); expect(iconFinder, findsOneWidget); final icon = tester.widget(iconFinder); expect(icon.color, equals(Colors.white)); // Error иконка белая // Проверяем цвет фона final container = tester.widget( find.descendant( of: find.byType(GestureDetector), matching: find.byType(Container), ), ); final decoration = container.decoration as BoxDecoration; expect(decoration.color, equals(const Color(0xFFD24720))); // Error фон }); testTester('показывает снекбар с кастомной продолжительностью', ( tester, ) async { const errorMessage = 'Кастомная ошибка'; const customDuration = Duration(seconds: 5); AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: errorMessage, displayDuration: customDuration, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(find.byType(AppSnackBar), findsOneWidget); expect(find.text(errorMessage), findsOneWidget); }); testTester('заменяет предыдущий снекбар при повторном вызове', ( tester, ) async { const firstMessage = 'Первая ошибка'; const secondMessage = 'Вторая ошибка'; // Показываем первый снекбар AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: firstMessage, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(find.text(firstMessage), findsOneWidget); // Показываем второй снекбар AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: secondMessage, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); // Проверяем, что показывается только второй снекбар expect(find.text(firstMessage), findsNothing); expect(find.text(secondMessage), findsOneWidget); expect(find.byType(AppSnackBar), findsOneWidget); }); }); group('AppSnackBar.showSuccess', () { testTester('показывает снекбар с успехом и правильными стилями', ( tester, ) async { const successMessage = 'Операция выполнена успешно'; AppSnackBar.showSuccess( context: tester.element(find.byType(Scaffold)), message: successMessage, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(find.byType(AppSnackBar), findsOneWidget); expect(find.text(successMessage), findsOneWidget); // Проверяем иконку и её цвет final iconFinder = find.byType(Icon); expect(iconFinder, findsOneWidget); final icon = tester.widget(iconFinder); expect(icon.color, equals(Colors.white)); // Success иконка белая // Проверяем цвет фона final container = tester.widget( find.descendant( of: find.byType(GestureDetector), matching: find.byType(Container), ), ); final decoration = container.decoration as BoxDecoration; expect( decoration.color, equals(const Color(0xFF6FB62C)), ); // Success фон }); testTester('показывает снекбар с кастомной продолжительностью', ( tester, ) async { const successMessage = 'Кастомный успех'; const customDuration = Duration(seconds: 2); AppSnackBar.showSuccess( context: tester.element(find.byType(Scaffold)), message: successMessage, displayDuration: customDuration, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(find.byType(AppSnackBar), findsOneWidget); expect(find.text(successMessage), findsOneWidget); }); }); group('AppSnackBar виджет поведение', () { testTester( 'показывает анимацию появления с правильной последовательностью', (tester) async { const message = 'Тестовое сообщение'; AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: message, ); // Проверяем начальное состояние await tester.pump(); final initialPosition = tester.widget( find.byType(Positioned), ); expect(initialPosition.top ?? 0, lessThan(0)); // Проверяем промежуточное состояние await tester.pump(const Duration(milliseconds: 150)); final middlePosition = tester.widget( find.byType(Positioned), ); expect( middlePosition.top ?? 0, greaterThan(initialPosition.top ?? 0), ); // Проверяем конечное состояние await tester.pump(const Duration(milliseconds: 150)); final finalPosition = tester.widget( find.byType(Positioned), ); expect(finalPosition.top ?? 0, greaterThan(0)); }, ); testTester('закрывается при тапе', (tester) async { const message = 'Тап для закрытия'; AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: message, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 350)); expect(find.byType(AppSnackBar), findsOneWidget); // Тапаем по снекбару await tester.tap(find.byType(GestureDetector)); await tester.pump(); // Ждем завершения анимации закрытия и удаления из Overlay await tester.pumpAndSettle(); // Проверяем, что снекбар исчез expect(find.byType(AppSnackBar), findsNothing); }); testTester('автоматически закрывается через указанное время', ( tester, ) async { const message = 'Автозакрытие'; const duration = Duration(milliseconds: 500); AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: message, displayDuration: duration, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 350)); expect(find.byType(AppSnackBar), findsOneWidget); // Ждем автоматического закрытия await tester.pump(duration); await tester.pumpAndSettle(); expect(find.byType(AppSnackBar), findsNothing); }); testTester('отображает длинное сообщение с ellipsis', (tester) async { const longMessage = 'Это очень длинное сообщение, которое должно отображаться в нескольких строках и обрезаться с помощью ellipsis если оно слишком длинное для отображения в виджете снекбара'; AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: longMessage, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(find.byType(AppSnackBar), findsOneWidget); // Проверяем, что текст имеет ограничение по строкам final textWidget = tester.widget(find.text(longMessage)); expect(textWidget.maxLines, equals(3)); expect(textWidget.overflow, equals(TextOverflow.ellipsis)); }); testTester('имеет правильную структуру виджета', (tester) async { const message = 'Структура'; AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: message, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); // Проверяем основную структуру expect(find.byType(Positioned), findsOneWidget); expect(find.byType(Material), findsAtLeastNWidgets(1)); expect(find.byType(GestureDetector), findsOneWidget); expect(find.byType(Container), findsAtLeastNWidgets(1)); expect(find.byType(Row), findsOneWidget); expect(find.byType(Icon), findsOneWidget); expect(find.byType(Text), findsAtLeastNWidgets(1)); }); testTester('имеет правильные отступы и размеры на разных экранах', ( tester, ) async { const message = 'Размеры'; // Тестируем на маленьком экране await tester.binding.setSurfaceSize(const Size(320, 480)); AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: message, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); var container = tester.widget( find.descendant( of: find.byType(GestureDetector), matching: find.byType(Container), ), ); // На маленьком экране максимальная ширина должна быть 350 expect(container.constraints?.maxWidth, equals(350)); // Тестируем на большом экране await tester.binding.setSurfaceSize(const Size(1280, 720)); await tester.pumpWidget(testApp); AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: message, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); container = tester.widget( find.descendant( of: find.byType(GestureDetector), matching: find.byType(Container), ), ); expect( container.constraints?.maxWidth, equals(350), ); // Максимальная ширина }); testTester('имеет правильное скругление углов', (tester) async { const message = 'Скругление'; AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: message, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); final container = tester.widget( find.descendant( of: find.byType(GestureDetector), matching: find.byType(Container), ), ); final decoration = container.decoration as BoxDecoration; expect(decoration.borderRadius, equals(BorderRadius.circular(16))); }); }); group('Доступность', () { testTester('имеет правильные семантические метки', (tester) async { const message = 'Доступность'; AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: message, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); // Проверяем наличие текста сообщения expect(find.text(message), findsOneWidget); // Проверяем наличие GestureDetector для закрытия expect(find.byType(GestureDetector), findsOneWidget); }); }); group('Управление состоянием', () { testTester('правильно обрабатывает отсутствие mounted контекста', ( tester, ) async { const message = 'Unmounted контекст'; // Создаем виджет, который будет удален await tester.pumpWidget( MaterialApp( home: Builder( builder: (context) { return Scaffold( body: ElevatedButton( onPressed: () { AppSnackBar.showError(context, message: message); }, child: const Text('Show SnackBar'), ), ); }, ), ), ); // Нажимаем кнопку для показа снекбара await tester.tap(find.byType(ElevatedButton)); await tester.pump(); // Заменяем весь виджет (контекст становится unmounted) await tester.pumpWidget( const MaterialApp(home: Scaffold(body: Text('New Screen'))), ); // Проверяем, что ошибки не возникает expect(tester.takeException(), isNull); }); testTester('правильно очищает ресурсы при dispose', (tester) async { const message = 'Очистка ресурсов'; AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: message, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(find.byType(AppSnackBar), findsOneWidget); // Заменяем весь виджет для вызова dispose await tester.pumpWidget( const MaterialApp(home: Scaffold(body: Text('New Screen'))), ); // Проверяем, что ошибки не возникает при dispose expect(tester.takeException(), isNull); }); testTester('правильно обрабатывает быстрые последовательные вызовы', ( tester, ) async { const messages = ['Сообщение 1', 'Сообщение 2', 'Сообщение 3']; for (final message in messages) { AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: message, ); await tester.pump(); } await tester.pump(const Duration(milliseconds: 100)); // Проверяем, что показывается только последнее сообщение expect(find.text(messages[0]), findsNothing); expect(find.text(messages[1]), findsNothing); expect(find.text(messages[2]), findsOneWidget); }); }); group('Управление снекбарами', () { testTester('правильно заменяет предыдущий снекбар новым', (tester) async { const firstMessage = 'Первый снекбар'; const secondMessage = 'Второй снекбар'; // Показываем первый снекбар AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: firstMessage, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(find.text(firstMessage), findsOneWidget); // Показываем второй снекбар (должен заменить первый) AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: secondMessage, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); // Проверяем, что остался только второй снекбар expect(find.text(firstMessage), findsNothing); expect(find.text(secondMessage), findsOneWidget); }); testTester('правильно заменяет снекбар error на success', (tester) async { const errorMessage = 'Ошибка'; const successMessage = 'Успех'; // Показываем снекбар с ошибкой AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: errorMessage, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(find.text(errorMessage), findsOneWidget); // Показываем снекбар с успехом AppSnackBar.showSuccess( context: tester.element(find.byType(Scaffold)), message: successMessage, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); // Проверяем, что показывается только снекбар с успехом expect(find.text(errorMessage), findsNothing); expect(find.text(successMessage), findsOneWidget); }); }); group('Анимация', () { testTester('анимация появления работает корректно', (tester) async { const message = 'Анимация появления'; AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: message, ); // В начале анимации снекбар должен находиться за пределами экрана await tester.pump(); final positionedWidget = tester.widget( find.byType(Positioned), ); expect(positionedWidget.top, lessThan(0)); // После завершения анимации снекбар должен быть виден await tester.pump(const Duration(milliseconds: 300)); final positionedWidgetAfter = tester.widget( find.byType(Positioned), ); expect(positionedWidgetAfter.top, greaterThan(0)); }); testTester('анимация скрытия работает корректно при тапе', ( tester, ) async { const message = 'Анимация скрытия'; AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: message, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 350)); expect(find.byType(AppSnackBar), findsOneWidget); // Тапаем для закрытия await tester.tap(find.byType(GestureDetector)); await tester.pump(); // Анимация закрытия должна начаться expect(find.byType(AppSnackBar), findsOneWidget); // После завершения анимации снекбар должен исчезнуть await tester.pumpAndSettle(); expect(find.byType(AppSnackBar), findsNothing); }); }); group('Интеграция с MediaQuery', () { testTester('правильно позиционируется с учетом SafeArea', (tester) async { const message = 'SafeArea test'; // Создаем приложение с кастомными отступами await tester.pumpWidget( MaterialApp( home: MediaQuery( data: const MediaQueryData( padding: EdgeInsets.only(top: 50), // Симулируем статус бар ), child: const Scaffold(body: Center(child: Text('Test'))), ), ), ); AppSnackBar.showError( tester.element(find.byType(Scaffold)), message: message, ); await tester.pump(); await tester.pump(const Duration(milliseconds: 350)); // Проверяем, что снекбар отображается (позиция может варьироваться в зависимости от того, // когда именно MediaQuery применяется) final positionedWidget = tester.widget( find.byType(Positioned), ); expect( positionedWidget.top, greaterThan(0), ); // Снекбар должен быть видимым expect(find.byType(AppSnackBar), findsOneWidget); }); }); }); }