Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import SnapshotTesting
import SwiftUI
import XCTest

/// Assert a SwiftUI view's snapshot in both light and dark interface styles.
///
/// 한 호출로 light/dark 페어 baseline 을 생성한다. swift-snapshot-testing 의 `named:` 인자가
/// 동일 `testName` 안에서 light/dark PNG 를 다른 이름으로 보존하므로 파일명 충돌이 없다.
///
/// 다크모드를 그리는 View 스냅샷은 본 헬퍼를 통과시켜 페어 누락을 구조적으로 방지한다.
@MainActor
func assertSnapshotPair<V: View>(
of view: @autoclosure () -> V,
layout: SwiftUISnapshotLayout = .sizeThatFits,
record: Bool? = nil,
fileID: StaticString = #fileID,
file: StaticString = #filePath,
testName: String = #function,
line: UInt = #line,
column: UInt = #column
) {
let rendered = view()
assertSnapshot(
of: rendered,
as: .image(
layout: layout,
traits: UITraitCollection(userInterfaceStyle: .light)
),
named: "light",
record: record,
fileID: fileID,
file: file,
testName: testName,
line: line,
column: column
)
assertSnapshot(
of: rendered,
as: .image(
layout: layout,
traits: UITraitCollection(userInterfaceStyle: .dark)
),
named: "dark",
record: record,
fileID: fileID,
file: file,
testName: testName,
line: line,
column: column
)
}
95 changes: 95 additions & 0 deletions docs/decision/supportDarkMode/snapshotPair/Implementation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Implementation — (2) assertSnapshotPair Helper

