Files
to-live-photo/Tests/LivePhotoCoreTests/LivePhotoCoreTests.swift
empty a8b334ef39 feat(M1): 完成 MVP 核心功能,添加埋点和应用图标
主要改动:
- 移除调试导出功能(exportToDocuments 及相关 UI)
- EditorView 添加封面帧预览和关键帧时间选择
- 新增 Analytics.swift 基础埋点模块(使用 os.Logger)
- 创建 Live Photo 风格应用图标(SVG → PNG)
- 优化 LivePhotoCore:简化代码结构,修复宽高比问题
- 添加单元测试资源文件 metadata.mov
- 更新 TASK.md 进度追踪

M1 MVP 闭环已完成:
 5个核心页面(Home/Editor/Processing/Result/WallpaperGuide)
 时长裁剪 + 封面帧选择
 完整生成管线 + 相册保存 + 系统验证
 壁纸设置引导(iOS 16/17+ 差异化文案)
 基础埋点事件追踪

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 20:34:20 +08:00

286 lines
9.8 KiB
Swift

import XCTest
@testable import LivePhotoCore
final class LivePhotoCoreTests: XCTestCase {
// MARK: - ExportParams Tests
func testExportParamsDefaults() {
let params = ExportParams()
XCTAssertEqual(params.trimStart, 0)
XCTAssertEqual(params.trimEnd, 1.0)
XCTAssertEqual(params.keyFrameTime, 0.5)
XCTAssertEqual(params.audioPolicy, .keep)
XCTAssertEqual(params.codecPolicy, .fallbackH264)
XCTAssertEqual(params.hdrPolicy, .toneMapToSDR)
XCTAssertEqual(params.maxDimension, 1920)
}
func testExportParamsCustomValues() {
let params = ExportParams(
trimStart: 0.5,
trimEnd: 2.0,
keyFrameTime: 1.0,
audioPolicy: .remove,
codecPolicy: .passthrough,
hdrPolicy: .keep,
maxDimension: 1080
)
XCTAssertEqual(params.trimStart, 0.5)
XCTAssertEqual(params.trimEnd, 2.0)
XCTAssertEqual(params.keyFrameTime, 1.0)
XCTAssertEqual(params.audioPolicy, .remove)
XCTAssertEqual(params.codecPolicy, .passthrough)
XCTAssertEqual(params.hdrPolicy, .keep)
XCTAssertEqual(params.maxDimension, 1080)
}
func testExportParamsCodable() throws {
let original = ExportParams(
trimStart: 1.0,
trimEnd: 3.0,
keyFrameTime: 2.0,
audioPolicy: .remove,
codecPolicy: .passthrough,
hdrPolicy: .keep,
maxDimension: 720
)
let encoded = try JSONEncoder().encode(original)
let decoded = try JSONDecoder().decode(ExportParams.self, from: encoded)
XCTAssertEqual(decoded.trimStart, original.trimStart)
XCTAssertEqual(decoded.trimEnd, original.trimEnd)
XCTAssertEqual(decoded.keyFrameTime, original.keyFrameTime)
XCTAssertEqual(decoded.audioPolicy, original.audioPolicy)
XCTAssertEqual(decoded.codecPolicy, original.codecPolicy)
XCTAssertEqual(decoded.hdrPolicy, original.hdrPolicy)
XCTAssertEqual(decoded.maxDimension, original.maxDimension)
}
// MARK: - AppError Tests
func testAppErrorInit() {
let error = AppError(
code: "LPB-101",
stage: .normalize,
message: "Test error",
underlyingErrorDescription: "Underlying",
suggestedActions: ["Action 1", "Action 2"]
)
XCTAssertEqual(error.code, "LPB-101")
XCTAssertEqual(error.stage, .normalize)
XCTAssertEqual(error.message, "Test error")
XCTAssertEqual(error.underlyingErrorDescription, "Underlying")
XCTAssertEqual(error.suggestedActions, ["Action 1", "Action 2"])
}
func testAppErrorCodable() throws {
let original = AppError(
code: "LPB-201",
stage: .extractKeyFrame,
message: "封面生成失败",
underlyingErrorDescription: nil,
suggestedActions: ["重试"]
)
let encoded = try JSONEncoder().encode(original)
let decoded = try JSONDecoder().decode(AppError.self, from: encoded)
XCTAssertEqual(decoded.code, original.code)
XCTAssertEqual(decoded.stage, original.stage)
XCTAssertEqual(decoded.message, original.message)
XCTAssertEqual(decoded.suggestedActions, original.suggestedActions)
}
// MARK: - SourceRef Tests
func testSourceRefWithAssetIdentifier() {
let ref = SourceRef(phAssetLocalIdentifier: "ABC123")
XCTAssertEqual(ref.phAssetLocalIdentifier, "ABC123")
XCTAssertNil(ref.fileURL)
}
func testSourceRefWithFileURL() {
let url = URL(fileURLWithPath: "/tmp/test.mov")
let ref = SourceRef(fileURL: url)
XCTAssertNil(ref.phAssetLocalIdentifier)
XCTAssertEqual(ref.fileURL, url)
}
// MARK: - WorkItem Tests
func testWorkItemDefaults() {
let cacheDir = URL(fileURLWithPath: "/tmp/cache")
let sourceRef = SourceRef(phAssetLocalIdentifier: "test-id")
let item = WorkItem(
sourceVideo: sourceRef,
cacheDir: cacheDir
)
XCTAssertNotNil(item.id)
XCTAssertNotNil(item.createdAt)
XCTAssertEqual(item.status, .idle)
XCTAssertNil(item.resultAssetId)
XCTAssertNil(item.error)
XCTAssertNil(item.coverImage)
}
// MARK: - LivePhotoBuildProgress Tests
func testLivePhotoBuildProgress() {
let progress = LivePhotoBuildProgress(stage: .normalize, fraction: 0.5)
XCTAssertEqual(progress.stage, .normalize)
XCTAssertEqual(progress.fraction, 0.5)
}
// MARK: - LivePhotoBuildStage Tests
func testLivePhotoBuildStageRawValues() {
XCTAssertEqual(LivePhotoBuildStage.normalize.rawValue, "normalize")
XCTAssertEqual(LivePhotoBuildStage.extractKeyFrame.rawValue, "extractKeyFrame")
XCTAssertEqual(LivePhotoBuildStage.writePhotoMetadata.rawValue, "writePhotoMetadata")
XCTAssertEqual(LivePhotoBuildStage.writeVideoMetadata.rawValue, "writeVideoMetadata")
XCTAssertEqual(LivePhotoBuildStage.saveToAlbum.rawValue, "saveToAlbum")
XCTAssertEqual(LivePhotoBuildStage.validate.rawValue, "validate")
}
// MARK: - WorkStatus Tests
func testWorkStatusRawValues() {
XCTAssertEqual(WorkStatus.idle.rawValue, "idle")
XCTAssertEqual(WorkStatus.editing.rawValue, "editing")
XCTAssertEqual(WorkStatus.processing.rawValue, "processing")
XCTAssertEqual(WorkStatus.success.rawValue, "success")
XCTAssertEqual(WorkStatus.failed.rawValue, "failed")
}
// MARK: - CacheManager Tests
func testCacheManagerInit() throws {
let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
let manager = try CacheManager(baseDirectory: tempDir)
XCTAssertEqual(manager.baseDirectory, tempDir)
XCTAssertTrue(FileManager.default.fileExists(atPath: tempDir.path))
// Cleanup
try? FileManager.default.removeItem(at: tempDir)
}
func testCacheManagerMakeWorkPaths() throws {
let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
let manager = try CacheManager(baseDirectory: tempDir)
let workId = UUID()
let paths = try manager.makeWorkPaths(workId: workId)
XCTAssertTrue(paths.workDir.path.contains(workId.uuidString))
XCTAssertEqual(paths.photoURL.pathExtension, "heic")
XCTAssertEqual(paths.pairedVideoURL.pathExtension, "mov")
XCTAssertEqual(paths.logURL.pathExtension, "log")
XCTAssertTrue(FileManager.default.fileExists(atPath: paths.workDir.path))
// Cleanup
try? FileManager.default.removeItem(at: tempDir)
}
func testCacheManagerClearWork() throws {
let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
let manager = try CacheManager(baseDirectory: tempDir)
let workId = UUID()
// Create work directory
let paths = try manager.makeWorkPaths(workId: workId)
XCTAssertTrue(FileManager.default.fileExists(atPath: paths.workDir.path))
// Clear it
try manager.clearWork(workId: workId)
XCTAssertFalse(FileManager.default.fileExists(atPath: paths.workDir.path))
// Cleanup
try? FileManager.default.removeItem(at: tempDir)
}
// MARK: - LivePhotoWorkPaths Tests
func testLivePhotoWorkPaths() {
let workDir = URL(fileURLWithPath: "/tmp/work")
let photoURL = URL(fileURLWithPath: "/tmp/work/photo.heic")
let pairedVideoURL = URL(fileURLWithPath: "/tmp/work/paired.mov")
let logURL = URL(fileURLWithPath: "/tmp/work/builder.log")
let paths = LivePhotoWorkPaths(
workDir: workDir,
photoURL: photoURL,
pairedVideoURL: pairedVideoURL,
logURL: logURL
)
XCTAssertEqual(paths.workDir, workDir)
XCTAssertEqual(paths.photoURL, photoURL)
XCTAssertEqual(paths.pairedVideoURL, pairedVideoURL)
XCTAssertEqual(paths.logURL, logURL)
}
// MARK: - LivePhotoBuildOutput Tests
func testLivePhotoBuildOutput() {
let workId = UUID()
let assetId = "test-asset-id"
let photoURL = URL(fileURLWithPath: "/tmp/photo.heic")
let videoURL = URL(fileURLWithPath: "/tmp/paired.mov")
let output = LivePhotoBuildOutput(
workId: workId,
assetIdentifier: assetId,
pairedImageURL: photoURL,
pairedVideoURL: videoURL
)
XCTAssertEqual(output.workId, workId)
XCTAssertEqual(output.assetIdentifier, assetId)
XCTAssertEqual(output.pairedImageURL, photoURL)
XCTAssertEqual(output.pairedVideoURL, videoURL)
}
// MARK: - Policy Enums Tests
func testAudioPolicyCodable() throws {
let policies: [AudioPolicy] = [.keep, .remove]
for policy in policies {
let encoded = try JSONEncoder().encode(policy)
let decoded = try JSONDecoder().decode(AudioPolicy.self, from: encoded)
XCTAssertEqual(decoded, policy)
}
}
func testCodecPolicyCodable() throws {
let policies: [CodecPolicy] = [.passthrough, .fallbackH264]
for policy in policies {
let encoded = try JSONEncoder().encode(policy)
let decoded = try JSONDecoder().decode(CodecPolicy.self, from: encoded)
XCTAssertEqual(decoded, policy)
}
}
func testHDRPolicyCodable() throws {
let policies: [HDRPolicy] = [.keep, .toneMapToSDR]
for policy in policies {
let encoded = try JSONEncoder().encode(policy)
let decoded = try JSONDecoder().decode(HDRPolicy.self, from: encoded)
XCTAssertEqual(decoded, policy)
}
}
}