프론트엔드 테스트 도입 — 설계 문서
작성일: 2026-06-10 상태: 범위 승인 완료, 구현 계획 수립 대기 룰 문서: .claude/rules/frontend/testing.md (테스트 작성 규칙 — 본 문서는 무엇을 먼저 만들지를 결정)
1. 목표
전 앱에 프론트엔드 테스트를 도입하되, 단일 원칙으로 조직한다: 조용한 깨짐(silent breakage)을 잡는다 — throw 없이 그럴듯한 오답을 내는 로직(급여 숫자, 날짜/타임존, 이스케이프, URL/탭 라우팅, 필드 매핑). 커버리지 퍼센트는 명시적으로 목표가 아니다.
사용자와 합의된 범위 결정:
- 파일럿이 아닌 전 앱 도입, 유닛 우선 + 선별적 컴포넌트 테스트. e2e는 임계 여정만.
- 이번 작업의 산출물은 러너 + 테스트 코드. 공유 vitest 프리셋 패키지, happy-dom/Testing Library 셋업, CI 연동, 커버리지 게이트는 이후 인프라 작업으로 분리.
- 타 앱으로의 E2E 확장은 이번 작업에 포함.
2. 근거 자료
세 가지 조사가 대상 선정의 근거다:
- 코드 전수조사 —
apps/*/src와packages/components/src의 export 심볼 166개 + 거대app.ts내부의 비공개 헬퍼(team-manager 6,069줄, admin 5,852줄 + scheduler 7,725줄, hr-system 6,028줄)까지 확인. 현재 프론트엔드 테스트 도구는 전무: vitest는packages/test에만 존재(v2.1.9), happy-dom/jsdom/@testing-library는 어디에도 미설치. - 버그 히스토리 마이닝 — 프론트엔드 fix 커밋 ~95건(2025-11 이후)의 클러스터: 탭 라우팅 회귀(6건+, PR 251/252), Messenger 정렬/타임존(9건+), GA의 API 인터페이스 불일치(PR 227/249),
positionFloatingMenu동일 버그 2회 수정, importer 컬럼 중복 제거 수정, 로그인 식별자 trim, API base URL 파싱(PR 253). - 중복 감사 —
kr-holidays.ts가 admin/team-manager에 동일 사본으로 존재;toDateStr(UTC 기반, 하루 밀림 의심 버그)이 team-manager app.ts와 admin scheduler.ts에 중복;positionFloatingMenu는 components/admin 사본이 이미 드리프트;router.ts변형이 7개 앱에 존재.
3. 유닛 테스트 — Wave 1 (테스트 파일 21개)
러너: 8개 패키지(hr-system, importer, admin, team-manager, employee-app, general-affairs, management-dashboard, packages/components)에 vitest devDependency + "test": "vitest run" / "test:watch": "vitest" 스크립트 추가. zero-config(기본 env node, 코로케이션 *.test.ts 자동 탐색). 이번 wave에서는 vitest.config 파일을 만들지 않는다.
| 패키지 | 테스트 파일 | 대상과 근거 |
|---|---|---|
| hr-system (6) | escape-html.test.ts | 수제 XSS 이스케이프 — 케이스 하나 누락이 조용히 XSS를 연다 |
selection.test.ts | 셀 선택 + TSV 클립보드 export | |
history.test.ts | undo/redo 트랜잭션 스택 | |
styles.test.ts | buildSelectionCss/buildAnchorCss 순수 문자열 빌더 | |
router.test.ts | stripBasePath/withBasePath — 탭 라우팅 버그군 | |
layout-persist.test.ts | localStorage 레이아웃 ↔ 기본 컬럼 병합(storage 스텁) | |
| importer (5) | excel-reader.test.ts | excelDateToDate/parseString/parseNumber — 급여 숫자 파싱. 날짜 serial 특이점은 characterization |
sheet-tab.parser.test.ts | 47개 시트가 타는 제네릭 엔진(parseGenericTab, inferColumnFormat) — 컬럼 중복 수정 이력 | |
payroll-ledger.parser.test.ts | 급여대장 매핑(characterization) | |
overtime.parser.test.ts | 시간외 파싱(characterization) | |
salary-grade.parser.test.ts | 호봉 파싱(export 3개) | |
| admin (2) | kr-holidays.test.ts | isHoliday/getHolidayName 동작 |
scheduler-helpers.test.ts | toDateStr(characterization — UTC 하루 밀림 의심 버그, 별도 보고), getAssignmentLabels, initialTabFromUrl | |
| team-manager (2) | kr-holidays-sync.test.ts | admin/team-manager 공휴일 사본 deep-equal 동기화 가드 |
app-helpers.test.ts | toDateStr 쌍둥이 + 탭 헬퍼 | |
| employee-app (1) | api-environment-store.test.ts | normalizeApiBaseUrl/getStartupApiBaseUrl(Capacitor 모킹) — PR 253 버그군 |
| general-affairs (1) | app-helpers.test.ts | safeNumber, formatDateTimeLocal(타임존), formatDateForInput |
| management-dashboard (1) | month-helpers.test.ts | previousMonthOf(연 경계), formatYearMonth, generateYearRange, currentMonth |
| components (3) | positionFloatingMenu.test.ts | 위치 기하 계산 — 동일 버그 2회 수정 + 사본 드리프트 |
messenger-helpers.test.ts | 대화 정렬 + 시간 표시(순수 헬퍼로 추출 후) — 정렬·타임존 모두 수정 이력 보유 | |
LoginForm.test.ts | 식별자 정규화(trim) — 팩토리 객체 메서드 직접 호출 |
동반 소스 변경 (동작 무변경)
- 현재 비공개인 헬퍼 ~9개에
export추가: team-manager/admin의toDateStr,getAssignmentLabels,initialTabFromUrl; general-affairs의safeNumber/formatDateTimeLocal/formatDateForInput; management-dashboard의 월 산술 헬퍼들. - Messenger의 대화 정렬·시간 표시 로직을 순수 헬퍼 함수로 추출(같은 파일 또는 형제 모듈) — DOM 없이 테스트 가능하게.
- characterization 테스트는 현재 동작을 고정한다. 동작이 틀려 보이는 경우(
toDateStr의 UTC 변환)에도 테스트는 현재 동작을 기록하고 발견 사항만 사용자에게 보고 — 이번 작업에서 동작 수정은 하지 않는다(사용자가 요청하면 별도 진행).
4. E2E 확장 (Playwright, packages/test)
인프라: packages/test/playwright.config.ts에 앱별 project 추가(admin :4001, employee-app :4002, team-manager :4004, general-affairs :4005) — 각자 baseURL과 storageState 보유. global-setup을 확장해 필요한 페르소나를 API로 로그인(packages/test/src/scenarios/personas.ts의 시드 계정 재사용). 데이터는 API로 시드, 역할 기반 로케이터, 테스트 완전 격리 — .claude/rules/frontend/testing.md 준수.
신규 spec 5개 (임계도·버그 히스토리 순):
e2e/tests/apps/leave-approval.spec.ts— employee-app에서 휴가 신청 → team-manager에서 승인 → employee-app에서 상태 반영 확인 (크로스앱 핵심 루프).e2e/tests/apps/overtime-approval.spec.ts— 같은 루프를 시간외로 (#1과 헬퍼 공유).e2e/tests/apps/admin-tab-routing.spec.ts— admin: 모든 탭 클릭 시 URL 갱신 → 새로고침 시 같은 탭+데이터 복원 → 뒤로가기 동작 (회귀 6건+ 클러스터 방어, PR 251/252).e2e/tests/apps/admin-add-employee.spec.ts— AddEmployeeWizard 끝까지 진행 → 직원 목록에 표시 (공유 컴포넌트, 온보딩 임계).e2e/tests/apps/ga-task-training.spec.ts— general-affairs: task 생성 → 실행 → 이수 기록; 법정의무교육 생성/이수 (PR 227/249 QA 클러스터 방어).
기존 hr-system 워크북 spec 15개는 그대로 유지.
5. 비범위 (연기 — 폐기 아님)
- 공유
@hereby/vitest-config프리셋 패키지, happy-dom + Testing Library 설치, Alpine 마운트 헬퍼 — Wave 1 이후 인프라 작업. - Wave 2 유닛: 나머지 bestrh 파서 8개,
engine.ts/workbook-adapter.tscharacterization,events.ts, Alpine 컴포넌트 레퍼런스 테스트, 나머지 router 변형들. - CI 파이프라인 연동과 커버리지 임계값.
- 중복 모듈 통합(kr-holidays, toDateStr, positionFloatingMenu, router 패밀리) — 별도 리팩터링으로 표시. 통합 전까지는 동기화/characterization 테스트가 중복을 가드.
6. 검증·완료 기준
- 신규 테스트 파일 전부가 각 패키지의
pnpm --filter <pkg> test로 green. - repo 전체
pnpm lint:check && pnpm build통과. - e2e spec이 로컬에서
make dev+ 시드 DB 기준 green (pnpm test:e2e). - 산출 보고: 패키지별 추가 테스트, 표면화된 의심 버그(예:
toDateStr), 후속 항목(Wave 2, 인프라 작업, 중복 통합 리팩터링).
7. Wave 1 구현 중 표면화된 의심 버그 (수정 안 함 — 별도 결정 필요)
toDateStr(team-manager app.ts / admin scheduler.ts 중복): Date 입력 시toISOString()기반이라 KST 자정~09:00 시각이 전날 날짜로 변환됨. 간호사 근무표 날짜 계산에 영향 가능. characterization 테스트로 고정됨 (toDateStr(new Date("2026-06-10T00:00:00+09:00"))→"2026-06-09"실측).formatDateForInput(general-affairs app.ts): 동일한 UTC 변환 패턴 — KST 자정 입력이 전날로 밀림 (테스트로 실측 고정).parseNumber(importer excel-reader.ts): 회계식 음수"(1,000)"이 조용히 0이 됨 — 엑셀 원본에 회계 서식 음수가 있으면 데이터 소실 가능.loadLayout(hr-system layout-persist.ts): 신규 기본 컬럼이 저장 레이아웃과 병합될 때 사용자 순서와 교차하면 직관과 다른 순서가 나올 수 있음 (실측: 저장 [c,a] + 신규 b(기본 1번 위치) → [b,c,a] — b가 맨 앞에 끼어듦).
참고: repo 전체 pnpm lint:check는 mockups/option-*.html 2건의 기존 에러로 실패 상태였음(이번 작업과 무관, merge-base 시점부터 존재). 신규 테스트 파일은 린트 에러 0건.