Some checks failed
CI / test (push) Has been cancelled
핵심 기능: - 단축근무·표준·반일 등 다양한 근무 패턴 (5개 프리셋 + 사용자 정의) - Windows 이벤트 뷰어 자동 출퇴근 감지 - 30분 단위 연장근무 적립/사용 시스템 - 1.0/0.5/0.25일 연차·반차·반반차 - 자동 점심·저녁·외출·자동 백업·화면 잠금 자동 외출 - 한국 공휴일 자동 등록 (음력 포함, holidays 패키지) - matplotlib 차트 기반 주간/월간/패턴 통계 - 미니 위젯 + 시스템 트레이 통합 - 한국어/English i18n - 자가 업데이트 (updater.exe + Gitea Releases) 아키텍처: - core/ (db, time_calculator, notifier, i18n, version, settings_keys) - ui/ (main_window + 9 dialogs + 3 controllers) - utils/ (backup, lock_detector, debug_log, updater_client, time_format) - tests/ (66 pytest 단위) + 통합/i18n GUI 검증 CI/CD: - .gitea/workflows/ci.yml: push 시 pytest + 통합 테스트 - .gitea/workflows/release.yml: v* 태그 push 시 두 .exe 자동 빌드 + Releases 첨부 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
158 lines
4.9 KiB
Python
158 lines
4.9 KiB
Python
"""
|
|
i18n GUI 검증: 언어 전환 시 UI 라벨이 실제로 바뀌는지.
|
|
|
|
동일 SettingsView를 한국어로 만들고 확인 → 영어로 만들고 확인.
|
|
(런타임 언어 전환은 위젯 재생성을 요구하므로 새 인스턴스로 검증)
|
|
"""
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
|
|
os.environ['QT_QPA_PLATFORM'] = 'offscreen'
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
from PyQt5.QtWidgets import QApplication, QPushButton, QGroupBox
|
|
|
|
app = QApplication.instance() or QApplication(sys.argv)
|
|
|
|
from core.database import Database
|
|
from core.i18n import set_language
|
|
|
|
db_path = os.path.join(tempfile.gettempdir(), 'clockout_i18n.db')
|
|
if os.path.exists(db_path):
|
|
os.remove(db_path)
|
|
db = Database(db_path)
|
|
|
|
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}")
|
|
|
|
|
|
def find_button_text(widget, text_substring):
|
|
for btn in widget.findChildren(QPushButton):
|
|
if text_substring in btn.text():
|
|
return btn
|
|
return None
|
|
|
|
|
|
def find_groupbox_title(widget, title_substring):
|
|
for gb in widget.findChildren(QGroupBox):
|
|
if title_substring in gb.title():
|
|
return gb
|
|
return None
|
|
|
|
|
|
def test_settings_korean_labels():
|
|
"""한국어 모드에서 SettingsView 라벨 검증."""
|
|
set_language('ko')
|
|
from ui.settings_view import SettingsView
|
|
dlg = SettingsView(db=db)
|
|
# 윈도우 제목
|
|
assert '설정' in dlg.windowTitle(), f"title: {dlg.windowTitle()}"
|
|
# 그룹박스
|
|
assert find_groupbox_title(dlg, '근무 시간') is not None
|
|
assert find_groupbox_title(dlg, '연장근무') is not None
|
|
assert find_groupbox_title(dlg, '데이터') is not None
|
|
# 저장 버튼
|
|
save_btn = find_button_text(dlg, '저장')
|
|
assert save_btn is not None
|
|
dlg.deleteLater()
|
|
|
|
|
|
def test_settings_english_labels():
|
|
"""영어 모드에서 SettingsView 라벨 검증."""
|
|
set_language('en')
|
|
from ui.settings_view import SettingsView
|
|
dlg = SettingsView(db=db)
|
|
assert 'Settings' in dlg.windowTitle()
|
|
assert find_groupbox_title(dlg, 'Work Time') is not None
|
|
assert find_groupbox_title(dlg, 'Overtime') is not None
|
|
assert find_groupbox_title(dlg, 'Data') is not None
|
|
save_btn = find_button_text(dlg, 'Save')
|
|
assert save_btn is not None
|
|
dlg.deleteLater()
|
|
set_language('ko') # 원복
|
|
|
|
|
|
def test_main_window_language():
|
|
"""MainWindow 라벨 — 한국어 / 영어"""
|
|
set_language('ko')
|
|
from ui.main_window import MainWindow
|
|
w = MainWindow()
|
|
assert '퇴근시간' in w.windowTitle()
|
|
# 메뉴 버튼: 통계/캘린더/일일보고/도움말/설정/퇴근하기
|
|
assert find_button_text(w, '통계') is not None
|
|
assert find_button_text(w, '캘린더') is not None
|
|
assert find_button_text(w, '도움말') is not None
|
|
assert find_button_text(w, '점심') is not None
|
|
w.deleteLater()
|
|
|
|
|
|
def test_mini_widget_language():
|
|
set_language('ko')
|
|
from ui.mini_widget import MiniWidget
|
|
w = MiniWidget()
|
|
assert '남은 시간' in w.title_label.text()
|
|
w.update_remaining('+00:30:00')
|
|
assert '추가' in w.title_label.text()
|
|
w.deleteLater()
|
|
|
|
set_language('en')
|
|
w2 = MiniWidget()
|
|
assert 'Remaining' in w2.title_label.text()
|
|
w2.update_remaining('+00:30:00')
|
|
assert 'Overtime' in w2.title_label.text()
|
|
w2.deleteLater()
|
|
set_language('ko')
|
|
|
|
|
|
def test_notifier_language():
|
|
"""알림 메시지가 언어에 맞춰 나오는지"""
|
|
set_language('ko')
|
|
from core.notifier import Notifier
|
|
from datetime import datetime, timedelta
|
|
n = Notifier(db=db)
|
|
fired = []
|
|
n.notification_signal.connect(lambda t, m: fired.append((t, m)))
|
|
n.check_clock_out_soon(datetime.now() + timedelta(minutes=20), datetime.now())
|
|
assert len(fired) == 1
|
|
assert '퇴근' in fired[0][0]
|
|
|
|
set_language('en')
|
|
n2 = Notifier(db=db)
|
|
fired2 = []
|
|
n2.notification_signal.connect(lambda t, m: fired2.append((t, m)))
|
|
n2.check_clock_out_soon(datetime.now() + timedelta(minutes=20), datetime.now())
|
|
assert len(fired2) == 1
|
|
assert 'Clock-out' in fired2[0][0]
|
|
set_language('ko')
|
|
|
|
|
|
case("I18N-1. SettingsView 한국어 라벨", test_settings_korean_labels)
|
|
case("I18N-2. SettingsView 영어 라벨", test_settings_english_labels)
|
|
case("I18N-3. MainWindow 한국어 (메뉴/버튼)", test_main_window_language)
|
|
case("I18N-4. MiniWidget ko/en 전환 후 라벨", test_mini_widget_language)
|
|
case("I18N-5. Notifier 알림 메시지 ko/en", test_notifier_language)
|
|
|
|
print()
|
|
print("=" * 60)
|
|
print(f"PASS: {len(PASS)} FAIL: {len(FAIL)}")
|
|
if FAIL:
|
|
print("\nFAILED:")
|
|
for label, err in FAIL:
|
|
print(f" - {label}: {err}")
|
|
|
|
if os.path.exists(db_path):
|
|
try: os.remove(db_path)
|
|
except: pass
|
|
|
|
sys.exit(1 if FAIL else 0)
|