> 짝 문서: 같은 경로의 [`PLAN.md`](./PLAN.md)
> 대상 PR: `feat/47-snapshot-pair` (base `feat/47-dark-mode-support` — PLAN(1) PR #48 merge 후 develop 으로 rebase)

## 1. What

PLAN.md 의 In-scope 를 산출물 중심으로 재기술.

### 신규 파일
- `Projects/Core/DoriDesignSystem/Tests/Snapshot/Helpers/SnapshotPair.swift` — `assertSnapshotPair` 헬퍼
- `docs/decision/supportDarkMode/snapshotPair/{PLAN.md, Implementation.md}` — 본 문서 페어

### 수정 파일
- `docs/frontend/test-strategy.md` — Stable Rules 에 페어 검증 1줄 + 헬퍼 위치 명시

### 산출물 (검증 결과)
- 로컬 build-for-testing 성공 로그 (DoriDesignSystemTests 컴파일 OK)
- 헬퍼 smoke TC 의 record 결과 (light/dark 2장 PNG 자동 생성 후 정리)
- CI green run URL

### Scope 외
- 기존 4개 TC 마이그레이션 — 별도 PR
- `DoriTestSupport` 이전 — PLAN(3) 합류 시점
- `.preferredColorScheme(.light)` 제거

## 2. How

3페이즈 순차.

### Phase A — 헬퍼 작성 (커밋 1)

`SnapshotPair.swift` 신설. 핵심 요점:
- `@autoclosure` 로 view-builder 한 번만 평가 후 두 trait 으로 재사용
- `@MainActor` 로 UIKit Trait 접근 안전성 보장
- `named: "light"` / `named: "dark"` 으로 baseline 파일명 충돌 방지
- 모든 source location 인자 (`fileID`, `file`, `testName`, `line`, `column`) 전달하여 실패 메시지가 호출자 함수로 안내

### Phase B — 로컬 smoke 검증 (no commit)

1. `tuist install && tuist generate --no-open`
2. `xcodebuild build-for-testing -workspace Dori-iOS.xcworkspace -scheme DoriDesignSystem -destination 'id=<iPhone 16 UDID>'` 로 컴파일 확인
3. 임시 TC 1개 추가 후 record/replay:
```swift
func test_pairHelper_smoke() {
assertSnapshotPair(of: PrimaryButton(title: "smoke").padding(16).frame(width: 200))
}
```
- 1회차: "No reference" 로 PNG 2장 자동 기록
- 2회차: exit 0
4. 검증 완료 후 임시 함수 + 생성 PNG 삭제. **커밋에 포함하지 않는다.**

### Phase C — 문서 + push (커밋 2)

`docs/frontend/test-strategy.md` Stable Rules 에 1줄, PLAN/Implementation 문서 같은 커밋. push.

#### Hook 루프

```bash
git push origin feat/47-snapshot-pair
RUN_ID=$(gh run list --branch feat/47-snapshot-pair --limit 1 --json databaseId --jq '.[0].databaseId')
gh run watch "$RUN_ID" --exit-status
```

build/test green 이면 done. fail 분류는 PLAN(1) Implementation 의 표 그대로.

## 3. When

- **순차 의존**: A → B → C
- **smoke 결과는 커밋 외**: 임시 TC/PNG 가 PR 에 새지 않도록 검증 직후 정리
- **기존 baseline 비건드림**: 헬퍼는 새 TC 만 쓰기 때문에 기존 16장 PNG 는 변경 0. 이 분리가 PR review 의 핵심 안전선
- **base branch**: PLAN(1) 의 swift-snapshot-testing 의존성에 의존하므로 PR #48 머지 전까진 base 가 `feat/47-dark-mode-support`. PR #48 머지 후 develop 으로 rebase

## 4. 종료기준 (Definition of Done)

- [ ] `Projects/Core/DoriDesignSystem/Tests/Snapshot/Helpers/SnapshotPair.swift` 생성, 빌드 통과
- [ ] 로컬 smoke TC 로 light/dark PNG 2장 자동 기록 확인 (검증 후 삭제)
- [ ] `docs/frontend/test-strategy.md` 에 헬퍼 규칙 1줄 명시
- [ ] PR CI 의 build · test step green
- [ ] 기존 4개 TC 의 baseline PNG 파일명/내용 변경 0건
- [ ] 본 Implementation.md "검증 기록" 에 CI URL 첨부

### 검증 기록 (완료 시 채움)

- 컴파일 로그: _(채울 자리)_
- smoke record 로그: _(채울 자리)_
- CI green run URL: _(채울 자리)_

## 사용한 기존 자산

- [`PLAN.md`](./PLAN.md)
- `../validateDarkMode/Implementation.md` — Phase/Hook 루프 패턴 참조
- `Projects/Core/DoriDesignSystem/Tests/Snapshot/PrimaryButtonSnapshotTests.swift` — 헬퍼가 대체할 기존 패턴
- swift-snapshot-testing 1.18+ `assertSnapshot(of:as:named:...)` — `named:` 인자 동작
- `Tuist/Package.swift` — swift-snapshot-testing 1.18.0 이미 등록 (PLAN(1) PR #48)
99 changes: 99 additions & 0 deletions docs/decision/supportDarkMode/snapshotPair/PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Dark Mode Validation — (2) assertSnapshotPair Helper

## Context

다크모드 검증 자동화의 3단계 중 2단계.
- (1) Asset Lint — 완료 (`../validateDarkMode/PLAN.md`, PR #48)
- (2) **`assertSnapshotPair` 헬퍼** — 본 PLAN
- (3) Feature 모듈 카탈로그 스냅샷 페어 — 별도 PLAN (`../testingDarkMode/PLAN.md`)

`DoriDesignSystem/Tests/Snapshot/*` 의 4개 TC 가 light/dark 두 함수로 분리돼 있다.

```swift
func test_enabled_light() {
assertSnapshot(
of: host { PrimaryButton(title: "저장") },
as: .image(layout: .sizeThatFits, traits: UITraitCollection(userInterfaceStyle: .light))
)
}

func test_enabled_dark() {
assertSnapshot(
of: host { PrimaryButton(title: "저장") },
as: .image(layout: .sizeThatFits, traits: UITraitCollection(userInterfaceStyle: .dark))
)
}
```

PLAN(3) 카탈로그가 같은 패턴을 그대로 늘리면 ~106장이 1개 화면당 두 함수 × view-builder 중복으로 확산된다. 한쪽만 추가하고 다른 쪽을 잊는 사각지대도 생긴다.

이 PLAN 은 **"light/dark 페어 보장"** 을 한 호출에 강제하는 얇은 헬퍼만 도입한다. 기존 4개 TC 의 마이그레이션과 PLAN(3) 의 신규 카탈로그 작성은 별도 작업.

## Scope

### In scope
- `assertSnapshotPair` 헬퍼 신설 (`Projects/Core/DoriDesignSystem/Tests/Snapshot/Helpers/SnapshotPair.swift`)
- `named:` 인자로 light/dark suffix 부여 → baseline 파일명 충돌 방지
- `docs/frontend/test-strategy.md` 에 페어 검증 규칙 1줄 명문화 → 미래 에이전트가 패턴을 잊지 않게

### Out of scope (별도 작업)
- 기존 4개 TC (`ColorTokenSnapshotTests`, `PrimaryButtonSnapshotTests`, `DoriCommonAlertSnapshotTests`, `DoriToastViewSnapshotTests`) 의 마이그레이션 — baseline 재기록 비용 별도 PR
- `DoriTestSupport` 모듈로 헬퍼 이전 — PLAN(3) Phase B 합류 시
- Feature 카탈로그 작성 — PLAN(3)

## Approach

### A. 헬퍼 시그니처

```swift
@MainActor
func assertSnapshotPair<V: View>(
of view: @autoclosure () -> V,
layout: SwiftUISnapshotLayout = .sizeThatFits,
record: Bool? = nil,
fileID: StaticString = #fileID,
file: StaticString = #filePath,
testName: String = #function,
line: UInt = #line,
column: UInt = #column
)
```

내부적으로 `assertSnapshot(of:as:named:...)` 을 두 번 호출 — `named: "light"` / `named: "dark"`. `testName` 그대로 전달하여 호출자-함수-기반 baseline 디렉토리 규칙 유지.

### B. baseline 파일명 규칙

- 기존 (별도 함수 × 2): `__Snapshots__/PrimaryButtonSnapshotTests/test_enabled_light.1.png`
- 신규 (페어 헬퍼): `__Snapshots__/<TestClass>/test_enabled.light-<n>.png` (정확한 포맷은 swift-snapshot-testing 1.18+ `named:` 동작)

헬퍼는 새 TC 만 만들면 기존 baseline 과 충돌하지 않음.

### C. 문서화

`docs/frontend/test-strategy.md` 의 Stable Rules 에 1줄 추가:

> 다크모드를 그리는 View 스냅샷은 light/dark 한 쌍으로만 baseline 을 생성한다. 헬퍼는 `Projects/Core/DoriDesignSystem/Tests/Snapshot/Helpers/SnapshotPair.swift` 의 `assertSnapshotPair`.

AGENTS.md Read Order 에 `docs/frontend/test-strategy.md` 가 이미 포함돼 있어 변경 없음.

## Files

| 작업 | 경로 |
|---|---|
| Create | `Projects/Core/DoriDesignSystem/Tests/Snapshot/Helpers/SnapshotPair.swift` |
| Modify | `docs/frontend/test-strategy.md` |
| Create | `docs/decision/supportDarkMode/snapshotPair/PLAN.md` |
| Create | `docs/decision/supportDarkMode/snapshotPair/Implementation.md` |

## Verification

1. **빌드 통과** — `tuist generate && xcodebuild build-for-testing -scheme DoriDesignSystem` 컴파일 OK
2. **헬퍼 smoke** — 임시 TC 1개로 light/dark PNG 2장 자동 기록 확인 후 정리 (커밋 제외)
3. **기존 baseline 무손상** — 마이그레이션 안 함. 기존 16장 PNG 파일명/내용 변경 0건
4. **CI green** — PR push 후 Build App workflow build/test step 통과

## Out of Scope but Tracked

- 기존 4개 TC 마이그레이션 — 헬퍼 안정성 확인 후 별도 PR. baseline 재기록 + diff 0건 검증 필요.
- `DoriTestSupport` 이전 — PLAN(3) Phase B 합류 시 모듈로 이동, feature 테스트 import.
- `.preferredColorScheme(.light)` 제거 — (1)(2)(3) 전부 완료 + 카탈로그 다크 스냅샷 전수 통과 후.
1 change: 1 addition & 0 deletions docs/frontend/test-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- 테스트는 상태 전이와 경계 동작을 우선 검증한다.
- 외부 의존성은 대체 가능해야 한다.
- navigation과 인증처럼 깨지기 쉬운 흐름은 Reducer 수준에서 검증 가능해야 한다.
- 다크모드를 그리는 View 스냅샷은 light/dark 한 쌍으로만 baseline 을 생성한다. 헬퍼는 `Projects/Core/DoriDesignSystem/Tests/Snapshot/Helpers/SnapshotPair.swift` 의 `assertSnapshotPair`.

## Secondary Rules

Expand Down