- statusLabel/formatSeconds belong to MaterialDetailView, not ViewModel Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
149 lines
5.1 KiB
Swift
149 lines
5.1 KiB
Swift
import XCTest
|
|
@testable import AIStudyApp
|
|
|
|
// MARK: - Mock services
|
|
|
|
private final class MockKnowledgeBaseService: KnowledgeBaseServicing {
|
|
var detailResult: Result<KnowledgeBase, Error> = .failure(NSError(domain: "test", code: -1))
|
|
func detail(id: String) async throws -> KnowledgeBase { try detailResult.get() }
|
|
}
|
|
|
|
private final class MockKnowledgeItemService: KnowledgeItemServicing {
|
|
var items: [KnowledgeItem] = []
|
|
func list(knowledgeBaseId: String, sortBy: String?, order: String?) async throws -> [KnowledgeItem] { items }
|
|
}
|
|
|
|
private final class MockQuizService: QuizServicing {
|
|
var quizzes: [Quiz] = []
|
|
func list(knowledgeBaseId: String) async throws -> [Quiz] { quizzes }
|
|
}
|
|
|
|
private final class MockReadingProgressService: ReadingProgressServicing {
|
|
var progress: MaterialReadingProgressDTO?
|
|
func getReadingProgress(materialId: String, targetType: String) async throws -> MaterialReadingProgressDTO {
|
|
guard let p = progress else { throw NSError(domain: "test", code: -1) }
|
|
return p
|
|
}
|
|
}
|
|
|
|
// MARK: - Tests
|
|
|
|
@MainActor
|
|
final class MaterialDetailViewModelTests: XCTestCase {
|
|
|
|
var vm: MaterialDetailViewModel!
|
|
var mockKB: MockKnowledgeBaseService!
|
|
var mockItem: MockKnowledgeItemService!
|
|
var mockQuiz: MockQuizService!
|
|
var mockReading: MockReadingProgressService!
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
mockKB = MockKnowledgeBaseService()
|
|
mockItem = MockKnowledgeItemService()
|
|
mockQuiz = MockQuizService()
|
|
mockReading = MockReadingProgressService()
|
|
vm = MaterialDetailViewModel(
|
|
knowledgeBaseId: "kb-test-001",
|
|
materialId: "mat-001",
|
|
kbService: mockKB,
|
|
itemService: mockItem,
|
|
quizService: mockQuiz,
|
|
readingService: mockReading
|
|
)
|
|
}
|
|
|
|
override func tearDown() {
|
|
vm = nil
|
|
super.tearDown()
|
|
}
|
|
|
|
// MARK: - Initial state
|
|
|
|
func test_initial_isLoading_is_true() { XCTAssertTrue(vm.isLoading) }
|
|
func test_initial_aiStatus_is_processing() { XCTAssertEqual(vm.aiStatus, .processing) }
|
|
func test_initial_itemCount_is_zero() { XCTAssertEqual(vm.itemCount, 0) }
|
|
func test_initial_isMarkedRead_is_false() { XCTAssertFalse(vm.isMarkedRead) }
|
|
func test_kbId_and_matId_set() {
|
|
XCTAssertEqual(vm.knowledgeBaseId, "kb-test-001")
|
|
XCTAssertEqual(vm.materialId, "mat-001")
|
|
}
|
|
|
|
// MARK: - load() items / quizzes
|
|
|
|
func test_load_sets_itemCount() async {
|
|
mockItem.items = [KnowledgeItem(id: "i1", knowledgeBaseId: "kb1", title: "Item1")]
|
|
await vm.load()
|
|
XCTAssertEqual(vm.itemCount, 1)
|
|
XCTAssertFalse(vm.isLoading)
|
|
}
|
|
|
|
func test_load_sets_quizCount() async {
|
|
mockQuiz.quizzes = [Quiz(id: "q1", knowledgeBaseId: "kb1", title: "Q", questionCount: 3, sourceType: "ai", status: "ready")]
|
|
await vm.load()
|
|
XCTAssertEqual(vm.quizCount, 1)
|
|
}
|
|
|
|
func test_load_aiStatus_ready_when_items_present() async {
|
|
mockItem.items = [KnowledgeItem(id: "i1", knowledgeBaseId: "kb1", title: "X")]
|
|
await vm.load()
|
|
XCTAssertEqual(vm.aiStatus, .ready)
|
|
}
|
|
|
|
func test_load_aiStatus_processing_when_empty() async {
|
|
await vm.load()
|
|
XCTAssertEqual(vm.aiStatus, .processing)
|
|
}
|
|
|
|
func test_load_isLoading_false_after() async {
|
|
await vm.load()
|
|
XCTAssertFalse(vm.isLoading)
|
|
}
|
|
|
|
// MARK: - load() progress
|
|
|
|
func test_load_sets_readingStatus_and_seconds() async {
|
|
mockReading.progress = MaterialReadingProgressDTO(
|
|
materialId: "mat-001", targetType: "knowledge_source",
|
|
status: "in_progress", totalActiveSeconds: 120,
|
|
isMarkedRead: false, lastReadAt: nil, lastPosition: nil
|
|
)
|
|
await vm.load()
|
|
XCTAssertEqual(vm.readingStatus, "in_progress")
|
|
XCTAssertEqual(vm.readingSeconds, 120)
|
|
}
|
|
|
|
func test_load_sets_isMarkedRead_true() async {
|
|
mockReading.progress = MaterialReadingProgressDTO(
|
|
materialId: "mat-001", targetType: "knowledge_source",
|
|
status: "completed", totalActiveSeconds: 300,
|
|
isMarkedRead: true, lastReadAt: nil, lastPosition: nil
|
|
)
|
|
await vm.load()
|
|
XCTAssertTrue(vm.isMarkedRead)
|
|
}
|
|
|
|
func test_load_handles_progress_error_gracefully() async {
|
|
mockReading.progress = nil // throws
|
|
await vm.load()
|
|
XCTAssertNil(vm.readingStatus)
|
|
XCTAssertEqual(vm.readingSeconds, 0)
|
|
XCTAssertFalse(vm.isLoading)
|
|
}
|
|
|
|
func test_load_skips_progress_when_materialId_nil() async {
|
|
let vm2 = MaterialDetailViewModel(
|
|
knowledgeBaseId: "kb-002", materialId: nil,
|
|
kbService: mockKB, itemService: mockItem,
|
|
quizService: mockQuiz, readingService: mockReading
|
|
)
|
|
mockReading.progress = MaterialReadingProgressDTO(
|
|
materialId: "mat-001", targetType: "knowledge_source",
|
|
status: "done", totalActiveSeconds: 99, isMarkedRead: true, lastReadAt: nil, lastPosition: nil
|
|
)
|
|
await vm2.load()
|
|
XCTAssertNil(vm2.readingStatus)
|
|
}
|
|
|
|
}
|