import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_test/flutter_test.dart' as flutter_test; /// 테스트 환경에서 사용할 유틸리티 함수들을 제공하는 클래스 class TestUtils { /// 테스트 모드 강제 설정 (테스트에서 명시적으로 설정 가능) static bool _forceTestMode = false; /// 테스트 모드 강제 설정 static void setTestMode(bool isTest) { _forceTestMode = isTest; } /// 현재 테스트 환경인지 확인 (개선된 버전) static bool get isInTest { // 강제 설정된 경우 해당 값 반환 if (_forceTestMode) { return true; } // 다양한 방법으로 테스트 환경 감지 시도 try { // 방법 1: flutter_test 라이브러리의 속성 확인 return flutter_test.WidgetController.hitTestWarningShouldBeFatal; } catch (_) { try { // 방법 2: 테스트 환경에서만 존재하는 Zone 속성 확인 return Zone.current['_testZoneSpecification'] != null; } catch (_) { try { // 방법 3: 디버그 모드 확인 (완벽한 방법은 아니지만 대안) return kDebugMode; } catch (_) { return false; } } } } /// 테스트용 고정 해시 생성 (테스트에서 예측 가능한 결과를 위해) static int _testHashCounter = 0; /// 테스트 환경에서 사용할 예측 가능한 해시 생성 static String getTestHash() { _testHashCounter++; final counter = _testHashCounter.toString().padLeft(2, '0'); return 'test$counter'; } /// 현재 타이머가 활성화되어 있는지 확인 /// 테스트 종료 시 !timersPending assertion 실패 원인 추적용 static bool isTimerPending() { try { // flutter_test 라이브러리의 테스트 환경에서만 사용 가능 if (isInTest) { // Zone 속성을 통해 타이머 상태 확인 시도 final zone = Zone.current; if (zone['_timerCount'] != null) { return zone['_timerCount'] > 0; } // 다른 방법으로 타이머 상태 확인 시도 try { // SchedulerBinding을 통해 프레임 스케줄링 상태 확인 return SchedulerBinding.instance.hasScheduledFrame || SchedulerBinding.instance.transientCallbackCount > 0; } catch (_) { // 실패 시 기본값 반환 return false; } } } catch (e) { if (kDebugMode) { debugPrint('타이머 상태 확인 중 오류: $e'); } } // 테스트 환경이 아니거나 오류 발생 시 기본값 반환 return false; } /// 테스트 환경에서 사용할 예측 가능한 해시 생성 (테스트 파일과의 호환성을 위한 별칭) static String generatePredictableHash() { return getTestHash(); } /// 테스트 환경에서 애니메이션을 비활성화하는 위젯 static Widget disableAnimations({required Widget child}) { if (!isInTest) { return child; } return MediaQuery( data: const MediaQueryData( textScaler: TextScaler.linear(1.0), platformBrightness: Brightness.light, padding: EdgeInsets.zero, viewInsets: EdgeInsets.zero, devicePixelRatio: 1.0, size: Size(800, 600), ), child: AnimatedBuilder( animation: const AlwaysStoppedAnimation(1.0), builder: (BuildContext context, Widget? child) { return child!; }, child: child, ), ); } /// 테스트 환경에서 안전하게 SnackBar를 표시하는 함수 static void showSnackBarSafely(BuildContext context, SnackBar snackBar) { if (!isInTest) { ScaffoldMessenger.of(context).showSnackBar(snackBar); return; } // 테스트 환경에서는 SnackBar 표시 대신 로그만 출력 debugPrint('테스트 환경에서 SnackBar 표시 (내용: ${snackBar.content})'); } /// 테스트 환경에서 안전하게 현재 SnackBar를 숨기는 함수 static void hideCurrentSnackBarSafely(BuildContext context) { if (!isInTest) { ScaffoldMessenger.of(context).hideCurrentSnackBar(); return; } // 테스트 환경에서는 로그만 출력 debugPrint('테스트 환경에서 현재 SnackBar 숨김'); } }