""" GUI smoke 테스트 — QApplication을 띄우지 않고 UI 다이얼로그 instantiation만 확인. 실제 .show()는 헤드리스 환경에서 위험하므로 생성 단계까지만 검증. """ import os import sys import tempfile sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # Qt platform plugin: offscreen으로 실제 창 안 뜨게 os.environ['QT_QPA_PLATFORM'] = 'offscreen' from PyQt5.QtWidgets import QApplication app = QApplication.instance() or QApplication(sys.argv) PASS, FAIL = [], [] def case(label, fn): try: fn() PASS.append(label) print(f"[PASS] {label}") except Exception as e: FAIL.append((label, f"{type(e).__name__}: {e}")) print(f"[FAIL] {label}: {type(e).__name__}: {e}") # Fresh DB from core.database import Database db_path = os.path.join(tempfile.gettempdir(), 'clockout_gui_test.db') if os.path.exists(db_path): os.remove(db_path) db = Database(db_path) def test_settings_view(): from ui.settings_view import SettingsView dlg = SettingsView(db=db) # 단축근무 7h30m 프리셋 적용 dlg.work_preset_combo.setCurrentIndex(1) # "단축근무 7시간 30분" assert dlg.work_hours_spin.value() == 7 assert dlg.work_minutes_spin.value() == 30 assert dlg.lunch_spin.value() == 30 dlg.deleteLater() def test_help_view(): from ui.help_view import HelpView dlg = HelpView() # 6개 탭 존재 tabs = dlg.findChild(type(dlg).__mro__[0]) from PyQt5.QtWidgets import QTabWidget tab_widget = dlg.findChild(QTabWidget) assert tab_widget is not None assert tab_widget.count() == 6 dlg.deleteLater() def test_mini_widget(): from ui.mini_widget import MiniWidget w = MiniWidget() w.update_remaining("01:30:00") assert w.time_label.text() == "01:30:00" w.update_remaining("+00:30:00") assert "추가" in w.title_label.text() w.deleteLater() def test_chart_widget(): from ui.chart_widget import make_chart_widget, draw_daily_hours, draw_weekday_avg w = make_chart_widget() # 빈 records로도 안전하게 그려져야 draw_daily_hours(w, []) # 데이터가 있을 때 records = [ {'date': '2026-04-25', 'total_hours': 8.5, 'overtime_minutes': 30}, {'date': '2026-04-26', 'total_hours': 9.0, 'overtime_minutes': 60}, ] draw_daily_hours(w, records) draw_weekday_avg(w, records) w.deleteLater() def test_stats_view(): from ui.stats_view import StatsView dlg = StatsView(db=db) # 데이터 없어도 정상 로드 assert dlg.weekly_total_hours.text() is not None dlg.deleteLater() def test_calendar_view(): from ui.calendar_view import CalendarView dlg = CalendarView(db=db) dlg.deleteLater() def test_main_window_init(): """MainWindow 초기화 — 가장 무거운 케이스""" # QLocalServer 충돌 방지: 프로세스 ID 기반 이름 변경 어려움 → init만 확인 from ui.main_window import MainWindow w = MainWindow() # 기본 상태 assert w.is_clocked_in == False assert w.lunch_break_enabled == False # auto_lunch 캐시 초기 None assert w._auto_lunch_enabled_cache is None # 단축키 7개 등록되었는지 from PyQt5.QtWidgets import QShortcut shortcuts = w.findChildren(QShortcut) assert len(shortcuts) >= 7, f"shortcuts: {len(shortcuts)}" w.deleteLater() def test_settings_save_load_round_trip(): """설정 저장→로드 라운드트립""" from ui.settings_view import SettingsView dlg = SettingsView(db=db) # 단축근무 프리셋 → 저장 dlg.work_preset_combo.setCurrentIndex(1) # save 호출 시 메시지박스가 뜨므로 save_settings 직접 호출 회피 # 대신 저장 로직과 동일한 dict 만들기 work_min = dlg.work_hours_spin.value() * 60 + dlg.work_minutes_spin.value() db.save_settings({ 'work_minutes': work_min, 'lunch_duration_minutes': dlg.lunch_spin.value(), }) # 새 다이얼로그에 다시 로드 dlg2 = SettingsView(db=db) assert dlg2.work_hours_spin.value() == 7 assert dlg2.work_minutes_spin.value() == 30 assert dlg2.lunch_spin.value() == 30 # 프리셋이 자동 매칭되었는지 assert dlg2.work_preset_combo.currentIndex() == 1 dlg.deleteLater() dlg2.deleteLater() # 실행 case("UI-1. SettingsView: 단축근무 프리셋 적용", test_settings_view) case("UI-2. HelpView: 6개 탭 생성 확인", test_help_view) case("UI-3. MiniWidget: 시간 업데이트 + 추가근무 표시", test_mini_widget) case("UI-4. ChartWidget: 빈/유효 데이터 그리기", test_chart_widget) case("UI-5. StatsView: 데이터 없이 로드", test_stats_view) case("UI-6. CalendarView: 인스턴스화", test_calendar_view) case("UI-7. MainWindow: 단축키 7개 등록 + 초기 상태", test_main_window_init) case("UI-8. Settings 저장→재로드: 단축근무 7h30m round-trip", test_settings_save_load_round_trip) print() print("=" * 60) print(f"PASS: {len(PASS)} FAIL: {len(FAIL)}") if FAIL: print("\nFAILED:") for label, err in FAIL: print(f" - {label}: {err}") # Cleanup if os.path.exists(db_path): try: os.remove(db_path) except: pass sys.exit(1 if FAIL else 0)